Magento 2 MCP, Part 3: A Storefront Chatbot

Magento 2 and MCP, Part 3: A Storefront Chatbot Scoped to the Customer

This is the companion to "Magento 2 and MCP: A Thin API Wrapper That Lets You Run Your Store by Conversation," which covers the admin-facing assistant — the wrapper pattern, auto-generating tools from the OpenAPI schema, scoping the toolset by permission, and the REST gaps. This piece assumes that grounding and turns the same machinery around to face the shopper. If you have not read Part 1, the one idea you need from it is this: an MCP server is a thin, standardised wrapper over an AI platform and the API your store already exposes, and a language model gains reach through it, not new powers.

The admin assistant in Part 1 assumed an administrator on a trusted machine, holding an integration token scoped by ACL. Change two things — the credential and the API surface — and the identical MCP-over-API pattern becomes a different product entirely: a storefront chatbot that answers a shopper in their own words and acts only on their own account. Nothing else about the approach changes. That is the single most useful idea to carry across: scope is the product. The wrapper is the same; the token and the surface decide whether you have built a back-office power tool or a customer-facing assistant.

It is also the form most people picture when they say "an AI chatbot for my Magento store" — and, as it happens, the form with the sharpest security edges, because it is public by definition. So this piece leads with the two questions that actually decide whether such a bot is safe to ship: how you generate its tools, and how you make absolutely sure each shopper only ever touches their own data.


The customer scope shrinks the blast radius

An admin integration token carries an ACL grant you have to design carefully. A customer token carries almost nothing, by design — and that is the point. You obtain one with generateCustomerToken(email, password) in GraphQL, or POST /rest/V1/integration/customer/token in REST, and Magento restricts whatever holds it to resources marked self or anonymous: that one shopper’s cart, orders, addresses, and account, plus anonymous catalogue browsing. There is no scoping discipline to get right, because the platform enforces it. A customer-scoped assistant cannot read another shopper’s data or reach an admin function, however it is prompted. The confused-deputy problem that haunts admin tooling largely evaporates here, because the deputy’s privileges simply are the customer’s, and nothing more.

That is the baseline: a genuinely reassuring property to start from — but it holds only as long as the right customer’s token reaches the right session, which is the harder half of this article, below.


The surface is GraphQL, not REST

Part 1 catalogued the REST gaps, and several of the most important — wishlist, reviews, cart, customer account — are exactly the features a shopper assistant needs. In core, they live in GraphQL rather than REST, and that is not an accident. GraphQL is Magento’s storefront-designed API: a single endpoint, field selection so a client fetches only what it needs, and the mutations REST never grew. Magento_WishlistGraphQl has been core since 2.4.0; customerCart, addProductsToCart, placeOrder, and createProductReview are all there.

Wrapping GraphQL in MCP is the same exercise as wrapping REST — each query or mutation becomes a tool — and a customer bearer token authorises both surfaces identically, so a bot can mix a GraphQL customerCart query with a REST order-status read behind one toolset if it needs to. But for a storefront assistant, GraphQL is the natural home, and that has a pleasant consequence: there is a clean way to generate the tools.


Generating the GraphQL tools with Apollo MCP Server

Just as the REST surface auto-generates from the OpenAPI schema in Part 1, the GraphQL surface auto-generates too, and the notable tool is Apollo MCP Server (open source). Point it at your GraphQL schema and a set of operations, and each operation becomes an MCP tool with a typed input schema taken from the operation’s variables. It speaks Streamable HTTP, and it executes against your existing endpoint while forwarding the request’s authentication headers untouched — which, as the next section explains, is the hook the per-customer token hangs on.

Here is a working example configuration pointing at a schema, a directory of operations, and your storefront GraphQL endpoint:

# apollo-mcp-server.yaml
transport:
  type: streamable_http
endpoint: https://store.example.com/graphql
schema:
  source: local
  path: ./magento-storefront.graphql      # exported from staging — see below
operations:
  source: local
  paths:
    - ./operations/                        # one .graphql file → one MCP tool

Each operation is a vetted, named query or mutation. Note what is absent from this one — there is no customer identifier anywhere; the storefront schema resolves "me" from the token:

# operations/GetMyOrders.graphql
query GetMyOrders($pageSize: Int = 5) {
  customer {
    orders(pageSize: $pageSize) {
      items { number status order_date total { grand_total { value currency } } }
    }
  }
}

