Skip to main content
Glama

Shopify MCP 데모

ChatGPT 또는 Claude 내부에서 라이브 Shopify 스토어를 쇼핑하고 Cashfree로 결제하세요 — 대화를 떠나지 않고 카탈로그, 장바구니, OTP 로그인, 저장된 주소 및 결제가 가능합니다.

"show me shirts from the store"
      ↓  SearchProducts
  product grid  ⇄  product detail
        └────────┬───────┘
                 ↓
  cart  →  phone  →  OTP  →  address  →  payment
                                            ↓
                            Cashfree  →  order summary

모든 결제 경로는 동일한 화면에서 종료됩니다: 구매한 항목, 수량 및 가격, 주문 ID 및 상태가 표시됩니다. Cashfree는 자금이 이동했음을 확인하지만 Shopify 장바구니를 볼 수 없으므로 그 안에 무엇이 있었는지 알 수 없습니다.

결제 후 다시 검색하면 위젯이 새로운 쇼핑 세션을 시작합니다 — 새로운 장바구니, 남은 영수증 없음. 한 번의 대화에서 두 번 구매하는 것도 작동합니다.

작동 방식

서버는 세 가지 역할을 동시에 수행합니다:

  • AI 호스트에 대한 MCP 서버로, 하나의 모델 대면 도구(SearchProducts)와 위젯 리소스를 노출합니다

  • Shopify에 대한 MCP 클라이언트로, https://{SHOP_DOMAIN}/api/ucp/mcp에 JSON-RPC로 통신합니다 — 인증 없음, 샵 도메인이 전체 구성입니다

  • Cashfree에 대한 REST 클라이언트로, 주문 생성, OTP 로그인, 저장된 주소 및 주문 상태를 처리합니다

React 위젯이 전체 여정을 관리합니다. 제품 검색만 모델에 도달합니다; 그 이후의 모든 것은 위젯-서버 간 통신으로, 흐름을 결정론적으로 유지합니다.

브라우징은 두 화면으로 구성됩니다. 그리드는 각 제품에 대해 가격 범위와 옵션 수를 표시하는 카드를 보여줍니다; 하나를 탭하면 설명, 변형 선택기 및 장바구니 추가가 있는 상세 화면이 열립니다. 단일 변형이 있는 제품은 카드에서 바로 추가할 수 있으며, 이미 장바구니에 있는 제품은 수량 스테퍼가 표시됩니다 — 따라서 일반적인 경우는 한 번의 탭으로 해결되고 실제 선택이 필요한 경우에만 화면이 필요합니다. 두 화면 모두 이미 장바구니에 있는 항목에 배지를 표시하며, 두 카운트는 동일한 함수에서 제공됩니다.

Related MCP server: Shopify Agentic MCP Gateway

설정

npm install
cp .env.example .env      # set SHOP_DOMAIN and the Cashfree keys
npm run build
npm start

노출시키고(ngrok http 8787), .env에서 SERVER_URL을 공개 오리진으로 설정하고, 재시작한 다음, 호스트에서 커넥터로 <public-origin>/mcp를 추가하세요.

재시작만으로 충분합니다 — 재빌드 불필요. 오리진은 부팅 시 읽혀 리소스가 제공될 때마다 위젯 HTML에 주입됩니다.

변수

목적

SHOP_DOMAIN

스토어. 이 줄을 편집하고 재시작하여 스토어를 전환합니다.

UCP_AGENT_PROFILE

모든 UCP 호출에 필요합니다. Shopify의 공개 예제 프로필은 데모에 적합합니다.

CASHFREE_ENV

sandbox(기본값) 또는 production.

CASHFREE_CLIENT_ID / CASHFREE_CLIENT_SECRET

대시보드 → 개발자 → API 키.

CASHFREE_RETURN_URL

Cashfree가 구매자를 반환하는 위치. 기본값은 안정적인 호스팅 페이지입니다.

SERVER_URL

터널링 시 공개 오리진.

PORT

기본값은 8787입니다.

PAYMENT_ANNOTATIONS

