Skip to main content
Glama
ksbsjh74-code

mart-compare-mcp

README.md
# mart-compare-mcp

마트에서 제품 A vs B(vs N개)를 비교/추천해주는 MCP 서버. 스펙(원산지/인증/영양정보)은
큐레이션 DB, 가격/리뷰는 실시간 조회로 붙일 수 있게 설계한 **하이브리드** 구조 - 단, 가격/리뷰
실시간 조회는 2026-08-25 기준 보류 상태다 (§3 참고).

지금은 실제로 빌드·실행·테스트·배포까지 확인된 상태다. 카테고리 5개(우유/생수/통조림햄/두부/참치캔)
샘플 데이터가 들어 있고, 3개 툴(`list_categories`/`search_products`/`compare_products`) 전부
실제로 curl로 호출해서 정상 동작 확인했다. Render에 배포돼 있고 엔드포인트는
`https://mart-compare-mcp.onrender.com/mcp` (무료 플랜이라 트래픽 없으면 슬립됨). (2026-08-25 갱신)

## 1. 로컬 실행

```bash
npm install
npm run build   # tsc 컴파일 + data/products/*.json을 dist로 복사
npm start        # http://localhost:3000/mcp 에서 대기
```

개발 중엔 `npm run dev` (tsx watch, 파일 저장 시 자동 재시작).

헬스체크: `curl http://localhost:3000/health` → `{"status":"ok"}`

## 2. 구조

```
src/
  index.ts              # Express + Streamable HTTP transport 진입점
  server.ts              # McpServer 인스턴스 생성 + 툴 등록
  tools/compareProducts.ts   # list_categories / search_products / compare_products 3개 툴
  lib/loadProducts.ts    # data/products/*.json 로더 (자체 DB)
  lib/liveData.ts        # 가격/리뷰 실시간 조회 - 현재 항상 null 반환하는 스텁 (§3 참고)
  data/schema.ts          # 제품 스펙 타입 정의
  data/products/*.json    # 카테고리별 큐레이션 데이터 (milk, water, canned-ham, tofu, tuna-can)
```

## 3. 지금 상태에서 "가짜"/"미완성"인 부분 (중요)