Apollo MCP Server offers the same curated-versus-freeform choice Part 1 kept returning to, and the recommendation is the same. Three modes:

  • Pre-defined operations — you write .graphql files (GetMyOrders.graphql, AddToCart.graphql), and each becomes one tool whose variables are all the model can fill. Predictable, vettable, the right default — and the only sane choice for mutations.
  • Persisted query manifests — if you run Apollo GraphOS, an approved operation list is safelisted automatically.
  • Introspection — the server exposes generic introspect / search / execute tools and lets the model author arbitrary GraphQL at runtime. Flexible for read exploration, but it is the GraphQL twin of the free-form searchCriteria problem from Part 1: the model can construct queries you never intended, so it wants guardrails (Apollo Router’s demand-control cost limits, read-only scope) and should never be given write access.

Apollo’s own recommended pattern maps cleanly onto the guardrails in Part 1: curated operations for mutations, introspection (if any) for reads only. Pre-define the handful of storefront actions a chatbot needs — view orders, manage cart, manage wishlist — as vetted .graphql operations and you get a tight, predictable toolset with no freeform query authoring anywhere near a write.

One Magento-specific wrinkle decides where you run the generation: GraphQL introspection is disabled in production application mode by default, a deliberate hardening. So the introspection-driven path will not work against a live store as-is, and re-enabling it in production is the wrong fix. Export the schema from a developer or staging instance, write your operations against that, and point the deployed MCP server at the production GraphQL endpoint with those curated operations. The schema is the same; only introspection visibility differs.


Binding the right customer to the right session

This is the part that actually decides whether the bot is safe. The hardest real question in a multi-user chatbot is not which tools to expose — it is making sure each shopper’s tool calls run as that shopper and nobody else. Get it wrong and you have built an IDOR generator. The rule is short: the customer identity is bound at the host/application layer from an authenticated session — never carried in the MCP server’s static config, and never a parameter the model fills in.

Concretely, for a storefront bot serving many shoppers at once:

1. The shopper logs into the storefront as normal. Your storefront or app backend obtains a customer token for them — generateCustomerToken(email, password) in GraphQL, or POST /rest/V1/integration/customer/tokenat login. The chatbot never collects credentials; it inherits an already-authenticated session.

2. The chat widget calls your application backend, which is the MCP host, carrying that shopper’s session. Your backend authenticates the request the same way the rest of your storefront does.

3. Your backend injects this shopper’s customer token as the bearer on the MCP client session, so every tool call the server forwards to Magento carries Authorization: Bearer <that customer's token>. Because Apollo MCP Server forwards the incoming auth header, the token is the only thing that decides whose data is touched.

4. Magento resolves the customer from the token and enforces self scope. The storefront API is built around implicit self — customer { orders }, customerCart, /V1/carts/mine — so there is no customer ID to pass, and therefore none for the model to get wrong.

That last point is the whole safety argument: the model never names whose data to fetch. It cannot, because the storefront API has no "fetch customer X" shape — identity lives entirely in the token, which the host set from the session out-of-band. If you ever find yourself adding a customer_id or email tool parameter, stop: you have just handed the model — and anyone who can prompt-inject it — the ability to request other people’s data.

Two corollaries follow:

  • stdio with a static token in env vars is single-customer only. It is fine for a personal assistant bound to one account, useless for a public bot. A multi-user chatbot needs Streamable HTTP with per-session auth, which is exactly what MCP’s OAuth 2.1 and session-binding direction exists to support.
  • Don’t blindly pass through a token the server minted for someone else. The MCP spec forbids naive token passthrough precisely because it creates confused-deputy holes. Bind each session to its user (user_id:session_id) and validate on every request that the token belongs to the current requester.

For a guest shopper there is no customer token at all: the bot uses anonymous catalogue browsing plus a guest cart, where the masked quote ID — not an identity — is the binding for cart operations. The same principle holds, that the model never supplies the identifier; it comes from the session the host controls.


What a shopper actually asks

