Skip to main content
Glama

Shopify MCP Demo

Durchstöbere einen echten Shopify-Shop direkt aus ChatGPT oder Claude und bezahle mit Cashfree – Katalog, Warenkorb, OTP-Anmeldung, gespeicherte Adressen und Zahlung, ohne die Unterhaltung verlassen zu müssen.

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

Jeder Zahlungsweg endet auf demselben Bildschirm: was gekauft wurde, mit Mengen und Preisen, plus Bestell-ID und Status. Cashfree bestätigt, dass Geld geflossen ist, hat aber nie den Shopify-Warenkorb gesehen und kann daher nicht sagen, was darin war.

Nach dem Bezahlen erneut suchen – das Widget startet eine neue Shopping-Sitzung: neuer Warenkorb, keine Quittung bleibt übrig. Zweimaliges Kaufen in einer Unterhaltung funktioniert.

Funktionsweise

Der Server ist drei Dinge zugleich:

  • ein MCP-Server für den KI-Host, der ein modellseitiges Tool (SearchProducts) sowie eine Widget-Ressource bereitstellt

  • ein MCP-Client für Shopify, der über JSON-RPC mit https://{SHOP_DOMAIN}/api/ucp/mcp kommuniziert – keine Authentifizierung, die Shop-Domain ist die gesamte Konfiguration

  • ein REST-Client für Cashfree, zur Erstellung von Bestellungen, OTP-Anmeldung, gespeicherten Adressen und Bestellstatus

Das React-Widget steuert die gesamte Reise. Nur die Produktsuche erreicht das Modell; alles danach läuft Widget-zu-Server, was den Ablauf deterministisch hält.

Das Stöbern umfasst zwei Bildschirme. Das Raster zeigt eine Karte pro Produkt mit dessen Preisspanne und Anzahl der Optionen; ein Tippen öffnet einen Detailbildschirm mit Beschreibung, Variantenauswahl und „In den Warenkorb". Ein Produkt mit einer einzigen Variante kann direkt von seiner Karte hinzugefügt werden, und ein bereits im Warenkorb befindliches Produkt erhält dort einen Mengenwähler – so kosten die häufigsten Fälle einen Tipp und nur eine echte Wahl einen Bildschirm. Beide Bildschirme zeigen an, was bereits im Warenkorb ist, und die beiden Zählungen stammen aus derselben Funktion.

Related MCP server: Shopify Agentic MCP Gateway

Einrichtung

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

Machen Sie es zugänglich (ngrok http 8787), setzen Sie SERVER_URL in .env auf die öffentliche Herkunft, starten Sie neu, und fügen Sie dann <öffentliche-Herkunft>/mcp als Connector in Ihrem Host hinzu.

Ein Neustart reicht – kein Neubau nötig. Die Herkunft wird beim Start gelesen und bei jeder Bereitstellung der Ressource in das Widget-HTML eingefügt.

Variable

Zweck

SHOP_DOMAIN

Der Shop. Wechseln Sie Shops, indem Sie diese Zeile bearbeiten und neu starten.

UCP_AGENT_PROFILE

Erforderlich bei jedem UCP-Aufruf. Das öffentliche Beispielprofil von Shopify ist für eine Demo geeignet.

CASHFREE_ENV

sandbox (Standard) oder production.

CASHFREE_CLIENT_ID / CASHFREE_CLIENT_SECRET

Dashboard → Entwickler → API-Schlüssel.

CASHFREE_RETURN_URL

Wo Cashfree den Käufer zurückleitet. Standardmäßig eine stabile gehostete Seite.

SERVER_URL

Öffentliche Herkunft bei Tunnelbetrieb.

PORT

Standardmäßig 8787.

PAYMENT_ANNOTATIONS

