ONE STOCK (중소 제조업 ERP 위에 AI를 얹으면 생기는 일)

3줄 요약

  1. 이카운트 ERP의 공식 OAPI를 MCP 서버로 감싸면 클로드 같은 AI가 재고·판매·BOM을 도구로 불러 쓴다.
  2. 공식 OAPI에 없는 BOM은 ERP 웹 화면이 내부에서 보내는 요청으로 가져왔다. 30분마다 끊기는 세션은 자동 재로그인으로 풀었다.
  3. 14일 만에 만들어 지금 화장품 제조업체 60명이 쓴다. Teams 재고봇을 붙인 뒤 재고 문의가 90% 줄었다.

"재고 몇 개 남았어요?"

뷰티 제조업체에서는 하루에도 이 질문이 수십 번 오간다.

영업팀도, 생산팀도, 대표도 묻는다. 그때마다 누군가 이카운트 ERP에 들어가 품목코드를 검색하고 창고별 수량을 확인한 뒤 Teams로 답을 보낸다. 한 건에 2-3분이다. 하루 열몇 번이면 은근히 시간을 잡아먹는다.

나는 제조업 기반 뷰티 브랜드 회사에서 브랜드사업부 팀장으로 일한다. 옆자리 영업팀이 "이거 재고 있어?"를 확인하느라 매번 흐름이 끊기는 걸 계속 봤다. 시간보다 큰 문제는 따로 있었다. 묻는 사람도 답하는 사람도 자기 일을 멈춰야 했다.

아무도 이걸 문제라고 하지 않았다. 원래 그런 거니까.

데이터는 있었다. 연결만 없었다

들여다보니 문제는 재고 조회 하나가 아니었다.

  • 재고 → 이카운트 ERP
  • 생산 요청 → Teams 채팅
  • BOM(자재명세서) → 엑셀 파일
  • 생산 계획 → 브랜드팀·B2B팀·생산팀이 따로 관리하는 시트

데이터가 따로 노니 "이 제품 100개 만들려면 원자재 충분해?" 같은 질문 하나에도 ERP를 켜고 엑셀을 열고 계산기를 두드려야 했다.

중소 제조업 163,000개 중 76%가 ERP를 쓰는데 AI를 쓰는 곳은 1%도 안 된다. 흩어진 데이터를 잇기만 하면 뭔가 달라질 것 같았다.


이카운트 API를 하나씩 붙이다 MCP를 만났다

처음엔 단순하게 생각했다. 이카운트 ERP에 OAPI가 있으니 API를 하나씩 불러서 필요한 데이터를 뽑으면 될 줄 알았다.

막상 해보니 보통 일이 아니었다.

재고 조회, 판매 조회, BOM API가 다 따로였다. 인증 방식도 응답 포맷도 달랐고 세션도 하나씩 관리해야 했다. API 10개를 붙이면 인증 로직 10개, 에러 처리 10개, 캐시 10개가 생긴다. 혼자 유지보수할 수 있는 구조가 아니었다.

나는 마케터다. IT팀도 없고 외주 예산도 없다.
그렇다고 이 병목을 대신 풀어 줄 사람도 없었다.
그래서 직접 만들기로 했다.

개발은 AI 에이전트에게 맡기기로 했다. 그러다 MCP(Model Context Protocol)를 알게 됐다.

MCP는 C타입 하나로 8핀도 USB-A도 다 꽂히는 허브다

MCP는 멀티허브 젠더에 빗대면 제일 쉽다.

맥북에는 C타입 포트밖에 없는데 연결할 기기는 제각각이다. 아이폰은 8핀, 외장하드는 USB-A, 모니터는 HDMI다. 기기마다 젠더를 사서 따로 꽂다 보면 끝이 없다. 그래서 멀티허브 젠더를 쓴다. C타입 하나만 꽂으면 그 뒤로 뭐든 연결된다. 맥북은 상대 기기 규격을 몰라도 된다. 허브가 다 변환한다.

