검색 안 되는 API 문서에 지쳐서, 직접 MCP 서버를 만들어 npm에 올린 이야기

검색 안 되는 API 문서에 지쳐서, 직접 MCP 서버를 만들어 npm에 올린 이야기

검색 안 되는 API 문서에 지쳐서, 직접 MCP 서버를 만들어 npm에 올린 이야기

NHN커머스 샵바이(Shopby) API 문서를 자연어로 검색하는 MCP 서버를 만들어 사내 팀에 배포한 과정이다. npx -y shopby-mcp 한 줄이면 Claude에서 바로 "이 API에 이 필터 있어?"를 물어볼 수 있다.


발단 — 회의 때마다 반복되던 질문

지금 다니는 회사는 커머스 백엔드로 NHN커머스의 샵바이(Shopby) API를 쓴다. 외부 솔루션이라 우리가 서버를 들여다볼 수 없고, 공식 API 문서가 사실상 유일한 기준이다. 그런데 기획·개발 회의 때마다 이런 질문이 끝없이 나왔다.

  • "회원정보 API에 등급 정보 들어 있어요?"
  • "이 목록 API, 기간 필터 지원해요?"
  • "이거 샵바이로 구현 가능한 스펙이에요?"

질문 자체는 1초면 던진다. 그런데 답을 찾는 데는 매번 한참 걸렸다. 이유는 단순하다. 샵바이 API 문서에는 검색 기능이 없다.


진짜 고통은 목록과 실제 API가 매칭이 안 된다는 것!

검색이 없는 것보다 더 괴로운 건 따로 있었다. 카테고리(메뉴)와 실제 API의 위치가 직관과 어긋난다. 이름만 보고 "여기 있겠지" 하고 들어가면 없다. 실제로 내가 헤맸던 사례들이다.

  • GET /display/brands — 경로는 display로 시작하는데, 문서에선 product 카테고리에 들어 있다.
  • GET /profile/like-brands/member (회원이 좋아요한 브랜드 목록) — 당연히 member인 줄 알았는데 **product**에 있다.
  • GET /reviews/boards (게시판별 상품평) — 리뷰니까 review겠거니 했더니 **display**에 있다.

이러니 카테고리를 믿을 수가 없었다. 결국 카테고리를 하나하나 열어보고 페이지 안에서 Ctrl+F로 다시 찾는 식이었다. API 하나 확인하는 데 몇 분씩 날아갔고, 그때마다 회의 흐름이 끊겼다.

샵바이가 공식으로 제공하는 MCP나 검색 도구는 없었다. 그래서 직접 만들기로 했다.

브랜드 API는 product 카테고리 하위에 위치해 있다. 특이한 점은 URI가 /display로 시작하는데 별도로 display 카테고리가 존재함에도 해당 API가 display 하위가 아니라 product 하위로 분류되어 있다는 점이다.


분석 — 문서 사이트는 어디서 데이터를 가져오는가

무작정 만들 수는 없으니, 먼저 문서 사이트가 무슨 데이터를 어떻게 불러오는지부터 봤다. 문서 페이지를 열고 브라우저 개발자도구의 Network 탭을 뒤졌다.

다행히 쓸 만한 게 있었다. 사이트가 로드될 때 https://docs.shopby.co.kr/config.json을 불러오는데, 그 안에 카테고리별 스펙 파일 목록이 들어 있었다.