honest (Standard) oder readonly. Ändern Sie es in .env und starten Sie neu, um beide Dispatch-Pfade zu testen. readonly lässt die Zahlungstools vorgeben, schreibgeschützt zu sein, damit der Host sie dispatchen kann – nur zur Diagnose, siehe oben. Eine Shell-Variable überschreibt die Datei, da --env-file keine bereits gesetzte Umgebungsvariable überschreibt.

Was Sie beim Ausführen erwartet

Zahlung funktioniert, außer gespeicherte Karten. Der Host steuert Zahlungstools basierend auf deren MCP-Annotationen. cashfree-here liefert die ehrlichen – { readOnlyHint: false, destructiveHint: true } für ein Tool, das eine Karte belastet – und dagegen lehnt der Host den Dispatch ab: das Modell formuliert die Absicht, der Host ruft die Widget-Vorlage des Tools vorab ab, und es kommt nie ein tools/call an.

Durch Setzen von PAYMENT_ANNOTATIONS=readonly werden sie zu { readOnlyHint: true, destructiveHint: false } überschrieben, und vier der fünf Tools werden dispatchen: UPI, Online-Banking, gehosteter Checkout, neue Karte – und sobald die Übergabe erfolgt, auch gespeicherte Karte.

Dieses Flag ist eine Messung, keine Lösung. Es lässt ein Tool, das Geld bewegt, behaupten, es tue nichts – eine Lüge gegenüber genau der Kontrolle, die existiert, um dies zu verhindern. Es ist standardmäßig ausgeschaltet, gibt bei erster Verwendung eine Warnung aus und darf nicht ausgeliefert werden.

Wenn ein Tool blockiert ist, zeigt das Widget dies an und bietet einen Cashfree-Link an, der funktioniert. Im Tab bezahlen, zurückkommen, und das Widget bestätigt die Bestellung.

Die Übergabe ist spät, nicht verloren – und „blockiert" kann falsch sein. Diese Datei sagte früher, dass die Widget-zu-Modell-Übergabe etwa zur Hälfte der Fälle fehlschlug. Eine aufgezeichnete Sitzung sagt etwas anderes: CheckoutTool wurde nach zwei Versuchen des Widgets (2 × 4s) als blockiert gemeldet, der Käufer nutzte den Cashfree-Link, und das tools/call kam dann trotzdem an – etwa 2–4s nach dem Klick, also etwa 12–20s Ende-zu-Ende. Der Server bearbeitete es in 37ms. Jede Millisekunde dieser Verzögerung lag upstream.

sendFollowUpMessage bittet den Host nicht, ein Tool auszuführen. Es sendet eine Benutzeraktion und löst sich auf, sobald diese Nachricht zugestellt ist – daher beginnt das Bestätigungsfenster des Widgets zum Zeitpunkt der Einreihung und misst dann eine vollständige Modell-Inferenzrunde auf der Host-Infrastruktur – gegen die das 4s-Timeout nie kalibriert wurde.

Die Folge ist schlimmer als ein langsamer Bildschirm: Der Käufer bezahlte auf dem externen Link, während im Hintergrund ein Cashfree-Widget für dieselbe payment_session_id gerendert wurde. Zwei aktive Zahlungsoberflächen für eine Bestellung. DISPATCH_ATTEMPTS = 2 sendet die Folgenachricht zweimal, also sind auch zwei Widgets möglich.

Die Lesart „scheitert zur Hälfte der Fälle" war unser eigener Fehler. Zwei App-Instanzen wurden auf dem einen postMessage-Kanal geöffnet, den der MCP-Apps-Transport bereitstellt: useMcpApp erstellte und verband eine für das Rendering, getClientPlatform() erstellte und verband eine andere für die Zahlungsübergabe. Die Handshakes liefen um die Wette, und der Verlierer antwortete auf alles Folgende mit „Nicht verbunden" – ein Käufer wählte eine Zahlungsmethode und es erreichte nie ein tools/call den Server. Eine Münzwurf-Übergabe sieht aus wie ein Zwei-Wege-Rennen, nicht wie ein unzuverlässiger Host. Der Hook abonniert nun den gemeinsamen Client, connect() ist idempotent, und das Aushängen ruft nicht mehr close() auf einem Singleton auf, der den React-Baum überlebt.