MCP가 딱 그 역할을 한다. ERP API는 인증 방식이 이렇고 응답 포맷이 저렇고 세션은 30분마다 갱신해야 한다. 대시보드는 이 규격을 하나하나 알 필요가 없다. MCP 서버가 중간에서 다 바꿔준다. 재고 조회, BOM 검색, 판매 현황 같은 기능을 "도구(Tool)"로 한 번만 정의해두면 AI든 대시보드든 Teams 봇이든 같은 방식으로 부르면 끝이다.

대시보드 → "재고 조회해줘" → MCP 서버 → 이카운트 ERP API 호출 → 결과 반환
                                    ↓
                              세션 관리, 에러 처리, 캐싱: 전부 여기서 해결

API 10개를 따로 관리하는 대신 MCP 서버 하나가 핸들러 10개를 품는다. 새 기능이 필요하면 MCP 서버에 핸들러를 하나 추가하면 된다. 대시보드 코드는 손대지 않는다.

MCP Architecture

이 그림이 그 구조다. 맥북 자리에 Claude나 GPT 같은 AI(MCP 클라이언트)가 있고, 꽂힌 케이블 하나하나가 MCP 서버다. 왼쪽은 Slack·Gmail·캘린더 같은 외부 서비스, 오른쪽은 로컬 데이터다. AI는 허브만 보면 되고 서비스마다 규격은 몰라도 된다. ONE STOCK에서는 이 케이블 중 하나가 이카운트 ERP다.

ONE STOCK 시스템 아키텍처

ONE STOCK 구조는 이렇다. React 대시보드가 Express Proxy를 거쳐 MCP 서버를 부른다. MCP 서버 안에는 재고조회·BOM검색·수요예측 같은 도구가 들어 있다. 이 도구들이 이카운트 ERP API를 직접 호출하거나 SQLite 캐시에서 데이터를 꺼낸다.

데이터를 가져오는 길이 하나로 모이니 대시보드는 어느 화면이든 같은 소스에서 같은 방식으로 값을 받는다. 이 규모의 시스템을 나 혼자 만들고 유지할 수 있었던 것도 이 구조 덕분이다.

이카운트 OAPI 연결은 세 단계다

이카운트 OAPI를 처음 붙일 때 제일 먼저 막히는 건 주소다. 로그인 주소가 회사마다 다르다.

  1. Zone 조회. 회사코드를 oapi.ecount.com에 보내면 우리 회사 서버 구역(Zone)이 돌아온다. 모든 회사가 같은 주소를 쓰는 건 이 단계뿐이다.
  2. 로그인. oapi 바로 뒤에 Zone을 붙인 주소로 인증키를 보내면 SESSION_ID를 받는다. 유효 시간은 30분이다. 테스트키는 oapi 대신 sboapi를 쓴다.
  3. 호출. 그 뒤 모든 API는 주소 끝에 SESSION_ID를 붙여 부른다. 조회 기간은 한 번에 최대 30일, 호출 간격은 1.1초 이상이다.

실서버키를 받았다고 모든 API가 열리지는 않는다. 테스트키로 한 번 불러서 통과한 API만 실서버키로 열린다. 이카운트는 이걸 "검증"이라고 부른다.

구조는 그럴듯했다. 이제 실제로 만들 차례였다.


14일의 기록

사고 1: 백엔드한테 말 안 하고 외주 줬다가 반나절 날린 날

솔직히 이 프로젝트에서 제일 어처구니없는 사고는 내가 쳤다.

백엔드는 Claude Code로 만들던 중이었다. 어느 날 문득 이런 생각이 들었다.

"프론트엔드는 Antigravity가 더 잘 만들지 않나?"

그럴듯한 생각이었다. 백엔드 담당과 프론트엔드 담당이 따로 있으면 프론트는 프론트 전문가한테 맡기는 게 당연하지 않나. 그래서 백엔드 담당 Claude Code한테는 아무 말도 하지 않고 조용히 Antigravity 창을 열었다.