honest(기본값) 또는 readonly. .env에서 전환하고 재시작하여 두 디스패치 경로를 테스트합니다. readonly는 결제 도구가 읽기 전용이라고 주장하도록 하여 호스트가 이를 디스패치하도록 합니다 — 진단 전용, 위 참조. 셸 변수가 파일을 재정의합니다. --env-file은 이미 설정된 환경 변수를 재정의하지 않기 때문입니다.

실행 시 예상되는 사항

저장된 카드를 제외한 결제는 작동합니다. 호스트는 MCP 주석에 따라 결제 도구를 게이트합니다. cashfree-here는 정직한 주석을 제공합니다 — 카드를 청구하는 도구에 대해 { readOnlyHint: false, destructiveHint: true } — 그리고 이에 대해 호스트는 디스패치를 거부합니다: 모델이 의도를 형성하고, 호스트가 도구의 위젯 템플릿을 프리페치하며, tools/call은 절대 도착하지 않습니다.

PAYMENT_ANNOTATIONS=readonly로 설정하면 이를 { readOnlyHint: true, destructiveHint: false }로 재정의하고 다섯 개 도구 중 네 개가 디스패치됩니다: UPI, 인터넷뱅킹, 호스티드 체크아웃, 새 카드 — 그리고 핸드오프가 도착하면 저장된 카드도 디스패치됩니다.

이 플래그는 측정 도구이지 해결책이 아닙니다. 돈을 이동시키는 도구가 아무것도 하지 않는다고 주장하게 만드는데, 이는 이를 잡기 위해 존재하는 정확한 제어에 대한 거짓말입니다. 기본적으로 꺼져 있으며, 첫 사용 시 경고를 출력하고, 출시되어서는 안 됩니다.

도구가 차단되면 위젯이 이를 알리고 Cashfree 링크를 제공하며, 이는 작동합니다. 탭에서 결제하고 돌아오면 위젯이 주문을 확인합니다.

핸드오프는 늦을 뿐 손실되지 않습니다 — 그리고 "차단됨"이 잘못될 수 있습니다. 이 파일은 이전에 위젯-모델 핸드오프가 약 절반의 시간 동안 실패한다고 말했습니다. 캡처된 한 세션은 그렇지 않음을 보여줍니다: CheckoutTool이 위젯의 두 번 시도(2 × 4초) 후 차단되었다고 선언되었고, 구매자가 Cashfree 링크를 사용했으며, tools/call이 어쨌든 도착했습니다 — 클릭 후 약 2-4초, 따라서 종단 간 약 12-20초 정도입니다. 서버는 37ms 만에 처리했습니다. 그 지연의 모든 밀리초는 업스트림에 있었습니다.

sendFollowUpMessage는 호스트에게 도구를 실행하도록 요청하지 않습니다. 사용자 턴을 게시하고 해당 메시지가 전달되면 해결되므로, 위젯의 확인 창은 큐에 넣는 시간에 시작하여 호스트 인프라에서 전체 모델 추론 턴을 측정합니다 — 4초 타임아웃은 이에 대해 보정된 적이 없습니다.

결과는 느린 화면보다 더 심각합니다: 구매자가 외부 링크에서 결제하는 동안 동일한 payment_session_id에 대한 Cashfree 위젯이 뒤에서 렌더링되었습니다. 하나의 주문에 두 개의 활성 결제 표면. DISPATCH_ATTEMPTS = 2는 후속 메시지를 두 번 보내므로 두 개의 위젯도 가능합니다.

"절반 시간 동안 실패"라는 판독은 우리 자체 버그였습니다. MCP Apps 전송이 제공하는 하나의 postMessage 채널에서 두 개의 App 인스턴스가 열리고 있었습니다: useMcpApp이 렌더링용으로 하나를 빌드하고 연결했고, getClientPlatform()이 결제 핸드오프용으로 다른 하나를 빌드하고 연결했습니다. 핸드셰이크가 경쟁했고, 진 사람이 이후 모든 것에 **"연결되지 않음"**이라고 응답했습니다 — 구매자가 결제 방법을 선택했고 tools/call이 서버에 도달하지 못했습니다. 동전 던지기 핸드오프는 양방향 경쟁의 모습이지 불안정한 호스트의 모습이 아닙니다. 이제 훅은 공유 클라이언트를 구독하고, connect()는 멱등적이며, 마운트 해제 시 React 트리보다 오래 사는 싱글톤에서 더 이상 close()를 호출하지 않습니다.

이 수정 이후 모든 디스패치가 첫 번째 시도에 성공했습니다. attemptsFor()는 이미 MCP Apps 호스트에서 1을 반환하므로, 재시도는 이 경쟁이 없었던 LegacyOpenAiClient를 사용하는 ChatGPT에만 적용됩니다 — 제거를 정당화하는 측정값이 없으므로 유지됩니다.

₹1,00,000 이상의 UPI는 실패합니다 "결제 방법이 이 주문에 적합하지 않습니다" 오류와 함께 — UPI의 거래당 한도, ₹99,600(성공) / ₹100,800(실패)에서 이분법으로 확인됨. 인터넷뱅킹과 호스티드 체크아웃은 더 높은 한도가 있습니다. 장바구니에는 상한이 없으므로 +를 몇 번 탭하면 경고 없이 이를 넘어갈 수 있습니다.

호스트 창을 다시 로드하는 것은 두 호스트 모두에서 안전합니다. 장바구니, 체크아웃 단계, 저장된 주소 및 결제 화면이 모두 돌아옵니다; 장바구니 본문은 다시 가져오지 않고 유지된 상태에서 복원되므로, Claude에서 재로드는 Shopify에 전혀 호출하지 않습니다. ChatGPT는 도구 결과를 재전달하지 않으므로, 제품 그리드를 재구축하기 위해 한 번의 search_catalog 호출과 그 이상은 필요하지 않습니다. 여러 단계에서 재로드가 포함된 네 가지 흐름에서 측정: 18개의 업스트림 호출, 그중 반복은 없음.

빌드 ID는 결제 화면에 출력됩니다. 호스트는 위젯 인스턴스를 캐시하며, 캐시된 인스턴스는 현재 인스턴스와 구별할 수 없습니다 — 여러 차례의 디버깅이 이미 삭제된 코드에 사용되었습니다. 빌드 ID가 실행 중인 서버와 일치하지 않으면 오래된 위젯을 보고 있는 것입니다.

재빌드는 브라우저에 도달하지 않으며, 새 채팅도 마찬가지입니다. 위젯 URI는 빌드 ID를 전달하므로 모든 빌드는 고유한 리소스이며, 그래도 충분하지 않습니다: Claude는 재빌드 새 대화에서 캐시된 위젯을 제공했으며, 로그에 resources/read가 전혀 없었습니다. 두 차례의 디버깅이 실행되지 않는 계측에 사용되었습니다. 다시 읽기를 강제하는 유일한 신뢰할 수 있는 방법은 커넥터를 연결 해제하고 다시 연결하는 것입니다. 로그에서 POST /mcp (resources/read ui://widget/shopify-store-<build>.html)를 확인한 후에야 보이는 것을 신뢰하세요.

재연결해도 전체 이야기는 아닙니다: 호스트의 캐시된 도구 메타데이터는 이전 빌드의 URI를 참조할 수 있으므로, 새 대화라도 이 서버가 더 이상 가지고 있지 않은 빌드 ID를 요청할 수 있습니다. 이 서버는 그중 어떤 것에 대해서도 응답합니다 — 아래 "위젯 URI 버전 관리로 폐기" 참조 — 하지만 해당 resources/read 줄의 ID는 반드시 실행 중인 서버의 것이 아닙니다. 결제 화면의 window.__BUILD__가 어떤 번들이 실행 중인지 알려줍니다.

알려진 제한 사항

  • 카드 입력이 Claude에서 렌더링되지 않으며, 상위 프로젝트에서 수정되지 않을 것입니다. Cashfree Elements는 PCI 필드를 중첩된 교차 출처 iframe으로 마운트합니다. Claude는 frame-src 'self' blob: data:를 적용하고 UI 리소스가 선언한 frameDomains를 무시하므로, 필드가 빈 클릭 불가능한 상자로 로드됩니다. 이는 정책이지 전송 중 버그가 아닙니다: Anthropic 엔지니어가 2026-04-09에 claude-ai-mcp#40 에서 중첩 iframe은 보안상의 이유로 허용되지 않는다고 밝혔으며, 이후 "영구적인가요, 일시적인가요?"라는 두 번의 질문은 답변되지 않았습니다. connectDomainsresourceDomains 4월에 수정되어 작동합니다 — 프레이밍만 차단되므로, 위젯에서 이 서버로의 fetch는 정상입니다.

    Stripe도 같은 벽에 부딪혔습니다: MCP Apps 문서는 카드 필드를 임베드하는 대신 호스팅된 Checkout 페이지를 app.openLink()로 엽니다. 그 방식은 여기 CheckoutTool과 동일한 형태이며, 현재 작동 중이고 Claude에서 카드에 권장되는 경로입니다.

    대화 내 카드 입력을 유지하는 것은 가능합니다 — iframe 없이 일반 <input> 필드를 이 서버에 직접 POST하는 방식이며, 이는 cashfree-here가 커밋 55da139에서 Elements로 대체되기 전에 제공했던 방식입니다. 원시 PAN을 서버(SAQ-D 영역)를 통과시키며, 3DS는 여전히 외부로 리디렉션되므로, 폼을 얻는 대신 흐름을 얻지 못합니다. 구축되지 않음; 기술적 결정이 아닌 제품 결정입니다.

  • 저장된 카드 결제가 Cashfree 내에서 실패합니다. CardPaymentTool은 저장된 카드를 올바르게 전달하고 나열하지만, 하나로 결제하면 /pg/orders/sessions/js에서 HTTP 500 {"message":"Internal Server Error"}를 반환합니다. 하나의 주문에서 격리됨: UPI와 넷뱅킹은 동일한 payment_session_id와 헤더에 대해 200을 반환합니다; payment_method.card.instrument_id 분기만 500을 반환하며, CVV 유무에 관계없이 발생합니다. 일반 500은 Cashfree 측 오류입니다 — 해당 서비스의 유효성 검사 오류는 특정 메시지와 함께 400을 반환합니다. Cashfree의 지원이 필요합니다.

  • UPI가 ₹1,00,000 한도 이상에서 제공되며, 선택기에서 비활성화되지 않고 Cashfree에서 실패합니다.

  • cashfree-here가 제자리에서 패치되었습니다. 두 가지 수정 사항이 이 리포지토리가 아닌 형제 체크아웃에 있습니다: useReconciliation.start()가 이제 이전 폴링 타이머를 지우고, 결제 성공 알림이 주문당 한 번만 발생합니다. 이 수정 사항이 없으면 유료 주문이 "Payment completed successfully"를 채팅에 몇 초마다 영원히 게시했습니다.

  • 선택된 주소가 주문에 바인딩되지 않습니다. 구매자가 하나를 선택하지만 Cashfree에 전달되지 않습니다. 해결되지 않음; OCC 팀의 답변이 필요합니다.

  • Shopify 주문이 생성되지 않습니다. 주문은 Cashfree에만 존재합니다. 생성하려면 Shopify Admin API 자격 증명이 필요하며, 이 프로젝트는 의도적으로 이를 피합니다.

  • 세션 저장소는 인메모리입니다. 서버 재시작은 진행 중인 체크아웃을 잃습니다.

  • 오퍼와 쿠폰은 연기되었습니다. 두 API 모두 docs/cashfree-occ-api.md에 입증되고 문서화되어 있습니다; UI만 누락되었습니다.

  • INR만 지원, 데모 스토어와 일치.

이 프로젝트를 확장하는 사람을 위한 참고 사항

확립하는 데 상당한 시간이 소요된 발견 사항. 각각 측정된 것이지 추정된 것이 아닙니다.

Shopify

  • /api/ucp/mcp만 대상으로 하세요. 이전 /api/mcp2026-08-31 이후로 사용 중단되며, 모든 응답에 그렇게 명시됩니다.

  • 게시된 문서는 세 곳에서 잘못되었습니다: 카탈로그/카트 엔드포인트 분할을 반대로 하고, UCP가 line_items[].item.id를 요구할 때 카트 라인을 merchandise_id로 문서화합니다. 여기의 타입은 문서가 아닌 src/lib/ucp/__fixtures__/의 캡처된 페이로드에서 가져옵니다.

  • update_cart는 선언적입니다 — 매번 완전한 원하는 라인 세트를 보내며; 제거는 라인을 생략하여 표현됩니다.

  • 금액은 카트 수준에서 한 번 보유된 통화와 함께 최소 단위입니다. formatMoneyIntl에서 소수점 자릿수를 가져오므로, 소수점이 없는 통화는 나누어지지 않습니다.

  • 비밀번호로 보호된 스토어는 여전히 카탈로그, 카트 및 체크아웃 링크를 제공합니다. 스토어프론트를 탐색할 때만 비밀번호 페이지가 나타납니다.

  • search_catalog는 제품 설명을 HTML로 반환하며, 변형은 축을 options: [{ name, label }]로 전달합니다. 설명은 React에 도달하기 전에 normalise.ts에서 일반 텍스트로 축소됩니다: OTP를 수집하는 동일한 화면에서 렌더링되는 스토어 제어 콘텐츠이며, 그 안의 어떤 서식도 인젝션 표면이 될 가치가 없습니다.

  • 단일 변형 제품은 여전히 하나의 옵션을 가집니다 — Shopify의 { name: "Title", label: "Default Title" } 플레이스홀더. 이를 렌더링하면 선택할 사항이 없는 제품 아래에 "1 titles"가 표시됩니다.

Cashfree

  • x-chxs-id는 Create Order의 payment_session_id 입니다. 이는 로그인 전에 주문이 존재하도록 강제합니다. 조작된 값은 payment_session_id_invalid를 반환합니다.

  • OCC 호출에는 정확히 세 개의 헤더가 필요합니다. 캡처된 요청의 브라우저 핑거프린팅(기기 ID, Forter 토큰, 쿠키, 출처) 중 어느 것도 강제되지 않습니다.

  • 주소는 결합된 주소 라인이 10–185자여야 합니다. 더 짧으면 UI에 힌트 없이 400 오류가 발생하므로 확인하지 않으면 알 수 없습니다.

  • 주소 생성 응답은 { shipping_address, billing_address }이며, 목록이 아닙니다. 목록으로 파싱하면 성공 시 빈 배열을 반환합니다.

  • /api/orders/:id는 Cashfree의 원시 본문을 프록시합니다. cashfree-here의 리컨실리에이션이 그 형태를 파싱하기 때문입니다.

위젯

  • 카드는 제품이고, 카트 라인은 변형이며, 서로 일치하지 않습니다. 하나의 티셔츠의 세 가지 색상을 하나의 카드로 압축하는 것은 브라우징에는 적합하지만 스테퍼에는 모호합니다 — 빨간색 하나와 파란색 하나를 포함한 카드 아래의 마이너스는 어떤 것을 제거할지 추측해야 합니다. 카드는 제품의 변형이 카트에 몇 개 있는지 계산하고, 답이 정확히 하나일 때만 스테퍼를 제공합니다; 그렇지 않으면 총 개수를 배지로 표시하고 구매자를 상세 화면으로 보냅니다. 거절하는 것이 그들이 선택하지 않은 것을 제거하는 것보다 비용이 덜 듭니다.

  • 상세 화면은 자체 상태를 보유하지 않습니다. 선택된 제품과 변형은 위젯 상태에 저장됩니다. 구매자가 스크롤할 때 위젯이 다시 마운트되고(아래 참조), 로컬 useState는 선택을 잃을 수 있기 때문입니다. 새로운 searchId에서 지워지며, 그렇지 않으면 이전 검색의 제품이 새 결과 위에 다시 열릴 것입니다.

이 호스트

  • Access-Control-Allow-Headers를 먼저 확인한 후 플랫폼을 탓하세요. 이 파일은 위젯 iframe의 GET 요청이 서버에 도달하지 않는다고 주장하곤 했습니다. 실제로는 도달합니다. cashfree-here는 정산 GET 요청에 ngrok-skip-browser-warning을 보내며, 이로 인해 요청이 프리플라이트(preflight)됩니다. 우리의 허용 목록에 해당 헤더가 없었기 때문에 브라우저가 프리플라이트를 거부했고, GET 요청은 전송되지 않았습니다. 이후 정산 시스템이 "결제 상태를 확인할 수 없음"이라고 보고하며 이미 PAID 상태인 주문에 대해 결제 실패를 표시했습니다.

    이를 플랫폼의 장벽처럼 보이게 만든 두 가지 이유는 다음과 같습니다. GET 요청이 로그에 전혀 나타나지 않았고, 프리플라이트는 노이즈로 간주되어 로그에서 필터링되었습니다. 따라서 거부된 요청과 전송되지 않은 요청을 구분할 수 없었습니다. 이제 /api/*에 대한 프리플라이트가 로깅됩니다.

    POST 전용 엔드포인트(/api/pay/addresses/list, /api/orders/status)는 이러한 잘못된 진단에 기반하여 구축되었습니다. 작동은 하지만 필요하지 않습니다.

  • 모델이 호출한 도구 호출만이 호스트가 해당 도구의 outputTemplate을 렌더링하게 합니다. callTool은 핸들러를 실행하지만 아무것도 렌더링하지 않습니다.

  • 위젯 iframe 내에서 window.open이 차단되며, 호스트의 외부 열기(external-open) 기능은 네비게이션을 발생시켜 결제 중간에 MCP 커넥터를 종료시킵니다. 일반 <a target="_blank">만이 작동합니다.

  • MCP 전송은 상태 비저장입니다(sessionIdGenerator: undefined). 요청별로 새로운 서버를 구축하면서 세션 ID를 발급하면 initialize 이후의 모든 작업이 "Server not initialized" 오류로 실패합니다.

  • 위젯 상태는 위젯보다 오래 지속되므로 모든 도구 결과에 타임스탬프를 포함해야 합니다. 호스트는 전체 대화에 걸쳐 상태를 유지하고, 각 새 위젯을 해당 상태로 재수화(rehydrate)합니다. 따라서 결제 후 검색을 수행하면 이전 위젯이 screen: "checkout" 상태를 유지하고 있기 때문에 "셔츠 보여줘"라는 질문에 이전 영수증을 응답하게 됩니다. 이후 추가된 항목은 Shopify가 이미 완료한 장바구니에 담기게 됩니다. SearchProducts는 이제 호출마다 searchId를 스탬프하고, 위젯은 표시되지 않은 searchId에 대해 리셋됩니다.

  • 상태를 소유한 것이 리셋이 지워야 할 대상입니다. useCartuseCheckoutFlow는 마운트 시점에 스스로 시드(seed) 값을 설정하고, 전달된 값을 다시 읽지 않습니다. 따라서 위젯 상태만 지우는 것은 아무 효과가 없으며, 이들은 한 렌더링 후에 자신의 오래된 값을 다시 써버립니다. 세션은 searchId로 키(key)가 지정되므로 React가 이를 폐기합니다.

  • 리셋은 effect가 아닌 렌더링 중에 파생하세요. effect는 먼저 이전 화면을 그리게 됩니다. 구매자가 바지를 요청하면 이전 주문의 "결제 완료" 화면이 잠깐 나타난 후 대체되는 것을 보게 됩니다.

  • 라이브 위젯 간에 쓰기 작업 순서를 보장하는 것은 없습니다. 대화 내의 모든 이전 위젯은 계속 실행되며 하나의 origin 전체 localStorage 키에 씁니다. revision 카운터는 인스턴스 내부에서 오래된 스냅샷이 더 새로운 스냅샷을 덮어쓰는 것을 방지합니다. 그러나 인스턴스 쓰기 순서를 보장하지는 않습니다. 각 인스턴스가 자신의 카운터를 증가시키기 때문입니다. 대화별로 상태를 키잉(keying)하면 이 문제가 올바르게 해결됩니다.

  • 위젯은 보이는 것보다 훨씬 자주 다시 마운트되며, CORS 프리플라이트가 이를 알려줍니다. Claude는 구매자가 스크롤할 때 위젯 iframe을 파괴하고 다시 생성하며, 자체 캐시에서 HTML을 제공합니다. 따라서 resources/read가 나타나지 않고 로그에서 다시 마운트가 보이지 않습니다. 이를 드러내는 것은 OPTIONS /api/shop/cart입니다. 프리플라이트는 문서별로 캐시되므로, 새로운 프리플라이트는 새로운 문서를 의미합니다. 22:48:29와 22:52:25에 측정되었으며, 스크롤 외에는 아무것도 하지 않았습니다. 위젯 내부의 모든 래치(latch), ref 및 observer는 위젯과 함께 소멸됩니다. 따라서 "마운트 시 한 번 로드"는 비율 제한이 아니라 스크롤 , 위젯 비율입니다.

  • IntersectionObserver는 위젯이 화면에 표시되는지 알려주지 않습니다. 위의 명백한 해결책은 보이는 경우에만 가져오는 것입니다. 그러나 작동하지 않습니다. 중첩된 브라우징 컨텍스트 내에서 null root를 사용하면 observer는 호스트 페이지의 뷰포트가 아닌 해당 iframe의 뷰포트를 기준으로 측정하기 때문입니다. 화면 밖으로 멀리 스크롤된 모든 위젯이 완전히 보이는 것으로 보고합니다. 빌드, 테스트, 측정, 삭제 모두 완료했으며, 세 개의 위젯이 여전히 모든 리로드에서 가져오기를 수행합니다.

  • 호스트가 반환하지 않는 것을 캐시하세요. 드문 경우가 아닌 정기적인 리마운트 패턴이 있으므로, 마운트 시 다시 가져오는 모든 것은 계속해서 다시 가져오게 됩니다. 세 개의 위젯이 활성화되어 있으면 호스트 리로드당 세 개의 장바구니가 다시 로드되고, Shopify는 결국 429 Rate limit exceeded를 응답했습니다. 이제 장바구니 본문은 장바구니 ID와 타임스탬프와 함께 저장되며, TTL(만료 시간) 이후에만 다시 가져옵니다. 세 번의 흐름 세션이 19~20번의 업스트림 호출에서 13번으로 줄었고, 이전에 6번의 호출이 필요했던 두 번의 리로드는 이제 전혀 호출하지 않습니다.

    처음에는 TTL이 30초였지만 아무 효과가 없었습니다. 세 번의 흐름에서 측정한 결과, 장바구니의 마지막 가져오기와 다음 마운트 사이의 간격은 32초, 41초, 41초, 42초, 53초, 80초, 143초였으며, 모두 만료 시간을 초과했습니다. 이제 TTL은 10분입니다. 본문은 표시 전용입니다. 수량 변경은 서버에서 다시 시드(seed)되고 결제 금액은 Shopify의 장바구니에서 가져오므로, 오래된 금액으로 결제될 수 없습니다.

  • 위젯 URI를 버전 관리하면 해당 URI는 사용 중지되므로, 모든 빌드를 제공하세요. URI는 호스트 캐싱을 방지하기 위해 빌드 ID를 포함합니다(아래 참조). 단점은 리빌드가 대화에 이미 있는 모든 위젯이 생성될 때 사용된 ID를 무효화한다는 것입니다. 호스트가 기억하는 URI를 다시 읽으면 서버가 -32602 Resource not found를 응답하고, 해당 위젯들은 "스토어를 불러올 수 없음"을 렌더링합니다. 이제 ui://widget/shopify-store-{build}.html에 대한 ResourceTemplate이 사용 중지된 ID에 대해 현재 번들을 제공하여 위젯을 망가뜨리는 대신 업그레이드합니다. 참고로 이 문제는 새로운 대화에서도 발생합니다. 호스트의 캐시된 도구 메타데이터가 여전히 이전 URI를 참조하기 때문입니다.

  • CSP 경고는 사용자가 의도하지 않은 것에 대한 것일 수 있습니다. MCPJam은 모든 도구에서 https://cdn.openai.com이 차단되었다고 보고했습니다. 20개의 참조는 Apps SDK의 ./css 배럴(barrel)을 통해 가져온 katex.min.css@font-face 규칙에서 비롯된 것으로, 이 위젯이 렌더링하지 않는 수학을 위한 것입니다. 해당 도메인을 선언하면 Shopify 및 Cashfree 위젯이 Claude 내부에서 OpenAI의 CDN에 의존하게 됩니다. 대신 다른 6개의 시트를 경로로 가져오면 해당 참조와 21KB의 CSS가 제거됩니다. 또한, MCP Apps CSP 모델에는 scriptSrc가 없으며 connectDomains, resourceDomains, frameDomains만 있다는 점에 유의하세요. 따라서 스크립트 소스에 대한 불만은 서버가 처리할 수 있는 문제가 아닙니다.

  • 스트리밍 가능 HTTP의 GET 레그(leg)는 404 대신 405로 거절하세요. 전송이 상태 비저장이므로 열어야 할 서버-클라이언트 스트림이 없습니다. MCPJam은 한 세션에서 해당 레그를 97번 열었고 404를 받았지만 중단하지 않았습니다. 따라서 이것이 문제를 일으키는 것은 아닙니다. 더 엄격한 클라이언트는 initialize 이전에 포기하는 것으로 알려져 있습니다. 404는 "해당 엔드포인트 없음"으로 읽히며, 이는 다른 잘못된 응답입니다.

  • 리로드 비용은 호스트에 따라 다르며, ChatGPT가 그 비용을 부담합니다. Claude에서 리로드 비용은 현재 0입니다. 상태와 장바구니 본문 모두 저장소에서 다시 가져오기 때문입니다. ChatGPT에서는 카탈로그가 재전송되지 않으므로 useProducts가 이 서버에 카탈로그를 요청합니다. 따라서 리로드당 search_catalog 한 번만 호출되며 다른 것은 없습니다.

엔드포인트

경로

목적

POST /mcp

AI 호스트를 위한 HTTP를 통한 MCP

POST /api/shop/cart

Shopify에 대한 장바구니 생성/업데이트

POST /api/shop/search

도구 결과를 재전송하지 않고 리로드한 호스트를 위한 카탈로그 복구

POST /api/pay/order

Shopify 장바구니에서 가격을 책정한 Cashfree 주문 생성

POST /api/pay/otp, /otp/verify

OTP 로그인

POST /api/pay/addresses/list, /addresses

저장된 주소: 읽기 및 생성

POST /api/pay/dispatched

결제 도구 핸들러가 실제로 실행되었는지 확인

POST /api/orders/status

자체 확인 화면을 위한 주문 상태

GET /api/orders/:id

cashfree-here의 정산을 위한 원시 주문 본문

로그

모든 요청은 메서드, 경로, 상태 및 지속 시간을 기록합니다. MCP 호출은 메서드와 도구 이름을 지정하고, 리소스 읽기는 URI를 지정합니다. 모든 호스트 호출이 동일해 보일 때 POST /mcp 단독으로는 읽을 수 없습니다.

13:59:48.201 → POST /mcp (tools/call SearchProducts) 200 328ms
13:59:52.884 → POST /api/shop/cart 200 904ms
14:00:03.117 → POST /api/pay/order 200 1026ms
14:00:09.640 ✗ POST /api/pay/addresses 502 121ms

타임스탬프가 있는 이유는 지속 시간만으로는 두 요청 사이의 간격을 측정할 수 없기 때문입니다. 결제 전달이 지연될 때 중요한 질문은 바로 이 간격입니다.

테스트

npm test          # watch
npm run test:run
npm run type-check

테스트는 해당 코드 옆에 있습니다. src/lib/ucp/__fixtures__/ 아래의 픽스처는 실제 캡처된 Shopify 응답이므로, shape 변경 시 데모가 아닌 테스트가 실패합니다. Cashfree 픽스처는 수동으로 작성되었으며 내용이 수정되었습니다. 해당 세션 토큰은 커밋되어서는 안 됩니다.

문서

  • docs/cashfree-occ-api.md — 실제로 검증된 OCC 계약입니다. Cashfree의 공개 문서에는 없습니다.

  • docs/spikes/2026-08-12-occ-spike.md — 스파이크가 측정한 내용입니다.

  • docs/superpowers/specs/ — 각 마일스톤에 대한 설계 사양입니다.

  • docs/superpowers/plans/ — 이 사양이 된 작업별 구현 계획입니다.

F
license - not found
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Manage your Savanto store from your AI: catalog, content, prompts, and analytics, by chat.

  • Manage your NanoCart store from any AI agent: products, orders, coupons, subscribers, reports.

  • Amazon brand, seller, niche & buy-box intelligence inside your own Claude or ChatGPT.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/droiddevgeeks/shopify-mcp-demo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server