Seit dieser Korrektur ist jeder Dispatch beim ersten Versuch gelandet. attemptsFor() gibt bereits 1 bei MCP-Apps-Hosts zurück, daher gilt die Wiederholung nur für ChatGPT, das LegacyOpenAiClient verwendet und dieses Rennen nie hatte – es gibt keine Messung, die seine Entfernung rechtfertigt, also bleibt es.

UPI scheitert über ₹1.00.000 mit „Zahlungsmethode ist für diese Bestellung nicht geeignet" – das Transaktionslimit von UPI, bestätigt durch Aufteilung bei ₹99.600 (ok) / ₹1.00.800 (fehlgeschlagen). Online-Banking und gehosteter Checkout haben höhere Limits. Der Warenkorb hat keine Obergrenze, daher kann man mit ein paar Tippen auf + ohne Warnung darüber hinausgehen.

Das Neuladen des Host-Fensters ist sicher, bei beiden Hosts. Warenkorb, Checkout-Schritt, gespeicherte Adressen und der Zahlungsbildschirm kommen alle zurück; der Warenkorb-Inhalt wird aus dem persistenten Zustand wiederhergestellt, nicht erneut abgerufen, daher kostet ein Neuladen in Claude keine Aufrufe an Shopify. ChatGPT liefert das Tool-Ergebnis nicht erneut aus, daher kostet es einen search_catalog-Aufruf, um das Produktraster neu aufzubauen, und nichts weiter. Gemessen über vier Abläufe mit Neuladungen bei mehreren Schritten: 18 upstream-Aufrufe, keiner davon eine Wiederholung.

Die Build-ID wird auf dem Zahlungsbildschirm angezeigt. Hosts cachen Widget-Instanzen, und eine gecachte ist von einer aktuellen nicht zu unterscheiden – mehrere Debugging-Runden gingen einst in Code, der bereits gelöscht war. Wenn die Build-ID nicht mit dem laufenden Server übereinstimmt, sehen Sie ein veraltetes Widget.