"프론트엔드 전체 구조 잡아줘. 백엔드 API랑 연결까지."

Antigravity는 정말 열심히 작업했다. 문제는 그러면서 백엔드 설정 파일을 자기 방식대로 싹 정리해버렸다는 거다. 환경 변수 파일을 덮어쓰고 포트 설정을 바꾸고 API 엔드포인트 경로를 새로 짰다. 며칠 걸려 쌓은 백엔드 구조가 30분 만에 흔적도 없이 사라졌다.

This is fine

git도 안 쓰고 있어서 복구할 방법이 없었다. 기억을 더듬어 백엔드를 처음부터 다시 짰다.

사람이든 AI든 말없이 남의 영역을 넘으면 반드시 충돌이 난다.

백엔드 담당한테 한 마디만 했어도 안 날 사고였다. 그 뒤로는 어느 AI가 어느 파일을 건드릴지 미리 못 박아 두는 버릇이 생겼다.

사고 2: 공식 문서에 없는 API를 이틀 동안 캐낸 이야기

이카운트 공식 OAPI에는 BOM 조회 API가 없다.

없다는 걸 확인하고 솔직히 잠깐 멈칫했다. BOM이 없으면 "원자재 충분한지 확인"이라는 핵심 기능이 통째로 날아간다. 공식 문서에 없으면 방법이 없는 건가, 다른 길이 있나.

다른 길을 찾기로 했다.

이카운트 웹 화면을 열고 F12를 눌렀다. 개발자 도구의 네트워크 탭을 띄워 두고 BOM 화면으로 넘어가면서 서버로 어떤 요청이 나가는지 하나씩 들여다봤다. 버튼을 누르고, 요청을 확인하고, 파라미터를 뜯어보는 일을 이틀 동안 반복했다.

이카운트 화면에 있는 기능이면 어딘가에 API가 반드시 있다. 공식 문서에 없을 뿐 웹 화면이 쓰고 있으면 나도 부를 수 있다. 그 믿음 하나로 버텼다.

이틀째 되던 날 드디어 잡았다.

// 이카운트 내부 API - BOM 조회
const bomEndpoint = 'ECAPI/SVC/Inventory/Bom/GetListBomStatus'
 
// 이 한 줄을 찾기까지 이틀이 걸렸다

엔드포인트를 찾자마자 호출해봤다. BOM 2,813건이 한 번에 쏟아졌다. 제품별 원자재 구성과 수량, 공정 정보까지 전부 들어 있었다.

묘한 쾌감이 있었다. 이틀 동안 F12 누르고 버튼만 클릭하던 사람이 갑자기 ERP 속을 다 들여다본 기분이었다. 개발에 재미를 붙인 게 아마 이때부터였다.

이카운트 관계자분이 이 글을 보신다면 공식 OAPI에 BOM 엔드포인트를 꼭 추가해주세요. 진심입니다.

세션 버그로 30분마다 시스템이 먹통이 됐다

ERP API 연동에서 제일 골치 아팠던 건 세션 관리였다. 이카운트는 접속할 때마다 인증 토큰을 내주는데 이 토큰이 30분마다 만료된다. 처음엔 토큰을 임의로 만들어 넣었다가 시스템 전체가 먹통이 됐다.

// ❌ 이렇게 하면 안 된다
const session = Math.random().toString(36)
 
// ✅ 로그인 응답에서 추출해야 한다
const session = loginResponse.split(':')[1]

그래서 API를 부를 때마다 먼저 "토큰 유효한지 확인 → 만료됐으면 자동 재로그인" 로직을 거치게 했다. 그 뒤 프로덕션에서 인증 에러는 0건이다. 이때 만든 세션 관리 로직이 나중에 대시보드 "실시간 확인" 기능의 바탕이 됐다.

실시간이냐 캐시냐

재고 조회에서는 고민이 하나 있었다. ERP에 매번 직접 물어보면 정확하지만 느리다. 이카운트 API는 호출 간격이 1.1초로 묶여 있어서 품목 여러 개를 한 번에 조회하면 몇 초씩 걸린다. 미리 저장해둔 데이터를 보여주면 빠르지만 최신이 아닐 수 있다.

그래서 둘 다 두고 사용자가 고르게 했다. 평소엔 SQLite에 저장된 데이터로 바로 답하고, 급할 땐 "실시간 확인" 버튼으로 ERP를 직접 부른다. Teams 봇이 0.8초 만에 답하는 것도 이 캐시 덕분이다.

연동하다 보면 이런 선택이 여섯 번쯤 나온다. 뭘 미리 정해야 하는지, 안 정하면 뭐가 기본값이 되는지는 ERP를 AI에 붙이기 전에 정해야 하는 여섯 가지에 모아 뒀다.

그렇게 웹 대시보드와 Teams 봇이 같이 돌아가는 구조를 갖춰 14일 만에 배포했다.


화면으로 보는 ONE STOCK

종합현황, 대표가 30초 만에 현장을 본다

ONE STOCK 종합현황

처음 열면 이 화면이 뜬다. 회사 총 재고 자산이 완제품·원자재·부자재·재공품(WIP) 네 묶음으로 나뉘어 한눈에 들어온다. 이달 생산 요청 건수, 가동 중인 라인 수, 생산 효율, 미해결 품질 이슈도 같이 보인다. 대표는 ERP에 들어가지 않아도 지금 현장 상태를 바로 안다.

아래쪽엔 안전재고에 못 미치는 품목 12개가 자동으로 올라온다. 히알루론산 원액은 안전재고 아래로 내려갔고 포장 박스 S도 절반만 남았다. 담당자가 매일 ERP를 뒤지기 전에 시스템이 먼저 경고를 띄운다.

오른쪽엔 품질 이슈 3건이 붙어 있다. HA 수분마스크 클레임, 모이스처 토너 공정 불량, 센텔라 크림 원료 입고 이슈다. 품질팀이 따로 보고서를 돌리지 않아도 여기서 바로 확인한다.

긴급 생산 요청, 구두 확인 3단계를 클릭 하나로

ONE STOCK 긴급 생산 요청

월별 생산 계획은 이미 잡혀 있다. 문제는 그 사이에 터지는 일이다.

거래처에서 갑자기 추가 오더가 들어오거나 자사몰 이벤트로 B2C 물량이 급히 필요해질 때가 있다. 전에는 이렇게 처리했다. 영업팀이 생산팀에 연락해 케파가 되냐고 묻고, 생산부장이 일정을 보고, 된다 안 된다 말로 답하고, 그걸 다시 Teams로 공유했다. 빠르면 한 시간, 느리면 다음 날이었다.

지금은 이 화면에 품목명·부서·수량·납기·사유를 넣고 등록하면 끝이다. 요청은 목록에 바로 쌓이고 생산부장한테 Teams 알람이 간다. 부장이 케파를 보고 답하는 것까지 이 화면 안에서 이어진다.

말로 오가던 확인이 시스템 안으로 들어오니 빠지는 요청이 없다. 등록하면 누군가는 반드시 확인한다.

생산관리, 엑셀 세 개에 흩어진 정보를 한 화면에

ONE STOCK 생산관리

원래 이 정보는 엑셀에 있었다. 브랜드팀이 수주 정보를 넣고 B2B팀이 고객사 납기를 고치고 생산팀이 공정 진행을 따로 관리했다. 세 팀이 각자 시트를 보면서 연락을 주고받았다. "이거 언제 나와요?" 하나에도 담당자를 찾고 시트를 열어 확인한 뒤에야 답할 수 있었다.

지금은 이 화면 하나에 다 들어온다. 수주 정보(고객사·수량·납기), 일정(투입일·완료예정), 공정 진행(배합→성형→충전→포장→검수) 단계별 수량이 한 행에 펼쳐진다. 메디팜 8매입 고보습은 배합 40,000개 완료, 성형 완료, 충전은 2,000개뿐이다. 어느 공정에서 멈췄는지 따로 보고받지 않아도 표만 보면 안다.

확정·접수·예측·준비 상태 배지로 우선순위도 바로 갈린다. 납기가 빨간색이면 긴급 건이다.

재고현황, 1,240 SKU에 창고 위치까지

ONE STOCK 재고현황

SKU 1,240개의 현재고를 품목코드로 검색하면 바로 나온다. 원자재·부자재·완제품으로 나뉘고 창고 위치 코드(A-01-02, Chem-01 등)까지 붙어 있어서 실물이 어디 있는지도 찾을 수 있다.

상단 요약 카드에는 품절 임박 12개, 과다 재고 45개 품목, 금일 입고 8건이 뜬다. 지금은 안전재고 미달 품목까지만 잡아 준다. 뷰티 원료는 유통기한을 따로 챙겨야 해서 유통기한 임박 제품을 추적하는 기능도 붙일 예정이다.

Teams 봇의 /재고 커맨드도 이 화면 데이터를 실시간으로 가져다 쓴다.

품질내역, 클레임부터 원료 입고까지 한 대장에

ONE STOCK 품질내역

품질 이슈가 생기면 이 화면에 기록한다. 클레임·공정불량·수입검사·보관이슈 유형으로 나누고 조치 상태(검토중·원인분석·반품완료·설비수리·전수검사·조치완료)와 담당자를 같이 붙인다. 평균 불량률은 0.4%, 수입 검사 완료율은 100%다.

지금은 별도 문서에 이슈를 입력하면 화면에 반영된다. 원스톡 안에서 바로 등록하고 처리 상태까지 관리하도록 합치는 중이다. 품질 이슈가 생산 일정·재고 데이터와 한 플랫폼에서 이어지면 "이 클레임 건 원자재 어느 로트에서 왔어?"까지 추적할 수 있다.


Teams 재고봇은 0.8초 만에 답한다

대시보드는 만들었지만 현장 직원들한테 "웹에 들어가서 확인하세요"는 현실성이 없었다. 이 사람들은 이미 하루 종일 Teams로 일한다.

그럼 Teams 안에서 바로 확인하게 하면 되지 않나.

Power Automate로 Teams 봇을 붙였다. /재고 AZ2505를 치면 0.8초 만에 창고별 재고가 올라온다.

Teams 재고봇 응답 화면

품목코드 하나만 넣으면 A완제품창고 8,668개, 쇼피 말레이시아 FBS 189개, 합계 8,857개가 나온다. 창고를 나눠 관리해도 전체 합계를 바로 본다.

전에는 "AZ2505 재고 몇 개야?" → 담당자 확인 → ERP 접속 → 검색 → Teams 답변 순이었다. 길면 10분이 걸렸고 담당자가 자리를 비우면 답이 올 때까지 기다렸다. 지금은 묻는 사람이 직접 0.8초 만에 확인한다.

재고 문의가 90% 줄어든 이유는 간단하다.
사람들이 이미 하루 종일 쓰는 Teams 안에 답을 넣었다.


결과

개발은 14일 만에 끝냈고 화장품 제조업체에 도입했다. 지금 60명이 전사에서 쓴다.

90%재고 문의 감소
0건요청 누락 (Teams→시스템)
0.8초Teams 봇 응답 시간
2,813BOM 데이터 자동 수집

재고 문의 90% 감소는 추정치가 아니다. 봇 도입 전 Teams 채팅에서 "재고"가 들어간 메시지 건수와 도입 뒤 /재고 커맨드 호출 횟수를 비교했다. 사람한테 묻던 걸 이제 봇한테 직접 묻는다.

