Conversation
|
Oxygen deployed a preview of your
Learn more about Hydrogen's GitHub integration. |
da1d725 to
1f5a330
Compare
824ff80 to
35c0000
Compare
- Add proper TypeScript types for JSON-RPC request handling - Use generated GraphQL types instead of 'as any' type assertions - Fix cart mutations to intelligently route between cartCreate/cartLinesAdd/cartLinesUpdate - Add null safety and input validation throughout
- Add null checks for all GraphQL query/mutation responses - Use consistent error messages that state observable facts - Add comment explaining policy search behavior returns all policies - Remove optional chaining where null checks are now explicit
- Add 'mcp' to ROUTE_MAP to include api.mcp.tsx when generating routes - Fixes failing tests that expect api.mcp to be copied with skeleton template
…refront MCP endpoint
d72a35c to
5135637
Compare
kdaviduik
left a comment
There was a problem hiding this comment.
LGTM aside from a few comments. Feel free to merge tomorrow if you address them and get another ✅ since I'll be OOO. Also:
Stale cookbook references (non-blocking): Three files reference the deleted api.$version.[graphql.json].tsx route:
cookbook/llms/express.prompt.md(line 2602)cookbook/recipes/express/README.md(line 2612)cookbook/recipes/express/recipe.yaml(line 82)
I think these should be updated in this PR since the PR is the direct cause of the breakage.
| /** | ||
| * Checks if the request is targeting the Storefront MCP endpoint. | ||
| */ | ||
| isMcpUrl(request) { |
There was a problem hiding this comment.
blocking: unit tests for isMcpUrl() and forwardMcp() are still missing.
This was blocking in the previous review and remains unaddressed. Test infrastructure already exists in storefront.test.ts (tests createStorefrontClient with mocked fetchWithServerCache, tests header forwarding) and request.test.ts (tests extractHeaders and getHeader). At minimum the test cases from the prior review should be covered:
isMcpUrlreturns true for/api/mcpand false for other paths (e.g.,/api/mcp/foo,/api/mcps,/api/2024-10/graphql.json)isMcpUrlreturns false for/api/mcp/(trailing slash)isMcpUrlreturns true for full URLs with query parameters (e.g.,https://store.com/api/mcp?session=abc) -getSafePathnamestrips query params before matchingforwardMcpforwards the correct headers to{shopDomain}/api/mcpforwardMcppreservesx-forwarded-forfrom buyer IP- Integration with
createRequestHandlerroutes MCP requests to the proxy
Note: the existing SFAPI proxy also lacks unit tests - that's pre-existing debt, but new code is the right time to establish the pattern.
| // Add headers for shop identification and geolocalization | ||
| ...extractHeaders( | ||
| (key) => defaultHeaders[key], | ||
| [ |
There was a problem hiding this comment.
non-blocking: basically same comment as here.
I think forwardMcp should forward STOREFRONT_REQUEST_GROUP_ID_HEADER (and ideally SHOPIFY_STOREFRONT_ID_HEADER) for observability.
The existing SFAPI forward method includes both of these from defaultHeaders for tracing/debugging and storefront identification. Without the request group ID header, MCP requests are invisible in Hydrogen's request correlation/debugging story.
Re: content-length - you're right that it's a forbidden request header per the Fetch spec, so omitting it is appropriate.
|
|
||
| const mcpUrl = `${getShopifyDomain()}/api/mcp`; | ||
|
|
||
| const mcpResponse = await fetch(mcpUrl, { |
There was a problem hiding this comment.
non-blocking: I mentioned this in my previous review and it was marked as resolved but my agents brought it up again, and I don't see any "jsonrpc" stuff in the code.
If fetch(mcpUrl, ...) throws (DNS failure, connection refused, TLS error), the error propagates as an unhandled exception. MCP clients speak JSON-RPC and expect structured error responses ({"jsonrpc": "2.0", "error": {...}}). An unhandled exception produces an opaque 500 or HTML error page - the MCP client can't distinguish "the server crashed" from "the proxy is misconfigured" from "the upstream is down", and an AI assistant might silently break with no useful error message for the end user.
The existing SFAPI forward has the same gap (pre-existing), but for new code a try/catch returning a proper JSON-RPC error would be an improvement - and a natural test case to add alongside the other missing tests.
Also worth noting: if request.body has already been consumed by upstream middleware, fetch silently sends an empty body - another scenario where a clear error response would help debugging.
| return response; | ||
| } | ||
|
|
||
| if (storefront?.isMcpUrl(request)) { |
There was a problem hiding this comment.
non-blocking as long as you follow up: every Hydrogen merchant who upgrades gets this new publicly-accessible /api/mcp proxy endpoint. It's gated behind proxyStandardRoutes (defaults true), which is consistent with the SFAPI proxy - merchants who have accepted proxyStandardRoutes: true have implicitly accepted this class of auto-proxied endpoint, and the risk profile is comparable.
I think the changeset should clarify the gating mechanism. Suggested wording: "This feature is automatically available when proxyStandardRoutes is enabled (the default) - no code changes required."
i'm OOO; dismissing so others can approve and unblock this
WHY are these changes introduced?
Enables AI agents (Claude, ChatGPT, and other MCP clients) to interact with Hydrogen storefronts through the standardized Model Context Protocol. This allows AI assistants to help customers browse products, manage carts, and get store information in natural language.
Related: https://shopify.dev/docs/apps/build/storefront-mcp/servers/storefront
WHAT is this pull request doing?
Adds automatic MCP proxy support to Hydrogen's
createRequestHandler. When requests are made to/api/mcp, Hydrogen now automatically forwards them to Shopify's centrally-hosted Storefront MCP server.The implementation:
/api/mcppath pattern${shopDomain}/api/mcpwith proper headersThis approach mirrors how Hydrogen already proxies Storefront API GraphQL requests at
/api/:version/graphql.json(which this PR also cleans up by removing the now-redundant manual route file).Additional cleanup: Removes the
api.$version.[graphql.json].tsxroute from the skeleton template sincecreateRequestHandlerhas automatically proxied these requests since December 2025 viaproxyStandardRoutes: true(enabled by default).HOW to test your changes?
Build and start the skeleton template:
cd templates/skeleton pnpm install pnpm build pnpm previewTest the tools/list endpoint:
Should return JSON with available MCP tools from Shopify's Storefront MCP server.
Test product search:
Should return product data from the store.
Verify the redundant Storefront API route was removed:
templates/skeleton/app/routes/api.$version.[graphql.json].tsxno longer exists/api/2024-10/graphql.jsonstill works via the automatic proxyImplementation Notes
Proxy Architecture
Similar to the existing Storefront API proxy (
isStorefrontApiUrl+forwardStorefrontApi), this adds:isMcpUrl()- Checks if request matches/api/mcpforwardMcp()- Forwards request to Shopify's hosted MCP endpointThe proxy forwards essential headers:
accept,content-type,cookie,origin,referer,user-agentShopify-Storefront-Private-Token(from storefront config)x-forwarded-forwith buyer IP for market/currency detectionWhy Proxy Instead of Local Implementation?
Shopify's centrally-hosted MCP server provides:
Checklist