With the scope, the surface, and the binding settled, the use cases are simply the shopper’s own sentences:

  • "Where’s my order?" — an order-status read on their own orders, by number or just "the last one."
  • "Add the blue one to my cart," "remove the duplicate," "change the quantity to two" — cart mutations on customerCart.
  • "What’s on my wishlist?" / "move my wishlist into the cart" — a wishlist query and mutation, the feature REST never had.
  • "Find me something similar under £40" — anonymous catalogue search, the shopper’s intent resolved against the product they are looking at.
  • "Reorder what I bought last month" — read the order history, re-add the items to the cart, and hand back to checkout.

Each is the shopper describing intent and the assistant resolving it against their own account. This is the most literal reading of the thesis from Part 1 — the interface is the customer’s own sentences.


The honest part: a public bot inverts the trust model

That is the core tradeoff: the admin assistant in Part 1 ran for one trusted operator on one machine. A customer chatbot is the opposite on every axis: public, multi-user, and talking to strangers as its whole job. The honest framing therefore turns sharper here, not softer. Three consequences:

  • Prompt injection is the default condition, not an edge case. Every input arrives from a stranger, and the catalogue, reviews, and product copy the bot reads back are themselves attackable surfaces. Treat all of it as untrusted data; never let retrieved content act as instructions. A public bot that can take actions on a shopper’s account is precisely the toxic combination — tools that act, plus untrusted input — that injection exists to exploit.
  • Keep it read-mostly, and never let it complete irreversible or payment steps unattended. Answering "where’s my order" is safe; silently placing an order, applying store credit, or changing a shipping address is not. Anything that moves money or commits the customer belongs back in the real storefront checkout with explicit confirmation, not executed by the model on a sentence. This is why curated-operations-for-mutations is not just a tidiness preference — it is the lever that keeps writes few, named, and reviewable.
  • Don’t let the bot hold customer credentials, and throttle it. It operates on the logged-in shopper’s own short-lived token, scoped to their session — never a service account that can impersonate. Rate-limit per session, and log injection-shaped inputs the same way the admin server in Part 1 logs writes.

The unifying idea is the permissions discipline from Part 1 stated at full strength: scope is the product. The same MCP-over-API wrapper becomes a powerful internal admin assistant or a narrow public chatbot depending entirely on which token it holds and which surface it wraps. Choose that scope deliberately — it is the single decision that fixes both what the assistant can do and what it can be tricked into doing. If you are building for store managers rather than shoppers, Part 2 covers the business workflows — content management, promotions, merchandising — the same toolset unlocks for a non-developer audience.


The cheatsheet

Admin assistant vs customer chatbot — the two things that change:

Admin assistant (Part 1)Customer chatbot (here)
CredentialIntegration token, ACL-scopedCustomer token, self/anonymous only
SurfaceREST / service contractsGraphQL (storefront)
Transportstdio, local, single operatorStreamable HTTP, per-session auth, many users
Tool generationOpenAPI schema → FastMCP / openapi-mcp-generatorGraphQL ops → Apollo MCP Server
IdentityThe operator (trusted)The shopper (untrusted, public)

Token & binding:

  • Get the customer token via GraphQL generateCustomerToken or POST /rest/V1/integration/customer/token.
  • Bind it per session at the host layer from the shopper’s authenticated session. Never in static config; never a model-supplied customer_id/email (that is an IDOR hole).
  • The storefront API is implicit-self (customer { orders }, customerCart, carts/mine) — the model never names whose data to fetch.
  • Multi-user needs Streamable HTTP + per-session bearer. No naive token passthrough; bind user_id:session_id and validate every request. Guests use a masked guest-cart ID, not an identity.

Generating tools (Apollo MCP Server):

  • Curated .graphql operations → one tool each (mandatory for mutations).
  • Persisted query manifests (GraphOS) → auto-safelisted.
  • Introspection (introspect/search/execute) → reads only, with demand-control cost limits; never near writes.
  • Magento disables GraphQL introspection in production → export the schema from staging, run against production.

Security verification checklist (public bot):

1. Treat every input and all retrieved content as untrusted — never let it act as instructions.

2. Read-mostly; payment/irreversible actions go back to the real checkout with confirmation.

3. Short-lived per-session token, no stored credentials, rate-limit per session, log injection-shaped inputs.

4. self scope is the floor; curated operations and read-only introspection are the polish on top.

← Previous
Next →

Written in collaboration with AI (Claude, by Anthropic). Ideas, verification, and accountability are mine; research and drafting are AI-assisted. Full disclosure → · Found an error? Tell me.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *