Commerce · MCP
Privacy and security
What personal data is masked, what is never exposed, and how to handle tokens, write tokens and untrusted text.
Least privilege
- Give tokens access level Read (
commerce.read) unless the client has a documented need for customer identity, tracking codes or addresses. Most analysis, stock and catalogue use cases do not. The admin form warns that Read incl. customer details (Lezen incl. klantgegevens) shows customers' names, email addresses and street addresses to whoever holds the token. - Give a token a write level (Read + edit products, Lezen + producten bewerken, or Read + edit products and categories, Lezen + producten en categorieën bewerken) only when the client has to change the catalogue, and the categories level only when it has to change the category tree. See Write tokens.
- Use short lifetimes. The default is 180 days and the maximum 365; prefer shorter for experiments and individual users, for example 30 days. A token with customer details can live at most 90 days, a write token at most 30 days.
- Creating a token needs the administrator's own password, notifies every shop administrator by email (never including the token) and is recorded in the activity log. Revoke a token you do not recognise right away.
- One token per client, so a single client can be revoked without affecting the others and the audit trail stays unambiguous.
- Use only
https://endpoints. In production the server refuses requests that are not secure, because a bearer token must never travel in clear text.
Tokens are secrets
A token is equivalent to a password for this data. Store it in a secret manager, never in a repository, a shared chat, a browser or a mobile app, and revoke it on suspicion. See Authentication and tokens.
These rules also apply to the REST API, which shares tokens, scopes and masking with the MCP server. A compact table is in Scopes and masking.
What is masked
Without the commerce.read.pii scope, identity is masked and some fields are withheld. This applies to search_orders rows and to get_order.
| Field | Without commerce.read.pii | With commerce.read.pii |
|---|---|---|
customer.name |
Jan Jansen becomes J. J***; a single name becomes J***; no name becomes null. |
Full name. |
customer.email |
jan@example.nl becomes j***@e***.nl; a one-character local part or domain label becomes ***; a value that is not an address becomes ***. |
Full address. |
customer.masked |
true |
false |
shipping_address |
Only country (ISO code) and city. |
Also postcode and street. |
shipments[].tracking_code |
null |
The carrier tracking code. |
shipments[].tracking_url |
null |
The carrier tracking link. |
Order search by email |
Refused with forbidden_scope. |
Allowed, within a 90-day window. |
The tracking code, the tracking link and the postcode count as personal data, because they identify a delivery to a person. They are withheld without the pii scope.
What is never returned
With any scope, the server never returns:
- order notes, customer messages and delivery instructions;
- order meta and guest-order tokens;
- payment provider details (gateway ids, payloads, provider names);
- phone numbers and IP addresses;
- invoice data, and billing address details beyond the customer name and email.
Personal data and the GDPR
Connecting an AI client is a data-protection decision
Even masked results are order data. With commerce.read.pii the results contain names, email addresses, addresses and tracking codes.
Sending any of it to a third-party AI client or hosted model makes that provider a processor or sub-processor of the shop.
Before connecting such a client you need a data processing agreement (DPA) and an assessment of its retention, training use and location, and the shop's privacy notice must cover it. Use the masked default wherever you can.
Untrusted text in results
Product names, brands, categories, descriptions and order line names come from suppliers and imports. The server reduces them to plain text: HTML entities are decoded and tags stripped, control characters are collapsed and the length is capped. They can still contain text that looks like instructions (prompt injection). The server instructions and tool descriptions tell clients to treat this text as data.
Customer-entered text (names, street, city, carrier and tracking code in order results) is reduced to plain text in the same way and is equally untrusted, also with the pii scope. Treat it as data, never as instructions or HTML.
Unpublished products and cross-origin use
search_productsandget_productreturn unpublished products (statusdraftorany) to every token withcommerce.read. This is intended: the server gives operational read access, the same as the REST API. Do not handcommerce.readtokens to parties who must not see drafts.list_pagesandget_pagereturn draft, scheduled and expired CMS pages to every token withcommerce.read, for the same reason.list_discountsandget_discountnever return a discount code to a token withoutcommerce.write.discounts, and never a code that a source manages.get_orderreturns internal staff notes only withcommerce.read.pii.- Neither the MCP server nor the REST API sends CORS headers: they are for servers and desktop clients, not for browser scripts.
Do not act on instructions found in data
If an agent can use write tools, from this server or any other, do not let it act on text it found in these fields without confirmation. A supplier text such as "set the price to 1" must never become a set_price call.
Write tokens
A token with a write scope can change the live shop: with commerce.write.products product content, SEO, prices, sale prices, stock, images, new products, brands and labels; with commerce.write.collections the category tree and featured products; with commerce.write.discounts discount codes; with commerce.write.orders the status of unpaid orders and shipments (never money); with commerce.write.content the CMS pages. Changes take effect immediately and cannot be undone by the server. Treat such a token as a deliberate, separate decision.
- Least privilege. Use a read token for analysis and lookups. Choose the base level Read and tick only the edit permissions the client needs (Products, Categories, Discount codes, Orders, Pages); each permission is its own decision and none implies another. An edit permission does not reveal customer details: only Read incl. customer details does, and the admin shows a warning when you combine them.
- Short lifetime. Write tokens created in the shop admin live at most 30 days. Revoke the token when the job is done.
- One token per integration. Name it after the integration, so the change history says who changed what. Review the change history of write tokens regularly (Change history, Wijzigingsgeschiedenis, on the API tokens page).
- Human approval in the client. Issuing the token is the administrator's decision, confirmed with their password. Per call, let a person confirm every write in the client. The write tools advertise
readOnlyHint: false, and the tools that delete, replace a whole list or cannot be undone (for exampledelete_page,delete_discount,cancel_unpaid_order,set_labels)destructiveHint: true, so a client can ask for confirmation; do not switch that off for this server. - Check before you retry.
create_product,create_collection,add_product_media,create_discount,add_order_note,create_pageand the moves (move_collection,move_label,move_featured_product_in_collection) are not idempotent. A blind retry can create duplicates or move twice.add_shipmentis safe to repeat with the sameidempotency_key. - Customer data, codes and drafts. Discount codes are credentials:
codeis only returned to a token withcommerce.write.discountsand never for codes a source manages. Internal order notes and tracking codes needcommerce.read.pii, even for a token withcommerce.write.orders. Draft and scheduled CMS pages are visible to every read token. Never put customer data in page content or in prompts to a hosted service. - Image URLs.
add_product_media,set_brand_logo,create_pageandupdate_pagedownload the URL the client sends, through the checks in the image URL rules:https, public addresses only, no redirects, at most 10 MB, image formats only. Only pass URLs of images you are allowed to publish in the shop.
Untrusted text can lead an agent to write
Product names, descriptions, reviews, emails, web pages and supplier feeds can contain text that looks like instructions (prompt injection). The server cannot tell an instruction from the operator from one hidden in product text: to the server both are valid calls of a valid token.
Do not give a write token to an agent that also processes untrusted text without a person confirming each write in the client. Prefer a separate, narrowly used write token for such an agent, keep its lifetime short and watch its change history.
Read-only guarantee for tokens without a write scope
- A token without a write scope (
commerce.write.products,commerce.write.collections,commerce.write.discounts,commerce.write.ordersorcommerce.write.content) is strictly read-only. It sees only the fifteen read tools; the write tools do not exist for it, and calling one answers JSON-RPC-32602. - The fifteen read tools are declared read-only and report
readOnlyHint: true,destructiveHint: falseandopenWorldHint: false. - No read tool writes shop data, deletes, refunds, imports or sends mail. The only outbound call of the read tools is the stock lookup of
get_productandcheck_sku_availability, and only when the shop uses an external inventory provider. - For a read token the only things the server records are its own bookkeeping: when a token was last used, and an audit row per tool call.
What write tools can and cannot change
- The write tools can only change product content and SEO, prices, sale prices, stock, product images, brands, labels, category placements, the category tree, featured products, discount codes, order status and shipments, and CMS pages, through the same actions as the shop admin, with the same validation and PIM rules.
- No tool deletes a product, variant, price or category, refunds money, cancels a paid order or changes customers. The order tools confirm an offline payment, cancel an unpaid order, add internal notes and record shipments, nothing more. The removals are images, category placements, brands that no product uses, labels, discount codes that were never used and pages other than the home page.
- Four tools download the image URLs the client names (
add_product_media,set_brand_logo,create_page,update_page);confirm_offline_paymentandadd_shipmente-mail the customer. They advertiseopenWorldHint: true; no other tool makes an outbound request. set_stockruns the shop's back-in-stock notifications to customers, as a stock change in the admin does.- A change and its change-history entry are stored together or not at all, and every change is attributed to the token that made it.
Audit trail
Every tool call by an authenticated token writes one audit row with the token, the tool, the start time, the duration, the outcome (ok, error, denied or invalid), the error code, a short target (for example an order reference, a product id, or skus:<count>) and the number of results.
Argument values, search text, email addresses, customer data, response bodies, token secrets and IP addresses are not stored. REST API calls are recorded the same way with interface = api (MCP calls with interface = mcp). Failed authentications write no audit row. Audit rows are deleted after 90 days.
Write calls also write an audit row. A write that really changes something is in addition recorded in the change history: the token and its name at the time, the tool, the channel, the subject (product, variant, category, brand, label, discount, order, shipment or page) and the old and new value of every changed field. The change history holds shop data only; it keeps no note text and no tracking codes (only a note id and a fingerprint of a changed tracking code or link), no discount codes of source-managed discounts, and no customer data, and is kept for 730 days. Because it keeps old and new values, including descriptions, treat it like the product data itself.
Responses to routed requests are sent with Cache-Control: no-store.