Ein Neubau erreicht den Browser nicht, und ein neuer Chat auch nicht. Die Widget-URI trägt die Build-ID, sodass jeder Build eine eigene Ressource ist – und das reicht immer noch nicht: Claude lieferte ein gecachtes Widget über Neubauten und neue Unterhaltungen hinweg aus, ohne ein einziges resources/read im Log. Zwei Debugging-Runden gingen in Instrumentierung, die nie ausgeführt wurde. Der einzig zuverlässige Weg, ein erneutes Lesen zu erzwingen, ist, den Connector zu trennen und wieder zu verbinden. Überprüfen Sie das Log auf POST /mcp (resources/read ui://widget/shopify-store-<build>.html), bevor Sie irgendetwas Gesehenem vertrauen.

Das erneute Verbinden ist immer noch nicht die ganze Geschichte: Die gecachten Tool-Metadaten des Hosts können eine URI aus einem früheren Build nennen, sodass selbst eine neue Unterhaltung nach einer Build-ID fragen kann, die dieser Server nicht mehr hat. Er antwortet für jede davon – siehe „Versionierung der Widget-URI führt zu ihrer Ablösung" weiter unten – aber die ID in dieser resources/read-Zeile ist die des Hosts, nicht unbedingt die des laufenden Servers. window.__BUILD__ auf dem Zahlungsbildschirm sagt Ihnen, welches Bundle ausgeführt wird.

Bekannte Einschränkungen

  • Karteneingabe kann in Claude nicht gerendert werden und wird upstream nicht behoben. Cashfree Elements mountet seine PCI-Felder als verschachtelte Cross-Origin-Iframes. Claude erzwingt frame-src 'self' blob: data: und ignoriert die frameDomains, die eine UI-Ressource deklariert, sodass die Felder als leere, nicht anklickbare Boxen geladen werden. Dies ist eine Richtlinie, kein Fehler im Transit: Ein Anthropic-Ingenieur erklärte am 09.04.2026 in claude-ai-mcp#40, dass verschachtelte Iframes aus Sicherheitsgründen nicht erlaubt sind, und zwei spätere Fragen nach „dauerhaft oder vorübergehend?“ blieben unbeantwortet. connectDomains und resourceDomains wurden im April behoben und funktionieren – nur das Framing ist blockiert, daher ist fetch vom Widget zu diesem Server in Ordnung.

    Stripe traf auf die gleiche Hürde: Die MCP-Apps-Dokumentation öffnet eine gehostete Checkout-Seite mit app.openLink() anstatt Karteneingabefelder einzubetten. Das ist die gleiche Form wie CheckoutTool hier, das heute funktioniert und der empfohlene Weg für Karten in Claude ist.

    Die Karteneingabe im Gespräch zu behalten ist möglich – einfache <input>-Felder (kein Iframe), die direkt an diesen Server senden, was cashfree-here vor Commit 55da139 auslieferte, als es durch Elements ersetzt wurde. Es leitet Roh-PANs durch unseren Server (SAQ-D-Territorium) und 3DS leitet immer noch aus, daher erkauft es die Form, nicht den Ablauf. Nicht gebaut; eine Produktentscheidung, keine technische.

  • Zahlung mit gespeicherter Karte schlägt innerhalb von Cashfree fehl. CardPaymentTool sendet und listet gespeicherte Karten korrekt, aber die Zahlung mit einer Karte gibt HTTP 500 {"message":"Internal Server Error"} von /pg/orders/sessions/js zurück. Auf eine Bestellung isoliert: UPI und Netbanking geben beide 200 mit derselben payment_session_id und denselben Headern zurück; nur der Zweig payment_method.card.instrument_id liefert 500, mit oder ohne CVV. Ein generischer 500 ist ein Fehler seitens Cashfree – deren Validierungsfehler sind 400 mit spezifischen Nachrichten. Benötigt Cashfree.

  • UPI wird oberhalb seines Limits von ₹1.00.000 angeboten und schlägt bei Cashfree fehl, anstatt im Auswahlmenü deaktiviert zu sein.

  • cashfree-here wird direkt patched. Zwei Korrekturen leben im Schwester-Checkout, nicht in diesem Repository: useReconciliation.start() löscht jetzt den vorherigen Poll-Timer, und die Benachrichtigung über erfolgreiche Zahlung feuert einmal pro Bestellung. Ohne sie hat eine bezahlte Bestellung „Payment completed successfully“ an den Chat gesendet, alle paar Sekunden, endlos.

  • Die ausgewählte Adresse ist nicht an die Bestellung gebunden. Der Käufer wählt eine aus und Cashfree wird nicht informiert. Ungelöst; benötigt eine Antwort vom OCC-Team.

  • Es wird keine Shopify-Bestellung erstellt. Die Bestellung lebt nur in Cashfree. Das Erstellen einer benötigt Shopify-Admin-API-Anmeldeinformationen, die dieses Projekt bewusst vermeidet.

  • Der Session-Speicher ist im Arbeitsspeicher. Ein Server-Neustart verliert einen laufenden Checkout.

  • Angebote und Gutscheine werden zurückgestellt. Beide APIs sind nachgewiesen und dokumentiert in docs/cashfree-occ-api.md; nur die UI fehlt.

  • Nur INR, passend zu den Demoshops.

Hinweise für alle, die dieses Projekt erweitern

Erkenntnisse, die viel Zeit gekostet haben. Jede ist gemessen, nicht angenommen.

Shopify

  • Ziele nur /api/ucp/mcp an. Die ältere /api/mcp ist nach dem 31.08.2026 veraltet und sagt dies bei jeder Antwort.

  • Die veröffentlichte Dokumentation ist an drei Stellen falsch: Sie vertauscht die Aufteilung der Endpunkte für Katalog/Warenkorb und dokumentiert Warenkorbzeilen als merchandise_id, wenn UCP line_items[].item.id erfordert. Typen hier stammen aus erfassten Payloads in src/lib/ucp/__fixtures__/, nicht aus der Dokumentation.

  • update_cart ist deklarativ – sende jedes Mal den vollständigen gewünschten Zeilensatz; das Entfernen wird durch Weglassen einer Zeile ausgedrückt.

  • Geld ist in Untereinheiten mit der Währung einmal auf Warenkorbebene gehalten. formatMoney nimmt die Dezimalstellenanzahl von Intl, daher werden Währungen ohne Dezimalstellen nicht geteilt.

  • Ein passwortgeschützter Shop stellt weiterhin Katalog-, Warenkorb- und Checkout-Links bereit. Nur das Durchsuchen des Shops stößt auf die Passwortseite.

  • search_catalog gibt Produktbeschreibungen als HTML zurück, und Varianten tragen ihre Achsen als options: [{ name, label }]. Die Beschreibung wird in normalise.ts auf Nur-Text reduziert, bevor sie React erreicht: Es handelt sich um vom Shop kontrollierte Inhalte, die auf demselben Bildschirm gerendert werden, der eine OTP sammelt, und keine Formatierung darin ist eine Injection-Oberfläche wert.

  • Ein Produkt mit einer einzigen Variante hat dennoch eine Option – Shopifys Platzhalter { name: "Titel", label: "Standardtitel" }. Das Rendern zeigt „1 Titel“ unter einem Produkt, das keine Auswahlmöglichkeiten bietet.

Cashfree

  • x-chxs-id ist die payment_session_id aus „Bestellung erstellen“. Das erzwingt, dass die Bestellung vor dem Login existiert. Eine erfundene gibt payment_session_id_invalid zurück.

  • Die OCC-Aufrufe benötigen genau drei Header. Keine der Browser-Fingerprinting-Daten in erfassten Anfragen – Geräte-IDs, Forter-Token, Cookies, Origin – wird erzwungen.

  • Adressen erfordern eine kombinierte Adresszeile von 10–185 Zeichen. Kürzere geben einen 400 ohne Hinweis in der UI, es sei denn, man prüft darauf.

  • Die Antwort auf Adresserstellung ist { shipping_address, billing_address }, keine Liste. Das Parsen als Liste gibt bei Erfolg ein leeres Array zurück.

  • /api/orders/:id gibt den rohen Body von Cashfree weiter, weil die Abgleichslogik von cashfree-here dieses Format parst.

Das Widget

  • Eine Karte ist ein Produkt; eine Warenkorbzeile ist eine Variante, und sie passen nicht zusammen. Drei Farben eines T-Shirts in eine Karte zusammenzufassen ist richtig zum Durchsuchen und mehrdeutig für einen Step-Stepper – ein Minus unter einer Karte, die ein Rot und ein Blau enthält, muss raten, welche weggenommen werden soll. Die Karte zählt, wie viele Varianten des Produkts im Warenkorb sind, und bietet einen Stepper nur an, wenn die Antwort genau eins ist; andernfalls zeigt sie die Gesamtzahl als Badge und schickt den Käufer zum Detailbildschirm. Verweigern ist billiger, als etwas zu entfernen, das sie nicht ausgewählt haben.

  • Der Detailbildschirm hält keinen eigenen Zustand. Das ausgewählte Produkt und die Variante leben im Widget-Zustand, weil das Widget neu gemountet wird, wenn der Käufer scrollt (siehe unten) und ein lokales useState würde die Auswahl damit verlieren. Sie werden bei einer neuen searchId gelöscht, sonst würde das Produkt der vorherigen Suche über den neuen Ergebnissen wieder aufgehen.

Dieser Host

  • Prüfen Sie Access-Control-Allow-Headers, bevor Sie die Plattform beschuldigen.
    In dieser Datei hieß es früher, dass GET-Anfragen aus dem Widget-Iframe den Server nie erreichen. Das tun sie doch. cashfree-here sendet bei seinem Abgleich-GET ngrok-skip-browser-warning, was die Anfrage preflight-pflichtig macht; unsere Erlaubnisliste enthielt diesen Header nicht, also lehnte der Browser den Preflight ab und der GET wurde nie gesendet. Der Abgleich meldete dann „Zahlungsstatus kann nicht überprüft werden“ und zeigte Zahlung fehlgeschlagen bei Bestellungen, die bereits BEZAHLT waren.

    Zwei Dinge ließen es wie eine Plattform-Barriere aussehen: Es erschien nie ein GET im Log, und Preflights wurden als Rauschen aus dem Log gefiltert – so waren eine abgelehnte Anfrage und eine nie gesendete Anfrage nicht zu unterscheiden. Preflights auf /api/* werden jetzt protokolliert.

    Die Nur-POST-Endpunkte (/api/pay/addresses/list, /api/orders/status) wurden auf dieser falschen Diagnose aufgebaut. Sie funktionieren, sind aber nicht notwendig.

  • Nur ein modellaufgerufener Tool-Aufruf bewirkt, dass der Host die outputTemplate dieses Tools rendert. callTool führt den Handler aus und rendert nichts.

  • window.open wird im Widget-Iframe blockiert, und der externe Öffnungsmechanismus des Hosts navigierte weg und tötete den MCP-Connector mitten in der Zahlung. Ein einfaches <a target="_blank"> ist das Einzige, was funktioniert.

  • Der MCP-Transport ist zustandslos (sessionIdGenerator: undefined). Das Ausstellen von Session-IDs beim Erstellen eines neuen Servers pro Anfrage führt dazu, dass alles nach initialize mit „Server nicht initialisiert“ fehlschlägt.

  • Der Widget-Zustand überlebt das Widget, daher muss jedes Tool-Ergebnis datiert sein.
    Der Host behält den Zustand für die gesamte Konversation und rehydriert jedes neue Widget daraus. Eine Suche nach einer Zahlung wachte daher mit screen: "checkout" auf und antwortete auf „zeige mir Hemden“ mit der vorherigen Quittung – und der nächste hinzugefügte Artikel landete in einem Warenkorb, den Shopify bereits abgeschlossen hatte. SearchProducts stempelt jetzt pro Aufruf eine searchId und das Widget setzt zurück, wenn es eine noch nicht gezeigte ID sieht.

  • Was auch immer den Zustand besitzt, ist das, was ein Zurücksetzen löschen muss. useCart und useCheckoutFlow initialisieren sich beim Mounten und lesen nie wieder die übergebenen Werte, daher bewirkte das Löschen des Widget-Zustands allein nichts und sie schrieben ihre veralteten Werte einen Render später direkt zurück. Die Session ist auf searchId geschlüsselt, sodass React sie stattdessen verwirft.

  • Leiten Sie ein Zurücksetzen während des Renderings ab, nicht in einem Effekt. Ein Effekt malt zuerst den alten Bildschirm; ein Käufer, der nach Hosen fragt, sah die „Zahlung erhalten“-Meldung der vorherigen Bestellung erscheinen und dann ersetzt werden.

  • Nichts ordnet Schreibvorgänge zwischen aktiven Widgets. Jedes frühere Widget in einer Konversation läuft weiter und schreibt in einen einzigen ursprungsweiten localStorage-Schlüssel. Ein revision-Zähler verhindert, dass ein veralteter Snapshot einen neueren innerhalb einer Instanz ersetzt; er ordnet keine Schreibvorgänge zwischen Instanzen, da jede ihren eigenen Zähler hochzählt. Die Schlüsselung des Zustands pro Konversation würde das Problem richtig beheben.

  • Ein Widget wird viel häufiger neu gemountet, als es aussieht, und der CORS-Preflight verrät es. Claude zerstört und erstellt das Widget-Iframe neu, während der Käufer scrollt, und bedient das HTML aus seinem eigenen Cache – daher erscheint kein resources/read und das Neumontieren ist im Log unsichtbar. Was es verrät, ist OPTIONS /api/shop/cart: Ein Preflight wird pro Dokument zwischengespeichert, also bedeutet ein neuer Preflight ein neues Dokument. Gemessen um 22:48:29 und 22:52:25, während nichts außer Scrollen passierte. Jeder Latch, Ref und Observer innerhalb des Widgets stirbt damit, daher ist „einmal beim Mounten laden“ keine Ratenbegrenzung – es ist eine Rate pro Scrollen, pro Widget.

  • IntersectionObserver sagt Ihnen nicht, ob das Widget auf dem Bildschirm ist. Die naheliegende Lösung oben – nur bei Sichtbarkeit abrufen – funktioniert nicht: Mit einem Null-Root in einem verschachtelten Browsing-Kontext misst der Observer gegen den Viewport dieses Iframes, nicht den der Host-Seite. Jedes weit aus dem Blickfeld gescrollte Widget meldet sich als vollständig sichtbar. Gebaut, getestet, gemessen, gelöscht – drei Widgets holten bei jedem Neuladen immer noch Daten.

  • Zwischenspeichern, was der Host nicht zurückgibt. Bei einer Routine des häufigen Neumontierens statt seltener Ereignisse wird alles, was beim Mounten neu abgerufen wird, ständig neu abgerufen: Drei aktive Widgets bedeuteten drei neu geladene Warenkörbe pro Host-Neuladen, und Shopify antwortete irgendwann mit 429 Rate limit exceeded. Der Warenkorb-Inhalt wird jetzt mit der Warenkorb-ID und einem Zeitstempel persistiert und nur nach Ablauf einer TTL neu abgerufen. Eine Drei-Fluss-Sitzung ging von 19–20 Upstream-Aufrufen auf 13 zurück, und zwei Neuladevorgänge, die früher sechs Aufrufe kosteten, kosten jetzt keinen.

    Die TTL betrug zunächst 30s, was nichts verhinderte: Gemessen über drei Flüsse betrug die Lücke zwischen dem letzten Abruf eines Warenkorbs und seinem nächsten Mounten 32s, 41s, 41s, 42s, 53s, 80s und 143s – jeder einzelne überschritt das Fenster. Jetzt beträgt sie 10 Minuten. Der Inhalt ist nur zur Anzeige; eine Mengenänderung bezieht die Daten neu vom Server, und die Zahlung wird über den Shopify-Warenkorb bepreist, sodass ein veralteter Wert nicht bezahlt werden kann.

  • Die Versionierung der Widget-URI macht sie ungültig, also bedienen Sie jeden Build. Die URI trägt eine Build-ID (siehe unten), um das Host-Caching zu umgehen. Der Preis ist, dass ein Neubau die ID ungültig macht, mit der jedes bereits in einer Konversation befindliche Widget erstellt wurde: Der Host liest die gespeicherte URI erneut, der Server antwortet mit -32602 Resource not found, und diese Widgets rendern „Store konnte nicht geladen werden“. Eine ResourceTemplate für ui://widget/shopify-store-{build}.html bedient jetzt das aktuelle Bundle für ungültige IDs, was sie aktualisiert statt sie zu zerstören. Beachten Sie, dass dies auch in einer neuen Konversation zuschlägt – die zwischengespeicherten Tool-Metadaten des Hosts nennen noch die alte URI.

  • Eine CSP-Warnung kann sich auf etwas beziehen, das Sie nie ausliefern wollten. MCPJam meldete https://cdn.openai.com als blockiert bei jedem Tool. Die zwanzig Referenzen stammten aus @font-face-Regeln in katex.min.css, die über das ./css-Barrel des Apps SDK eingebunden wurden, für Mathematik, die dieses Widget nicht rendert. Die Deklaration der Domain hätte ein Shopify- und Cashfree-Widget von OpenAIs CDN innerhalb von Claude abhängig gemacht; das Importieren der anderen sechs Stylesheets über den Pfad entfernte stattdessen die Referenz und 21KB CSS. Beachten Sie auch, dass das CSP-Modell der MCP Apps kein scriptSrc hat – nur connectDomains, resourceDomains und frameDomains – daher kann eine Skriptquellen-Beschwerde nicht vom Server beantwortet werden.

  • Lehnen Sie das GET-Bein von streamable HTTP mit 405 ab, nicht mit 404. Der Transport ist zustandslos, daher gibt es keinen Server-zu-Client-Stream zum Öffnen. MCPJam öffnete dieses Bein 97 Mal in einer Sitzung und nahm die 404 hin, ohne abzubrechen, also ist dies nicht das, was es dort zum Scheitern bringt; strengere Clients sollen vor initialize aufgeben. 404 wird als „kein solcher Endpunkt“ gelesen, was eine andere und falsche Antwort ist.

  • Die Neuladekosten unterscheiden sich je nach Host, und es ist ChatGPT, das zahlt. In Claude kostet ein Neuladen jetzt nichts: Zustand und Warenkorb-Inhalt kommen beide aus dem Speicher zurück. In ChatGPT wird der Katalog nicht erneut ausgeliefert, daher fragt useProducts diesen Server danach – ein search_catalog pro Neuladen und sonst nichts.

Endpunkte

Pfad

Zweck

POST /mcp

MCP über HTTP für den KI-Host

POST /api/shop/cart

Warenkorb erstellen/aktualisieren gegen Shopify

POST /api/shop/search

Katalogwiederherstellung für einen Host, der neu geladen hat, ohne das Tool-Ergebnis erneut auszuliefern

POST /api/pay/order

Cashfree-Bestellung erstellen, bepreist über den Shopify-Warenkorb

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

OTP-Anmeldung

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

Gespeicherte Adressen: lesen und erstellen

POST /api/pay/dispatched

Wurde ein Zahlungs-Tool-Handler tatsächlich ausgeführt?

POST /api/orders/status

Bestellstatus für unseren eigenen Verifikationsbildschirm

GET /api/orders/:id

Roher Bestelltext für den Abgleich von cashfree-here

Logs

Jede Anfrage protokolliert Methode, Pfad, Status und Dauer; MCP-Aufrufe nennen die Methode und das Tool, und Ressourcen-Lesevorgänge nennen die URI – POST /mcp allein ist unlesbar, wenn jeder Host-Aufruf identisch aussieht.

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

Der Zeitstempel ist vorhanden, weil Dauern allein die Lücke zwischen zwei Anfragen nicht messen können, was die einzige relevante Frage ist, wenn eine Zahlungsauslieferung zu spät eintrifft.

Tests

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

Tests liegen neben dem Code, den sie abdecken. Fixtures unter src/lib/ucp/__fixtures__/ sind echte aufgezeichnete Shopify-Antworten, sodass eine Formänderung einen Test und nicht eine Demo zum Scheitern bringt. Cashfree-Fixtures sind handgeschrieben und geschwärzt – deren Session-Token dürfen nicht eingecheckt werden.

Dokumente

  • docs/cashfree-occ-api.md – der OCC-Vertrag, live verifiziert. Nicht in Cashfrees veröffentlichten Dokumenten.

  • docs/spikes/2026-08-12-occ-spike.md – was der Spike gemessen hat.

  • docs/superpowers/specs/ – Designspezifikationen für jeden Meilenstein.

  • docs/superpowers/plans/ – die Aufgaben-für-Aufgaben-Umsetzungspläne, zu denen sie wurden.

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