- **가격/리뷰는 실제로 안 붙어 있고, 당장 붙일 방법이 없다.** 원래 네이버쇼핑 검색 API로
  KR 가격을 채우려 했는데, 이 API는 **2026-07-31부로 완전 종료**됐고 공식 대체 API가 없다
  (네이버 개발자센터 "사용 API" 목록에서 "검색" 항목 자체가 사라진 걸로 실제 확인함. 출처:
  [waffleboard.io](https://waffleboard.io/blog/naver-search-api-hub-migration-guide)).
  대안으로 쿠팡파트너스 검색 API를 검토했는데, 시간당 호출 10회 제한(3연속 403이면 계정
  영구정지 위험) + 파트너스 가입 심사 필요 + 약관상 용도가 제휴 링크 유도라 순수 가격비교에
  써도 되는지 불명확, 이 세 가지 때문에 사람이 직접 판단해서 가입해야 해서 일단 보류했다.
  11번가 OpenAPI도 찾아봤지만 셀러 전용 문서만 확인됨. 楽天市場(JP) 연동은 애초에 코드도 없음.
  자세한 내용/재검토 방법은 `lib/liveData.ts` 상단 주석 참고.
- **참치캔·두부 데이터에서 포화지방산/트랜스지방산 필드는 의도적으로 뺐다.** 식약처 API 원본 값이
  같은 제품 총 지방 함량보다 3~6배 커서(예: 지방 15g인데 포화지방산 50g) 필드 매핑 오류 또는
  원본 데이터 오류로 의심됨. 두부 카테고리 4개 제품 전부와 참치캔 4개 제품 전부에서 동일하게
  나타난 문제라 우연이 아니라 이 API 필드(AMT_NUM23/24) 자체의 구조적 문제로 보임 - 반면 우유/
  생수/통조림햄에서는 이 문제가 없었음. 공식 문서로 AMT_NUM23/24 정의를 재확인하기 전까지 이
  두 필드는 절대 쓰지 말 것. (PlayMCP 테스트 중 발견: 2026-08-25)
- **egg(계란) 카테고리는 아직 없다.** `data/staging/egg.draft.json`은 식약처 API에 "계란"으로
  검색해서 나온 20건 전부 계란쿠키/계란과자/구운계란 등 가공식품이었고, 마트에서 파는 생란(달걀
  한 판) 제품이 하나도 없어서 검수 결과 전부 버렸다. 다시 수집하려면 검색어를 "달걀"로 바꾸거나
  `FOOD_CAT1_NM`(식품대분류) 파라미터로 알가공품류를 좁혀서 재시도할 것.
- **데이터 파일에 `needsVerification: true`가 붙은 항목은 출처 검증이 안 끝난 예시 데이터.**
  compare_products 응답에도 이 사실을 note로 같이 내려주니, 이 값을 사실처럼 답변에 쓰면 안 됨.
- `certifications` 필드는 검색으로 실제 확인한 것만 넣었다 (예: 제주삼다수 먹는물연구소 ERA 인증).
  경쟁 제품에 대해 "부적합/불합격" 같은 부정적 사실은 검증 안 된 채로는 절대 넣지 않았다 —
  이런 정보는 명예훼손 소지가 있으므로, 넣으려면 반드시 식품안전나라(식약처) 공식 회수·행정처분
  정보 같은 1차 공식 소스로만 채울 것.

## 4. 카테고리/제품 추가하는 법

**수동으로 추가:**
1. `src/data/products/` 에 카테고리별 json 파일 추가 (또는 기존 파일에 항목 추가)
2. `ProductSpec` 스키마(`src/data/schema.ts`)를 따를 것 — 특히 `sources`를 반드시 채우고,
   출처를 못 찾은 값은 넣지 말고 `needsVerification: true` + notes로 남길 것
3. `npm run build` 다시 실행 (json이 dist로 복사돼야 반영됨)

**자동 수집 (1층 - 식약처 API):**

한국 제품의 존재/영양정보는 식약처 식품영양성분DB Open API로 대량 수집 가능.
**주의**: 이 API는 `foodsafetykorea.go.kr` 사이트 자체 검색이 아니라 **공공데이터포털(data.go.kr)**을
통해 신청해야 함 — foodsafetykorea.go.kr에서 검색하면 다른(링크형/L타입) 서비스가 나와서 신청이 막힘.

```bash
# 1. https://www.data.go.kr/data/15127578/openapi.do 접속
#    → "활용신청" 버튼 클릭 → 자동승인(개발계정, 트래픽 10,000/일)
# 2. 승인 후 마이페이지에서 서비스키(인증키) 확인
# 3. .env.example을 .env로 복사하고 FOODSAFETY_API_KEY 채우기
cp .env.example .env

# 4. 카테고리별로 수집 (검색어, 우리 카테고리id) - .env가 자동으로 읽혀서 이렇게만 하면 됨
npm run ingest -- 우유 milk
```

결과는 `src/data/products/`가 아니라 **`src/data/staging/milk.draft.json`에 초안으로만 저장**돼.
자동으로 반영 안 되니까, 이 파일을 열어서:
- 진짜 마트에서 파는 브랜드 제품만 골라내고 (연구용 샘플/조리식품 등 노이즈 많음)
- 브랜드명이 비어있는 항목은 채우거나 버리고
- 인증/차별점 정보(2층)는 이 스크립트가 못 채우니 따로 검색해서 보강하고

정리된 항목만 `src/data/products/milk.json`으로 옮겨 담을 것. 이 스크립트는 영양정보 초안을
빠르게 만들어주는 용도지, 검수를 대신해주지 않음.

> **검증 관련 투명하게 밝힘**: 이 API 스펙(Base URL `apis.data.go.kr/1471000/FoodNtrCpntDbInfo02`,
> 요청 파라미터, `AMT_NUM1~157` 필드명)은 실제로 data.go.kr 페이지에 브라우저로 접속해서 API
> 명세(Swagger) 화면을 직접 읽고 확인한 것. AMT_NUM 코드가 각각 무슨 영양소인지는 문서(엑셀)를
> 브라우저로 못 열어봐서, 같은 API를 이미 구현해둔 오픈소스(ISC 라이선스) 프로젝트
> [k-mfds-fooddb-mcp-server](https://github.com/slicequeue/k-mfds-fooddb-mcp-server)의
> 매핑 코드로 교차 확인했음. 실제 API 호출 자체는 이 컨테이너 네트워크가 `apis.data.go.kr`을
> 막고 있어서 (`host_not_allowed`) 여기선 못 했고, 대신 실제 응답 스키마를 그대로 흉내낸 mock으로
> 요청 조립→응답 파싱→매핑→파일 저장 전체 흐름을 검증함. **진짜 키로 첫 호출은 네가 직접 해봐야 함.**

## 5. 배포 (Render) - 완료

GitHub repo([ksbsjh74-code/mart-compare-mcp](https://github.com/ksbsjh74-code/mart-compare-mcp))
연동해서 Render Free 플랜으로 배포 완료.

- 헬스체크: `https://mart-compare-mcp.onrender.com/health`
- PlayMCP 등록용 엔드포인트: `https://mart-compare-mcp.onrender.com/mcp`
- 환경변수는 Render 대시보드 Environment 탭에서 직접 관리 (`FOODSAFETY_API_KEY`만 등록해둠 -
  ingest 스크립트는 로컬에서 돌리는 거라 사실 서버 런타임엔 필요 없음, 나중에 정리해도 됨)
- `main` 브랜치에 push하면 Render가 자동으로 재배포함
- 무료 플랜은 트래픽 없으면 슬립 상태로 들어가고 첫 요청에 콜드스타트(수십 초)가 있을 수 있음 —
  실사용 트래픽이 생기면 유료 플랜(Starter, $7/월) 전환 고려

**첫 배포 시 헬스체크가 계속 타임아웃났던 버그(수정 완료, 커밋 `d03db8c`)**: `src/index.ts`에서
`@modelcontextprotocol/sdk`의 `createMcpExpressApp()`을 옵션 없이 호출하면 기본값이
`host: '127.0.0.1'`인데, 이 경우 SDK가 DNS 리바인딩 방지 미들웨어를 자동으로 걸어서 `Host` 헤더가
`localhost`/`127.0.0.1`/`[::1]`이 아닌 모든 요청을 403으로 거부한다. Render 헬스체크와 실제
클라이언트 요청은 `Host: mart-compare-mcp.onrender.com`으로 들어오기 때문에 `/health`까지 같이
막혀서, 앱은 로그상 정상적으로 포트에 바인딩됐는데도 배포가 계속 헬스체크 타임아웃으로 실패했다.
`createMcpExpressApp({ host: "0.0.0.0" })`로 명시해서 해결함 — 공개 배포 환경에서 이 SDK를 쓸 때
반드시 넣어야 하는 옵션이니, 나중에 다른 프로젝트에서도 같은 헬퍼를 쓴다면 주의할 것.

## 6. PlayMCP 등록 절차 (2026-08 기준 확인한 내용)

1. §5에서 배포한 서버의 엔드포인트가 인터넷에서 접근 가능해야 함 (`/mcp` 경로가 POST를 받아야
   함). PlayMCP는 원격(remote) MCP 서버 등록 방식이라 로컬 stdio 서버는 그대로는 못 씀.
2. https://playmcp.kakao.com 에 카카오 계정으로 로그인
3. "MCP 서버 등록"에서 배포한 서버의 엔드포인트 URL(`https://.../mcp`) 입력
4. 처음엔 비공개(임시 등록) 상태로 본인 계정에서만 테스트 가능
5. 다른 사용자에게 공개하려면 카카오 파트너 검증 절차를 거쳐야 함 (이 부분 세부 요건은
   PlayMCP 사이트 내 "이용 가이드"에서 별도 확인 필요 — 계속 업데이트되는 영역이라
   등록 직전에 다시 확인할 것)

## 7. 다음 단계 제안

- [x] 카테고리 확장 (두부/참치캔 추가, 계란은 데이터 품질 문제로 보류)
- [x] Dockerfile/render.yaml 작성
- [x] GitHub repo 생성 + Render 배포 완료
- [x] 가격 실시간 조회 API 조사 (네이버쇼핑 종료 확인, 쿠팡파트너스/11번가 검토 후 보류)
- [x] 배포 후 헬스체크 타임아웃 버그 수정 + `/mcp` 실제 호출 검증 완료 (2026-08-25, 커밋 `d03db8c`)
- [x] PlayMCP 등록 + 실제 채팅으로 3개 툴(list_categories/search_products/compare_products)
  전부 호출 테스트 완료 (2026-08-25, 심사 요청 제출함 - 심사 결과 대기 중). 테스트 중
  포화지방산/트랜스지방산 데이터 품질 문제가 참치캔뿐 아니라 두부에도 있는 걸 추가로 확인함
  (위 §3에 반영)
- [ ] egg 카테고리 재수집 (검색어 "달걀" 또는 FOOD_CAT1_NM 필터로 재시도)
- [ ] (선택) 가격 실시간 조회 재도전 - 쿠팡파트너스 가입 심사 받고 시간당 10회 제한 감안한
  캐싱 구조로 붙이거나, 11번가 공식 문서를 직접 열어봐서 일반 상품검색 API 존재 여부 확인