지금 돌아가는 기능

  • Teams 재고봇: /재고 품목코드로 창고별 현황을 바로 조회
  • 종합현황 대시보드: 재고 자산, 생산 효율, 품절 임박·품질 이슈를 실시간으로 확인
  • 생산관리: 수주·납기·공정 단계를 한 화면에서 추적, 브랜드·B2B·생산팀이 같은 표를 보고 소통
  • 재고현황: 1,240 SKU 전체를 카테고리·창고 위치별로 조회
  • 품질내역: 이슈를 유형별로 나누고 조치 상태와 담당자를 관리

자주 받는 질문

이카운트 ERP를 클로드와 연결할 수 있나?

연결할 수 있다. 이카운트 OAPI를 MCP 서버로 감싸고 재고 조회, BOM 검색, 판매 현황 같은 기능을 도구로 등록하면 클로드가 그 도구를 부른다. 이카운트 쪽에서 준비할 건 회사코드, 사용자 ID, API 인증키다. MCP 서버는 .env를 알아서 읽지 않는다. 인증키는 MCP 설정의 환경변수로 넣는다.

이카운트 OAPI 주소는 어떻게 되나?

Zone 조회 주소는 모든 회사가 같은 oapi.ecount.com이다. 로그인과 API 호출에는 oapi 바로 뒤에 우리 회사 Zone을 붙인 주소를 쓴다. 테스트키는 sboapi로 시작한다. 테스트키로 oapi 주소를 부르면 Code 204 "테스트용 인증키입니다"가 뜬다.

이카운트 BOM API가 있나?

내가 연동할 때 공식 OAPI에는 BOM 조회가 없었다. 그래서 ERP 웹 화면이 쓰는 내부 요청을 찾아 BOM 2,813건을 가져왔다. 이 방법은 OAPI와 별도로 ERP 웹 로그인 세션이 필요하다. 비공식이라 이카운트가 화면을 바꾸면 깨질 수 있다.

테스트키와 실서버키는 뭐가 다른가?

테스트키는 테스트 서버용이라 실제 데이터가 안 나온다. 실서버키로는 테스트키 검증을 통과한 API만 열린다. 조회 API는 응답 코드 200을 받으면 검증이 끝나고, 저장 API는 테스트 서버에 실제로 한 건 이상 저장해야 검증된다. 테스트키는 기본 3회까지 발급받을 수 있다.

Code 205 "허용되지 않은 IP"가 뜨는데?

실서버키는 ERP에 등록한 IP에서만 로그인할 수 있다. IP는 최대 20개까지 등록할 수 있다. 로그인 응답은 HTTP 200으로 오는데 본문에 Code 205가 숨어 있다. 그래서 뒤따르는 호출이 "Please login"으로 실패하는 것처럼 보인다. Power Automate 같은 클라우드 자동화는 요청마다 IP가 바뀌니 관측되는 대역을 넉넉히 등록해 두는 게 낫다.


돌아보며

아무도 시키지 않았다. 생산팀 옆자리에서 매일 반복되는 비효율을 보다가 직접 원인을 찾고 설계해서 배포했다. 지금 60명이 매일 쓴다.

공식 API에 없다고 멈추지 않았고, 이카운트 웹 화면의 네트워크 탭을 이틀 동안 뜯어봤고, 세션 버그는 풀릴 때까지 붙잡았다.

돌아보면 한 일은 두 가지다.
문제를 문제로 알아보고
그걸 풀 도구를 찾아 끝까지 붙들고 갔다.

마케터가 ERP 위에 AI를 얹었다는 사실보다, 현장 병목을 옆자리에서 매일 봤다는 게 더 컸다.

여기서 쓴 MCP 연결 방식은 이카운트에만 쓰는 게 아니다. 증권사가 같은 규격으로 낸 서버에서 조회와 주문의 경계가 어떻게 갈리는지는 키움증권 MCP를 뜯어본 글에 있다.