[ { "url": "../spec/admin-shop-public.yml", "name": "admin" }, { "url": "../spec/display-shop-public.yml", "name": "display" }, { "url": "../spec/member-shop-public.yml", "name": "member" }, { "url": "../spec/product-shop-public.yml", "name": "product" }, { "url": "../spec/order-shop-public.yml", "name": "order" } // ... (shop 12개) ]

../spec/*.yml. 이게 핵심이었다. 문서 사이트 본문은 JavaScript로 렌더링돼서 긁기 까다롭지만, 이 .yml 파일들은 OpenAPI 3.0 명세 원본이다. 검색이 안 되는 건 문서 사이트(UI)의 문제일 뿐, 데이터 자체는 이미 기계가 읽기 좋은 구조로 존재했다.

여기서 방향이 정해졌다. LLM을 새로 학습시킬 필요도, 크롤링할 필요도 없었다. 이 OpenAPI 명세를 파싱해서 검색 가능한 형태로 인덱싱하고, 그 위에 Claude가 호출할 인터페이스만 얹으면 된다. 그 인터페이스로 가장 잘 맞는 게 MCP(Model Context Protocol) 서버였다. 우리 팀은 이미 Claude Code를 쓰고 있어서, MCP로 만들면 평소 작업 흐름에 그대로 들어간다.


설계 — 세 덩어리로 쪼개기

구조는 세 층으로 단순하게 잡았다.

  1. 수집/최신화config.json을 읽어 거기 적힌 모든 .yml을 받아온다.
  2. 검색 엔진 — yml을 "엔드포인트 1개 = 레코드 1개"로 평탄화하고, 그 위에서 검색·상세조회를 한다.
  3. MCP 서버 — 검색 엔진을 Claude가 호출할 수 있는 툴로 노출한다.

검색 엔진을 MCP와 분리한 건 의도한 선택이다. 나중에 슬랙봇이든 웹 UI든 다른 데서도 같은 검색 로직을 재사용하려고 그렇게 나눴다. 이 선택은 뒤에서 실제로 도움이 됐다.

검색 엔진의 핵심

OpenAPI는 paths → 경로 → HTTP메서드 → 오퍼레이션으로 깊게 중첩돼 있다. 이걸 풀어서 각 API를 { 출처파일, 메서드, 경로, operationId, 태그, 요약, 파라미터(=필터), 응답필드 } 형태의 평평한 레코드로 만들었다. 검색은 이 평평한 배열 위에서 돈다.

신경 쓴 부분은 세 가지다.

첫째, 한국어로 바로 검색되게 했다. 샵바이 명세의 요약·설명이 한국어라, "장바구니"라고 한글로 쳐도 영어 operationId(get-cart)를 몰라도 매칭된다.

둘째, 응답·요청 바디의 필드명까지 색인했다. 그래야 "쿠폰 가능 여부 있어?" 같은 질문에 enableCoupons 필드를 가진 API가 잡힌다. $ref로 분리된 스키마는 재귀적으로 펼쳐서 색인했다.

셋째, 한↔영 동의어를 양방향으로 확장하고 일반 기능어는 점수에서 낮췄다. "환불↔refund", "장바구니↔cart" 같은 동의어를 넣되, "여부·가능·정보" 같은 흔한 기능어는 가중치를 강등했다. 이걸 안 했더니 "쿠폰 가능 여부"를 물었을 때 엉뚱한 게시판 API가 1위로 올라왔다. 처음엔 실제로 그랬고, 불용어 처리를 넣고 나서야 의도한 API가 1위가 됐다.

MCP 툴

서버는 Claude가 부를 수 있는 툴을 노출한다.

  • search_apis(query, category?) — 자연어로 API 검색
  • get_api_detail(operationId) — 특정 API의 필터·요청·응답 스키마 전체
  • list_apis(tag?) / index_stats() — 태그 단위 브라우징, 현황

흐름은 이렇다. "브랜드 리스트 API에 필터 뭐 있어?"라고 물으면, Claude가 search_apis("브랜드 리스트")로 후보를 좁히고, get_api_detail로 그 API의 필터 목록을 받아, 사람이 읽는 문장으로 답한다. 검색·정렬·구조 추출은 결정론적인 코드가 맡고, Claude는 무엇을 검색할지 판단하고 결과를 설명하는 일만 한다. 이렇게 나누니 정확하고 토큰도 덜 든다.


로컬 테스트 — 처음으로 찾아주는 순간

엔진과 MCP를 붙이고 Claude Code에 로컬로 등록해서 처음 물어봤다. 그동안 카테고리를 헤매던 일이 한 번에 끝났다.

처음 출발점이었던, 그 카테고리 불일치 API들도 이제 위치를 외울 필요가 없다. "회원이 좋아요한 브랜드 API 찾아줘"라고 하면 그게 product에 있든 member에 있든 알아서 찾아준다.


자동 최신화 — 샵바이가 스펙을 바꿔도 손 안 대게

여기서 욕심이 하나 생겼다. 샵바이 API는 월간으로, 또 그 사이사이 상시로 불규칙하게 바뀐다. 뭐가 바뀌었는지 알려면 업데이트 게시판을 따로 들어가서 확인해야 한다. 이것도 결국 "찾아봐야 아는" 일이라, 처음 겪던 불편이랑 똑같았다. 그래서 사람이 챙기지 않아도 도구가 알아서 따라가게 만들고 싶었다. 목표는 하나였다. MCP 서버가 새 세션에서 켜질 때마다 알아서 최신 스펙을 물어오는 것.

서버 응답을 직접 까보니 스펙 파일이 ETagLast-Modified 헤더를 준다. 그래서 조건부 요청(If-None-Match / If-Modified-Since)을 쓰기로 했다. 바뀐 파일만 새로 받고, 그대로인 파일은 서버가 304로 돌려주니 본문을 안 받고 건너뛴다. 24개를 다 점검해도 대부분 304라 시작이 느려지는 느낌은 없다.

여기서 한 가지 더. 파일 내용이 바뀌는 건 이걸로 해결되지만, 샵바이가 아예 새 모듈(새 yml 파일)을 추가하면 목록 자체가 달라진다. 그래서 파일 목록인 config.json도 같이 갱신하게 했다. 다음 세션에서 목록을 다시 읽으면서 새로 생긴 파일까지 알아서 발견해 받아오고, 없어진 모듈은 캐시에서 지운다. 결국 사용자는 아무것도 안 해도 늘 최신 스펙으로 검색하게 된다. 업데이트 게시판을 들여다볼 일도 없어졌다.


배포 — 한 줄이면 끝으로 만들기

내 문제는 풀렸지만, 이건 우리 팀만의 문제가 아니다. 샵바이를 쓰는 개발자라면 누구나 겪는다. 그래서 누구나 쉽게 설치하도록 npm 패키지로 출시했다.

claude mcp add shopby-docs -- npx -y shopby-mcp

npx로 실행되게 만든 게 핵심이다. 클론도, 수동 설치도 필요 없다. 이 한 줄을 등록해두면 Claude가 서버를 띄울 때 알아서 최신 패키지를 받아 실행한다. 스펙 yml은 패키지에 동봉하지 않고 첫 실행 때 원격에서 받아 캐시한다. 그래야 재배포 부담 없이 항상 최신을 보장한다.

출시 전에는 스스로 꽤 깐깐하게 검증했다. 오프라인 첫 실행 동작, 잘못된 인덱스 URL일 때의 경고, 검색 정밀도 회귀, 패키지에 실제로 포함되는 파일까지 점검하고, 네트워크 의존 없는 스모크 테스트와 GitHub Actions CI(Node 18/20/22)도 붙였다. 출시 시점 기준으로 shop·server 합쳐 24개 스펙, 약 700개 엔드포인트가 검색된다. (admin·internal 영역은 공개 호스트가 없어 제외했는데, 어차피 일반 개발자가 접근할 수 있는 범위가 아니다.)


확산 — 그리고 PM을 위한 우회

팀에 공유한 뒤, 비개발 직군인 PM·기획자도 쓰게 하고 싶었다. 회의에서 "이거 구현 가능해요?"를 가장 많이 묻는 사람들이 그들이다.

처음엔 슬랙봇을 떠올렸다. 채널에서 @샵바이봇 회원정보에 등급 있어?라고 하면 답하는 그림이었다. 그런데 슬랙봇이 자연어로 답하려면 결국 LLM 호출이 필요하고, 그건 Anthropic API 별도 결제로 이어진다. 검색으로 컨텍스트를 좁히니 비용 자체는 크지 않지만, 지금 단계에서 굳이 결제 라인을 만들 이유는 없었다.

마침 PM분들도 각자 Claude 구독 계정이 있었다. 그래서 슬랙봇 대신, 같은 shopby-mcp 패키지를 각자의 Claude Desktop 앱에 설치해드리는 방향으로 틀었다. 추가 결제 없이, 각자 자기 구독 안에서 "상품 데이터에 쿠폰 가능 여부 있어?"를 자연어로 물어본다. 개발자용으로 만든 도구 하나가 인터페이스만 바꿔 비개발 직군까지 커버한 셈이다.

PM이 Claude Desktop 앱에서 자연어로 질문하고 답을 받는 화면


배운 것

불편함을 끝까지 들여다보니 구조가 보였다. "검색이 없다"는 표면 불편 아래에는 "데이터는 OpenAPI로 잘 구조화돼 있다"는 사실이 있었고, 그게 해법의 방향을 정했다. 크롤링이나 LLM 학습 같은 무거운 길로 안 가게 해준 건 Network 탭을 들여다본 30분이었다.

결정론적인 부분과 LLM을 분담시킨 것도 주효했다. 검색과 구조 추출은 코드가, 판단과 설명은 LLM이 맡는다. 이렇게 나누면 정확하고 토큰도 적게 든다.

재사용을 염두에 두고 분리한 게 확산으로 이어졌다. 검색 엔진을 MCP에서 떼어놓은 덕에 개발자(Claude Code), PM(Claude Desktop), 그리고 검토했던 슬랙봇까지 같은 코어로 커버할 수 있었다.

나만 쓰는 스크립트와 남도 쓰는 패키지는 다르다는 것도 체감했다. 오프라인, 잘못된 설정, 네트워크 차단 같은 엣지를 다루고 테스트·CI·문서를 붙이는 과정에서 배운 게 많았다.


설치 방법

개발자(Claude Code):

claude mcp add shopby-docs --scope user -- npx -y shopby-mcp

PM·기획자(Claude Desktop): 설정 → Developer → Edit Config 에 추가한 뒤 앱을 재시작한다.

{ "mcpServers": { "shopby-docs": { "command": "npx", "args": ["-y", "shopby-mcp"] } } }

둘 다 Node.js 18 이상이 필요하고, 첫 실행 때 스펙을 자동으로 받아온다.


이제 회의에서 "이 API에 이 필터 있어?"가 나오면 페이지를 헤매는 대신 Claude에게 묻는다. 검색이 없던 문서가 검색되는 문서가 됐다.

JP
이중표Frontend Engineer

3년차 프론트엔드 개발자. Next.js, React, TypeScript 기반 웹 애플리케이션 개발 전문. 대규모 트래픽 환경에서 SSR·ISR 렌더링 전략 설계 경험.

이력서 보기