Description
Connect a coding agent to commercetools Projects through managed Commerce MCP Servers with Merchant Center sign-in, configured per repository, and design focused Commerce MCP Servers for other agents: minimal tools, filtered responses, task-specific descriptions, validated with blind agent runs. Points to the documentation for the Knowledge MCP. Use when connecting a coding agent to a commercetools Project, continuing that setup after sign-in, or giving an agent only the Commerce MCP tools and data its task needs.
Installation
In any Claude Code session, run the following commands one at a time:
-
Add the commercetools AI marketplace:
/plugin marketplace add commercetools/commercetools-ai-plugins -
Install the commercetools AI plugin (Skills + Subagents + MCP):
/plugin install commercetools@commercetools
If you've updated the plugin or installed it in another window and need the current session to pick up the latest version:
/reload-plugins
commercetools/commercetools-ai-plugins. Then, click on the plugin and click Install.Instructions Included
commercetools MCP
| Server | Gives an agent | Access |
|---|---|---|
| Knowledge MCP | Documentation search, GraphQL and OpenAPI schemas, query validation | Public, no sign-in. Bundled with this plugin. |
| Commerce MCP | Tools that read and change Project data | Needs access to a Project. Managed (hosted by commercetools, configured in the Merchant Center) or self-hosted (you run the open-source package). |
Connect your development environment
Configure an MCP server for an agent
Other uses
Answer other Commerce MCP and Knowledge MCP questions from the documentation:
Checklist
- Development-environment connections follow the repository connection
- MCP servers for other agents follow the agent MCP server workflow, including its blind runs
- Other Commerce MCP and Knowledge MCP questions answered from the linked documentation
References
Configure a focused Commerce MCP
Two config targets, same underlying tools (snake_case) and filtering engine:
- Managed MCP Server (preferred) — hosted by commercetools; the agent's credential is scoped to only this server, so a leak can't reach beyond its curated tools and filtered fields. Supports per-tool description overrides and parameter constraints. API reference, overview.
- Self-hosted
@commercetools/commerce-mcp(fallback) — CLI flags /mcp.json; tool curation and field filtering only.
Ground rules
You do the legwork, the user supplies intent and confirms fit. These four rules govern every step below and keep the skill from producing a confident, wrong config.
- The task comes from the user — don't invent it. Ask an open question and let them describe the agent's job in their own words. Never present a multiple-choice menu of use cases ("Read/analyze catalog", "Customer support", …) — invented options are still inventing the task, and push the user into a box that may not fit. If the answer is vague, ask open follow-ups until you can name the concrete workflow steps.
- Work on the project the user points you at — don't go hunting. Confirm the project (region + key)
and read it through a Commerce MCP they have connected. Never search the disk,
.envfiles, or other projects for a project key or credentials — a stray credential is almost never the one they meant, and creating a server in the wrong project is hard to undo. - Ground every choice in real data; never guess. Which tools are correct, what to filter, and how to describe tools all depend on how this project models its data. If you can't inspect it, get the data from the user — never fall back to a generic config built from assumptions.
- Propose, then confirm. Show your proposal and reasoning for the choices that decide fit — tools, dropped/redacted fields, descriptions — and adjust before finalizing or creating anything. They know the business; you know the data.
Workflow
Always follow these steps:
-
Docs search (required, run first) — The first time you use this skill in a session you must run this before answering. It gathers the latest verified documentation as your primary grounding, filtered to the products this skill covers. Use this script for documentation search while working with this skill; the Knowledge MCP covers everything else. Always confirm details against retrieved documentation rather than the skill text alone:
node "<this skill's directory>/scripts/docs-search.mjs" \ --query "<extract key terms — the resources/operations the agent needs>" \ --app-name "<host app: claude-code, claude-chat, cursor, codex, copilot — or the host's own name>" \ --model "<current-model>" \ --commercetools-project-key "<if known>" \ --commercetools-region "<if known>" \ --limit 10Pass the commercetools project key and region (as inapi.{region}.commercetools.com) only if already in your context; otherwise omit both. Never search files or ask the user for them. -
Establish the task, its callers, and the target project — Per ground rules 1 and 2. Also ask who or what invokes this agent, and how far they're trusted — it shapes the tool set as much as the task does (select-tools.md).Then make sure a Commerce MCP is connected to that project — every later step reads or writes through it. If none is, or it lacks the MCP-server tools, walk the user through the setup now, in one go → connect-commerce-mcp.md.
-
Choose the variant — Default to Managed and recommend it, including for local development. Pick self-hosted only for the two reasons in choose-variant.md.
-
Discover the available tools — Pull the live inventory; never design against a memorized list → discover-tools.md.
-
Inspect the project's real data (required) — The step that makes the config correct, per ground rule 3 → inspect-project-data.md.
-
Curate the minimal tool set — Least privilege, grounded in step 5 → select-tools.md.
-
Strip and sharpen —
- Field filtering (both variants): drop noise, redact sensitive fields → field-filtering.md.
- Tool customizations (Managed only): rewrite descriptions for the task, pin/constrain parameters → tool-customizations.md.
-
Emit / apply the config —
- Managed: assemble the draft, create/update the server with the Commerce MCP's
create_mcp_servers/update_mcp_serverstools, and tell the user how to create the agent's runtime client → managed-server-api.md. - Self-hosted: emit the
mcp.json/ CLI config → choose-variant.md.
- Managed: assemble the draft, create/update the server with the Commerce MCP's
-
Test with blind agent runs, then iterate (required) — A config drafted from data inspection is a first guess. Validate it by having a fresh agent with no design context attempt the task using only this server; feed the findings back into steps 6–8 and re-run until clean → test-and-iterate.md.
Optional scripts
node "<this skill's directory>/scripts/openApi-schemata.mjs" \
--resource-name "<e.g. api-Product-read, api-Customer-read, api-CartDiscount-write>" \
--app-name "<host app: claude-code, claude-chat, cursor, codex, copilot — or the host's own name>" \
--model "<current-model>"
Output
Close with a short report — the user needs to know what now exists and what is still on them:
## Managed MCP Server: <key>
- **URL:** <mcpServer.url> · **State:** Enabled · **Auth:** ClientCredentials | CommercetoolsIdentity
- **Tools (N):** <list> — <one line on the scope this covers>
- **Filtering:** <what's dropped/redacted, and why> · **Formatting:** <if set>
- **Customizations (N):** <tool> — <one-line reason each>
- **Blind runs:** <scenarios run, what they hit, what changed as a result>
- **Over to you:** create the runtime client at <Merchant Center link to the server's new-API-client form>,
connect it, export the token before launching the client. Token expires <date> — plan the refresh.
mcp.json and the client scopes it needs instead.Worked example
Checklist
Before shipping a config:
- Task came from an open question to the user; tools, filters, and descriptions were confirmed with them.
- Project (region + key) confirmed and inspected through a Commerce MCP — no credentials scavenged from disk or pasted into the chat.
- Design-time connection signed in through the Merchant Center (no API client); on production, widened by the two management tools at most.
- Tool selection, filtering, and descriptions came from real project data, not assumptions.
- Least privilege: only the tools the task needs, read-only unless it must write, no broad
allgroup in production. - Who invokes the agent — and how much they're trusted — was asked and factored into the tool set.
- Tool names and parameters verified against the live catalog, and matched to how this project models its data.
- Responses trimmed to what the task reads; PII exposed only where the task needs it; list-heavy tools formatted or paginated.
- Parameters the agent shouldn't choose are pinned (Managed
const/ self-hosted context flags) — e.g. store key,staged. - Descriptions (Managed) are task- and project-specific, and were written from the default text rather than replacing it blind — the agent never sees the original.
-
state: "Enabled"— new servers default toDisabled.authenticationModewas set explicitly, not defaulted into. -
create_mcp_servers/update_mcp_serversare not on the agent's own server — they let it widen its own surface. - Blind runs passed and the config was iterated on their findings, not shipped as the first draft.
Choose the variant: Managed vs self-hosted
@commercetools/processors). They differ in hosting, configuration surface, and a few
capabilities.Flags, config field names, and per-variant capabilities change over time. Treat the specifics below as orientation and read the current docs before finalizing: Commerce MCP docs and the@commercetools/commerce-mcpREADME for self-hosted; managed-server-api.md for Managed.
Version alignment: the managed Commerce MCPv4tool set matches self-hosted Commerce MCP4.0.0and up. This skill assumes that shared snake_case tool set — for self-hosted, use4.0.0+; earlier releases expose a different, older tool surface.
Default to Managed
view_products), so a leak grants direct API access that bypasses your curation and
filtering entirely — the confused-deputy problem.commerce-mcp" (a broad dev server is a different thing from a locked-down, task-focused one —
don't reuse it as the agent's surface).Capability differences (verify against current docs)
| Lever | Self-hosted | Managed | Configured via |
|---|---|---|---|
| Curate the tool set | Yes | Yes | self-hosted: tools flag · Managed: tools (select-tools.md) |
| Filter / redact response fields | Yes | Yes | self-hosted: field-filtering flag (stringified FieldFilteringManagerConfig) · Managed: jsonOutputFiltering (field-filtering.md) |
| Compact output formatting | Yes | Yes | self-hosted: output-format flag · Managed: toolOutputFormatting |
| Pin operating context (store, etc.) | Yes | Yes | self-hosted: store/customer/cart/business-unit/admin flags · Managed: parameter const |
| Override individual tool descriptions | No, at time of writing | Yes | Managed: toolCustomizations (tool-customizations.md) |
Constrain individual tool parameters (const/default/allowedTypes) | No, at time of writing | Yes | Managed: toolCustomizations |
Tool curation and field filtering exist in both, so a focused config is achievable either way. Look flag names up live rather than memorizing them, and confirm the per-tool reshaping gap against current self-hosted docs — the feature set moves.
Self-hosted registration shape
mcp.json roughly
like this — fill in the current flags from the docs, don't copy a fixed list:{
"mcpServers": {
"commerce-mcp": {
"command": "npx",
"args": [
"-y", "@commercetools/commerce-mcp",
// tools, auth (project key + client credentials or access token), field filtering,
// output format, context pinning, dynamic tool-loading threshold, remote/stateless, etc.
// → see the Commerce MCP docs for the current flags and their defaults.
]
}
}
}
Keep credentials in env vars / a secrets manager — never commit them. (The credential-free commercetools Knowledge MCP is safe to commit; the Commerce MCP, which carries project credentials, is not.)
Connect a Commerce MCP for design work
create_mcp_servers and update_mcp_servers. This is the design-time connection —
separate from the server you're building for the agent.Use the repository connection
read_project succeeds, continue the
agent-server workflow here instead of offering another project.What the design work needs on top:
- The management tools. A development project's connection uses the All tool group, which already
includes
create_mcp_serversandupdate_mcp_servers. A production project's uses All read, which doesn't — see production projects. - Permission to manage MCP servers. The tools a signed-in user sees are capped by their Merchant Center permissions; without the Managed MCP Servers permission, the management tools stay hidden even on an All server.
Verify before going further
read_projectreturns the confirmed project'skeyandname. A mismatch means the connection points at another project — stop.create_mcp_serversandupdate_mcp_serversare listed. If they aren't, either the server's tools don't include them (production) or the user's Merchant Center permissions don't allow managing MCP servers. Checktools/listrather than guessing which.
Production projects
- Add only the two management tools to that server in the Merchant Center (the server's tool selection), keeping everything else read-only. Data stays unwritable; the session can create and update MCP server configs.
- Design on development, recreate on production. Build and test the config against a development project, then create the final server on production in the Merchant Center from the finished draft. Only possible if the development project models its data the same way — check, don't assume (inspect-project-data.md).
Keep the management tools off the agent's server
create_mcp_servers or update_mcp_servers to the server you're building for the agent: a tool
that can rewrite MCP server configs lets its caller widen its own surface, undoing the least-privilege work
this skill exists for. They belong on the design-time connection only.If you can't make the call yourself
If the management tools aren't available and the user can't enable them, don't stall or fake it: produce the finished server draft, ask the user to create the server in the Merchant Center from it, and continue from what they report back. Never invent a result you didn't get.
Discover the available tools (don't assume a list)
(type, majorVersion). Never design a config against a memorized list. Pull the current inventory and
reason from it. Any tool names in this skill (e.g. read_products) are illustrations, not an authoritative
catalog.The authoritative catalog
read_mcp_server_types with type: "CommerceMCP" on the connected Commerce MCP. Add majorVersion
(e.g. v4) to pin a version; omit it to get the latest.{ type, majorVersion, tools[] }, where each tool carries:| Field | Use it for |
|---|---|
name | The exact snake_case value to put in tools — never hand-type these |
description | The current default text, i.e. your starting point for an override (tool-customizations.md) |
operationType | read or write — the fastest least-privilege filter (select-tools.md) |
groups | Which groups (all, read_all) the tool belongs to |
type lists every server type. This is also the source of truth for which majorVersion values
exist — as of writing v1 (deprecated) and v4, but check rather than trust that.const / default / allowedTypes overrides from it alone. For parameter shapes use
a live tools/list against a connected server, or openApi-schemata.mjs for the underlying resource.read_mcp_server_types isn't listed (e.g. filtered by the signed-in user's permissions), get the inventory from
tools/list on the broad Commerce MCP the user already has connected, and confirm against the catalog
before creating the server (connect-commerce-mcp.md). The
Available Tools docs
section is a browsable third option, but it is prose and can lag the API.Don't infer names by crossing verbs with resources
{create,read,update,delete}_{resource} is the wrong mental model. Real
v4 examples:- There are no
delete_*tools at all. Deletion is not on the surface. If the agent's task implies cleanup or removal, say so early: it needs a different path (direct REST, a separate process, or a rethink of the task), not a tool you can add. - Read-only, no write counterpart:
read_messages,read_product_projections,read_product_search,read_customer_search. update_payment_intentsexists with no matching read or create.create_transactionsandread_transactionsexist, but there is no update.- Verbs beyond CRUD exist:
replicate_carts. create_bulk/update_bulkoperate across resource types and are their own thing.
A name that isn't in the catalog is rejected at write time, so a typo or an invented name fails the create call rather than silently doing nothing — but checking the catalog first is cheaper than guessing.
Naming is snake_caseverb_resource. The same tool set is shared with self-hosted Commerce MCP4.0.0+ (choose-variant.md), so names don't differ between the two variants.
Field filtering & output formatting (both variants)
@commercetools/processors engine; only the config
surface differs.Drop vs redact vs keep
- Keep any field the task actually uses — don't strip PII a customer-service or fulfilment agent needs (email, address, line items). Filtering is not a substitute for the right tools and least-privilege scopes (select-tools.md); that's where access is controlled.
- Drop (remove the field and everything nested under it) everything the task doesn't read, including sensitive fields it doesn't need. Biggest token saver and the one you'll reach for most: audit metadata, unused nested structures, large arrays.
- Redact (keep the key, replace the value) only where the agent must know a field exists — or
downstream code keys off its shape — but must not see the value. The API already returns true secrets
masked (a Customer
passwordcomes back as a hash), so redaction is mostly redundant for secrets on the current tool surface. Its realistic use is governance: keeping identifiable data out of the model's context or out of logs when the agent runs in a shared or third-party runtime.
Managed: jsonOutputFiltering
"jsonOutputFiltering": {
"redact": { // replace value, keep key
"fieldNames": ["password"], // exact field name at any depth (case-sensitive)
"fieldNameContains": ["secret"] // substring of a field name at any depth (case-INsensitive)
},
"drop": { // remove key + nested value
"fieldNames": ["vatId"],
"fieldPaths": ["addresses.phone"] // exact dotted path (case-sensitive)
},
"fieldPathsToKeep": ["custom.fields.loyaltyTier"], // exempt paths from redact/drop
"redactedWith": "[REDACTED]" // placeholder; defaults to "[REDACTED]" if omitted
}
redact and drop — supports all three):fieldPaths— exact dotted path from the root, case-sensitive.fieldNames— exact field name at any depth, case-sensitive.fieldNameContains— substring of a field name at any depth, case-insensitive (widest net; the safer bias for redaction).fieldPathsToKeep— exact paths that survive even if they would otherwise match aredact/droprule.
Self-hosted: --fieldFiltering
FieldFilteringManagerConfig. Same engine, flatter shape — rules carry their own
type (filter = drop, redact = mask):{
"jsonRedactionText": "[REDACTED]",
"paths": [{ "value": "addresses.phone", "type": "filter", "caseSensitive": true }],
"properties": [{ "value": "vatId", "type": "filter", "caseSensitive": true },
{ "value": "password", "type": "redact", "caseSensitive": true }],
"includes": [{ "value": "secret", "type": "redact", "caseSensitive": false }],
"whitelistPaths": [{ "value": "custom.fields.loyaltyTier", "caseSensitive": false }]
}
Managed jsonOutputFiltering | Self-hosted FieldFilteringManagerConfig |
|---|---|
drop | rules with type: "filter" |
redact | rules with type: "redact" |
fieldPaths | paths |
fieldNames | properties |
fieldNameContains | includes |
fieldPathsToKeep | whitelistPaths |
redactedWith | jsonRedactionText (URLs: urlRedactionText) |
fieldPaths/fieldNames to case-sensitive and fieldNameContains to case-insensitive;
self-hosted sets caseSensitive explicitly per rule.Worked filter (a customer read)
An analytics agent segments customers by loyalty tier and country — no use for contact details or tax id, so drop them and keep that PII out of the model's context entirely:
// Managed
"jsonOutputFiltering": {
"drop": { "fieldNames": ["email", "vatId"], "fieldPaths": ["addresses.phone"] }
}
Discover what to filter
openApi-schemata.mjs /
graphql-schemata.mjs.custom.fields.* and custom objects. Project-defined and opaque in the generic
schema, they may hold business-critical data or pure integration noise. Never blanket-drop custom
without reading real values first; use fieldPathsToKeep to spare the important ones from a broader rule.Confirm the filter with the user, especially custom fields and anything borderline-sensitive — they know which fields carry business meaning and whether the task needs a given piece of PII.
Output formatting
"toolOutputFormatting": "JsonToTabular"; self-hosted:
--toolOutputFormat=tabular. This renders array results as a table instead of repeated JSON objects.
Trim fields first, then format.Inspect the project's real data
Response shapes
openApi-schemata.mjs (invocation in the skill's Optional scripts section) rather
than guessing field names.tools/call response: call the tool once against a representative
project and read the actual payload — that shows you the concrete field names, nesting, and the volume of
data you'll want to trim.How the project uses the model
custom.fields.*, defined via Types) and custom objects are
project-defined and opaque in the generic schema. They may hold critical business data or pure integration
noise — you can only tell by reading real values.What to inspect
Use the MCP read tools themselves, or the API. Always load a few items before customizing any tool, plus related items that are linked (e.g. categories link to products and can be used to search or filter):
- Catalog structure — top-level categories; product types and their attribute definitions (keys and allowed values).
- Pricing model — embedded vs standalone prices; whether price channels / stores are in play.
- Custom data — the Types (custom-field definitions) on the resources in scope, plus a few real objects to see what those fields actually hold; and any custom objects relevant to the task.
Create / update a Managed MCP Server
read_mcp_server_types, read_mcp_servers, create_mcp_servers,
update_mcp_servers — on a Commerce MCP the user has connected, Managed or self-hosted
(connect-commerce-mcp.md). You make tool calls; no REST requests or token handling. The shapes below
are a working summary: the tool's own description and input schema (tools/list) are authoritative for
parameters, and the MCP Servers API reference
covers field semantics.1. Confirm the management tools are callable
tools/list on the connected Commerce MCP for create_mcp_servers and update_mcp_servers.read_project and show the user the project key and
name it returns, next to the project they confirmed in step 2 of the workflow. A repository can hold
connections to several projects, and the one you call decides where the write lands. A server created in
the wrong project is live there until someone notices, so a mismatch stops the write. If the tools are
missing, don't reach for raw credentials — see
connect-commerce-mcp.md.2. Inventory what already exists
read_mcp_servers (no arguments, or limit/offset to page) before you create anything. Don't assume a
clean slate — projects often carry servers from earlier exploration, and a key collision fails the
create call anyway. The endpoint takes no where predicate, so filter the list yourself; then
read_mcp_servers with key on anything that looks related, to see its real tool list and state. Signs
of leftover experimentation rather than deliberate configuration: no name/description, a broad all
tool list, several near-identical duplicates.Never repurpose or disable an existing server without asking the user.keyis immutable, so reusing one means overwriting the config a live agent may be connected to — and you can't tell from the config alone whether something is pointed at it. That includes the server you're working through.
3. Choose the authentication mode
authenticationMode decides what credential a client presents, and it changes who the config is for.| Mode | Credential | Fits | Tools exposed |
|---|---|---|---|
ClientCredentials (default) | API Client with scope mcp:{projectKey}:{key} | Autonomous agents, machine-to-machine | Every tool on the server |
CommercetoolsIdentity | The user's own Identity account (their Merchant Center login) | Human-in-the-loop assistants | Only configured tools the signed-in user's Merchant Center permissions allow |
What this changes in practice:
- Set it in the draft, deliberately. Omitting
authenticationModesilently yieldsClientCredentials. Change it later with thesetAuthenticationModeupdate action. CommercetoolsIdentityneeds no API Client at all — but the client must support OAuth 2.0 authorization-code discovery. A client that only takes a static bearer token cannot connect in this mode.- Each mode has its own authorization server. A token minted for one mode is rejected by a server configured for the other, which surfaces as an auth failure that looks nothing like a mode mismatch.
- Permission-based filtering applies per signed-in user and without reconfiguring the server; a user whose permissions allow none of the configured tools is rejected outright. So the effective surface is the intersection of your tool list and their permissions — your config sets the ceiling, not the floor.
ClientCredentialsworks for human-in-the-loop too, but then per-user filtering is your problem.
4. Create the server — create_mcp_servers
McpServerDraft goes under body:{
"body": {
"key": "sell-off-agent", // unique, IMMUTABLE, becomes part of the URL
"name": { "en": "Sell-off promotion agent" }, // optional LocalizedString
"authenticationMode": "ClientCredentials", // optional; defaults to ClientCredentials — see step 3
"state": "Enabled", // optional; DEFAULTS TO "Disabled"
"mcpServer": {
"type": "CommerceMCP",
"majorVersion": "v4", // check read_mcp_server_types for current versions
"tools": ["read_product_search", "read_products", "read_inventory", "create_product_discounts"],
"toolCustomizations": [ /* see tool-customizations.md */ ],
"jsonOutputFiltering": { /* see field-filtering.md */ },
"toolOutputFormatting": "JsonToTabular" // optional
}
}
}
id, version, and the computed mcpServer.url
(https://mcp.{region}.commercetools.com/projects/{projectKey}/{key}) — the endpoint the agent connects
to. Tool names and parameter overrides are validated against (type, majorVersion), so an invented name
or a bad override fails the call instead of silently doing nothing.New servers default tostate: "Disabled". Set"state": "Enabled"in the draft or send asetStateaction later; a disabled server rejects every tool request.
5. Update an existing server — update_mcp_servers
version with read_mcp_servers, then send the server's key (or id), that
version, and an ordered action list. A stale version returns ConcurrentModification — re-read and
retry rather than bumping the number blindly.{
"key": "sell-off-agent",
"version": 3,
"actions": [
{ "action": "setTools", "tools": ["read_product_search", "read_products", "create_product_discounts"] },
{ "action": "setJsonOutputFiltering", "jsonOutputFiltering": { "drop": { "fieldNames": ["lastModifiedBy"] } } },
{ "action": "setState", "state": "Enabled" }
]
}
version; use it for the next update.Available update actions
setTools(replace),addTool,removeTool— the tool set.setToolCustomizations(replace; send[]to clear),addToolCustomization,removeToolCustomization.setJsonOutputFiltering— the response filter; replaces it entirely.setToolOutputFormatting— output format.setState—Enabled/Disabled.setAuthenticationMode—ClientCredentials/CommercetoolsIdentity(step 3).setName,setDescription.
delete_mcp_servers; to take a server out of
service, setState it to Disabled — it keeps its config and can be re-enabled. If the user wants it gone,
they delete it on the server's page in the Merchant Center, which also revokes its API clients and tokens.key is immutable — there is no setKey. Pick one that describes the agent role
(catalog-read-agent, not instance-3).Hand off: the agent's runtime client
ClientCredentials server, the agent can't connect until a runtime client exists. You can't create
it — the Commerce MCP has no API client tools — so send the user straight to the form. (On a
CommercetoolsIdentity server there is no runtime client: the user signs in with their own account. Skip to
the client config below.)mcpServer.url gives the host, id the server:https://mc.{region}.commercetools.com/{projectKey}/settings/mcp-servers/{id}/overview/api-clients/new
mcpServer.url and replace its leading mcp. with mc. rather than assembling the region
yourself, and use the server's id — a UUID — not its key. There, Add API Client derives the scope
from the server, and the Merchant Center shows the credentials plus a ready-to-use command for a token; it
can also generate a bearer token directly
(details). Ask the user
to name the client after the agent that will use it.Three things trip everyone up:
- It uses ONE dedicated scope:
mcp:{projectKey}:{key}— not data scopes. Data access is gated by the server config (the tools you selected), not by the client's scopes: a client with data scopes but nomcp:scope gets a 403. Creating it from the server's page gets this right; a client created elsewhere usually doesn't. - The secret is shown once. The user stores it for the agent then — never in the chat with you.
- Plan the token refresh now. Token validity is up to 30 days (
2592000seconds). 30 days is a hard ceiling with no non-expiring option, so expiry is a scheduled event, not an edge case — decide who refreshes it and how before rollout, rather than finding out when a production agent starts failing auth. Refreshing means minting a new token from the same durable client credentials; the API Client itself doesn't need recreating.
Client config, without committing the token
.mcp.json, which is typically
git-tracked. Use an env var instead:{
"mcpServers": {
"<key>": {
"type": "http",
"url": "<mcpServer.url>",
"headers": { "Authorization": "Bearer ${CTP_MCP_TOKEN}" }
}
}
}
http-type entry, ${VAR} is resolved only from variables already exported in the
shell that launched the client. The client does not read .env itself, and unlike a stdio entry there's
no launch command that could source one. An unset variable passes through as the literal ${VAR} string, so
the failure shows up as an opaque auth error rather than a config error. Have the user export it (e.g.
set -a; . ./.env; set +a) before starting the client.Repository connection
Your coding agent reads and configures a Project while you build. Each developer signs in as themselves, so no secret sits in the repository.
Connect one Project first. Offer more once that one works.
Rules
- Create the server with the user's own Merchant Center sign-in, not an API client. The script in step 2 uses the session the user's sign-in stores, so no secret is created or pasted. The Merchant Center form is the fallback when the script can't run.
- Write the connection with the script (step 3), not by hand. It checks the URL against the
project, merges into
.mcp.jsonwithout touching other servers, and prints the next step. Don't use an agent's ownmcp addcommand either. - Relay the printed next step; don't run it. Sign-in needs the user's browser, and the tools
only load when this conversation is reopened in a new process, which you can't do. Reopening keeps
the conversation, so the setup continues where it stopped. For that step, don't offer
!-prefixed in-session commands instead. The one exception is the Merchant Center sign-in of step 2 (scripts/sign-in.mjs): it needs no reopened conversation, so you run it yourself in the background and relay its URL. - Tools follow the environment. Development gets All: letting the agent set up and configure the project is the point, and read-only would defeat it. Production gets All read, always. Other environments are the user's call.
- Production is not detectable. The Project resource has no environment flag, on REST or
GraphQL. Never infer it from configuration: implementations mirror settings across environments,
and misconfigured production projects are common. Ask for further projects (step 2), and treat
prod,productionorlivein the project key or name as production. The absence of a marker means nothing. - Don't research or search. Look for configuration only in the repository and the paths your agent's section names, never across the filesystem. Don't invent scoping (no launcher scripts, wrappers, aliases, or config directories inside the workspace), and never add test entries to the user's configuration to learn a format.
Steps
-
Check what exists. A
mcp.{region}.commercetools.comURL in the agent's MCP configuration is a connection. After sign-in, the reopened conversation continues here.-
Its tools are available: call
read_project. It proves the connection end to end and returns the Project's display name:Connected to{projectKey}:{project display name}.Connect another project?
Suggest setting that name on the MCP Server in the Merchant Center. For another project, continue with step 2.
-
Its tools aren't available: sign-in or approval is missing. Run the step 3 script again with the same URL. It changes nothing and prints the next step.
-
No connection: continue with step 2.
-
-
Create the server. Ask for the Project:
Paste the Merchant Center URL of the project you want to connect. Normally your development project.
https://mc.{region}.commercetools.com/{projectKey}/welcomeDon't have it? Log in at https://identity.commercetools.com/ and copy the URL from your browser.An API host plus project key works too. Extract region and project key, and check the region against the hosts list. If it isn't there, ask again rather than guessing.For every project after the first, also ask:
Which environment is{projectKey}: development, staging, production, or something else?Create the server with the script, run from this skill's root. Use--environment developmentfor a development project and--environment productionfor production or any other environment, which gives read-only tools:node scripts/create-managed-mcp-server.mjs --region "<region>" --project "<projectKey>" --environment "<development|production>"Without--confirmedit changes nothing and prints the plan. Show the plan to the user and wait for an explicit yes, because the next run creates a server in their Project. Then run the same command with--confirmed. It prints the MCP server URL: use it in step 3 and skip the form.- It exits 4 with
needs.ctxsignIn: there is no Merchant Center session, it expired, or it cannot call the API. Show the userneeds.command(scripts/sign-in.mjs) and say that it opens a Merchant Center sign-in and runsmc-scriptsthroughnpx, which can download it. Wait for an explicit yes. Then run it in the background and read its stderr. Show the user the sign-in URL it prints: they open it in a browser and finish there. This is the samemc-scripts login --mc-api-urlthe custom-application scripts use. It leaves a Merchant Center session token in~/.commercetools/mc-credentials.json, valid for about an hour, which the create script reads to call the Merchant Center API as the user. The script passes the project and the one scope that managing MCP servers needs (manage_mcp_servers), and no API client is involved. A session made by another login, for another project or without that scope is replaced rather than reused. The sign-in script ends the login it started when it finishes. A matching session finishes immediately and prints no URL. When it prints"ok": true, run the create script again. If the create script still reports a missing scope,needs.commandcarries--force. - The sign-in exits 3: the user didn't finish in time. Run it again for a new URL.
- A finished-login notice from the session, such as a background task completing, only means the sign-in ended. It is not approval to create the server: the plan still needs the user's explicit yes.
- It exits 1: usually the user's Merchant Center role can't manage MCP servers in that Project. Report the error and use the form below.
Fallback: send the user to the form:
Create the MCP server:https://mc.{region}.commercetools.com/{projectKey}/settings/mcp-servers/new- Authorization method: OAuth / Identity
- Server key:
coding-agent - Tools: Group select → All for a development project, All read for production
- After saving, make sure the server is enabled with the toggle on its detail page
Then paste the MCP server URL here.For production, or a key or name with a production marker, say it plainly:
{projectKey}is production. Use All read, so nothing can be changed there through the agent.Leave Name and Description empty. Step 1 fills in the name from a verified value. Field details: Create an MCP Server.Group selections pick up new tools automatically. That suits development, and All read stays read-only as they arrive. Tool selection guards against accidents. The security boundary is the signer's Merchant Center permissions; see tool filtering. - It exits 4 with
-
Write the connection. For Claude Code, VS Code with Copilot and GitHub Copilot CLI, run this from this skill's root, with the repository root as
--dir:node scripts/connect-repository-mcp.mjs --url "<MCP server URL>" --project "<projectKey>" --dir "<repository root>" --agent "<claude-code|vscode|copilot-cli>" --model "<current-model>"It names the connectionct-{projectKey}, which prefixes every tool call and approval prompt so the target Project stays visible. Relay its "Tell the user" block verbatim, then stop.An error is a hard stop: report it, and don't edit the file by hand. If the run itself is blocked, give the user the same command with the script's absolute path. Don't report the connection as working beforeread_projectsucceeds.For other agents, see their section.
Per agent
.mcp.json, written
by the script. The file uses the portable format all three read, so one committed file serves a
mixed team.Claude Code
--agent claude-code, also in desktop-hosted sessions: the script detects those itself.-
Terminal: the printed command signs in, then reopens this conversation with
claude --continue, which asks once to approve the server. -
Desktop app: sessions there can't sign in, but load the server without approval. The printed command only signs in from a terminal; the user then comes back to this conversation. Don't tell them to exit or close anything.
-
The sign-in page shows
invalid_clientor "Not found": Claude kept a stored sign-in forct-{projectKey}from a server that has since been deleted or recreated, and the auth server no longer knows it. Check that the server exists and is enabled, then have the user clear the stored sign-in in their own terminal and repeat the sign-in. The--settingsflag is needed because the server comes from.mcp.jsonand is not yet approved:claude --settings '{"enabledMcpjsonServers":["ct-{projectKey}"]}' mcp logout ct-{projectKey}The sign-in command needs an interactive terminal: it fails when run through the!prefix.
Visual Studio Code with Copilot
--agent vscode. There is no terminal step: in a trusted workspace, VS Code starts the server
when the next chat message is sent. MCP: List Servers in the Command Palette shows its state..vscode/mcp.json (top-level servers) instead of
.mcp.json, add the entry there by hand, as "ct-{projectKey}": { "type": "http", "url": "<url>" }.GitHub Copilot CLI
--agent copilot-cli. The printed step reopens this conversation with copilot --continue,
where the user signs in with /mcp auth ct-{projectKey}. Project-level servers load after folder
trust. copilot mcp login doesn't exist, copilot mcp add writes only to the user configuration,
and copilot mcp list and copilot -p skip project-level servers, so none of them can check the
file.OpenAI Codex
Not covered by the script: Codex has no per-project MCP configuration. Say so first:
Codex has no per-project MCP configuration. This goes in~/.codex/config.tomland will be available in your other Codex projects too. Add it?
codex mcp add ct-<projectKey> --url <url> && codex mcp login ct-<projectKey> && codex resume --last "Signed in. Continue the commercetools MCP setup: verify the new connection, then ask whether to connect another project"
codex mcp login ct-<projectKey>Cursor
url. The
user signs in from Cursor's MCP settings.Other agents
Ask the user where their agent keeps MCP configuration and how it signs in.
Checklist
- Existing connections checked first; an available one verified with
read_project - One Project connected before offering more
- Server created with
scripts/create-managed-mcp-server.mjsafter the user approved its plan; the form only as fallback - Sign-in run in the background with its URL relayed to the user; the session file named; the
manage_mcp_serversscope and no API client - OAuth / Identity, not API client credentials
- All for development, All read for production, stated for every project
- Environment asked for every project after the first; a production marker treated as production
- Nothing inferred from Project configuration
- Connection written with
scripts/connect-repository-mcp.mjs, not by hand; an error treated as a hard stop - The printed next step relayed verbatim, not run; success claimed only after
read_project
Select the minimal tool set
mcpServer.tools and self-hosted --tools.Two questions, not one
Method
- Write down the agent's workflow as concrete steps ("find products with inventory > N", "read their current price", "create a product discount"). Map tools to the task, not to resources — if a step doesn't map to a tool, the agent doesn't need that data.
- From the live inventory (discover-tools.md), pick one tool per step. Favor search/read tools for discovery and a single specific write tool for the action.
- Match the tool to how the project models its data. If prices are standalone rather than embedded
on variants, the agent needs the standalone-price read tool, not just
read_products; if a feature isn't used at all, its tools are dead weight (inspect-project-data.md). - Least privilege. Include a tool only if the task actually needs it. A read/report agent gets read
tools only — writing tools widen the blast radius of a bad call. The catalog's
operationType(read/write) tells you which is which without guessing from the name (discover-tools.md). - Drop anything speculative. If you're unsure, leave it out — adding it later is a one-line change
(Managed:
addTool; self-hosted: edit--tools).
all and read_all are convenient
for exploration, but a production agent should list the individual snake_case tools it needs. all is not a
snapshot of today's catalog — it's a standing subscription to whatever ships next:Theminorandpatchversions are applied automatically. If you have enabledallorread_allfor your MCP Server, new tools can become available through an automatic release. If you select tools individually, new tools are not added unless you explicitly include them in your configuration. — Versioning
all" — and that holds however much you trust the agent: a trusted agent
still gets tools you never evaluated.Output
- Managed: a
toolsarray on the server sub-object, e.g."tools": ["read_product_search", "read_products", "read_inventory", "create_product_discounts"]. Names must be unique and valid for the server's(type, majorVersion). - Self-hosted:
--tools=read_product_search,read_products,read_inventory,create_product_discounts.
Run the proposed set past the user with your reasoning before finalizing — they may spot a step you missed or a tool that isn't needed.
Test with blind agent runs, then iterate
Precondition: a live, connectable server
-
Server
Enabledand the runtime client created by the user — hand-off. -
Server connected to this session. Blind sub-agents inherit the session's MCP connections, so the user must add the server to the current client (e.g.
claude mcp add, using the runtime client) and reconnect. Then confirm the tools are callable — list them, make one real call — before spawning any tester. For a cleaner test, ask the user to temporarily disable the design-time connection (ct-{projectKey}) so it can't leak into the run. -
On a
CommercetoolsIdentityserver, note whose permissions you're testing. The surface under test is the intersection of your tool list and the signed-in user's Merchant Center permissions, so a tool missing fromtools/listmay be a permissions result rather than a config bug — and a test account with wider permissions than the real users will pass runs they would fail.
If in-session connection isn't possible, use a fallback (below) — don't skip the test.
Run blind sub-agents
Each tester's prompt must:
- State the task exactly as the real agent would get it (from what the user described in step 2), plus any starting inputs.
- Restrict it to the server: ONLY the
{key}server's tools; every other tool (broad Commerce MCP, Knowledge MCP, web, docs, file search) is unavailable. - Mock steps outside the MCP's job ("assume the notification is sent", "assume the human approved").
- Ask for the report format below.
Report format
- Completed? Final action/result, and whether it matches the task.
- Tool-by-tool: was each description enough to know when and how to use it? Right on the first call, or fumbled parameters / retried?
- Missing: steps with no matching tool; responses lacking a needed field.
- Excess: response fields it never used.
- Wrong-tool / ambiguity: where it hesitated or two tools looked interchangeable.
- Parameter friction: values it guessed; anything constant for this task.
- Verdict + concrete config-change suggestions.
Read the actual transcript, not just the summary — a tester often "succeeds" while quietly doing the wrong thing first.
Map findings → config levers
| Symptom in the run | Fix |
|---|---|
| Wrong tool chosen / unsure when to use / fumbled the call | Sharpen the description; add when-not-to-use (tool-customizations.md) |
| Needed data the response didn't include | Loosen the field filter (over-dropped) or add a missing tool (field-filtering.md, select-tools.md) |
| Response carried fields it never used | Tighten the field filter; add JsonToTabular for list-heavy tools |
| Guessed a value that's fixed for this agent | const it; narrow with allowedTypes; default for soft ones |
| A tool was never called across all runs | Remove it |
| Overreached / did something out of scope | Remove the tool or pin params to shrink the blast radius |
Propose the changes to the user with the run evidence before applying.
Apply and re-test
tools/list, so the test client must reconnect
before the next round; field-filter changes apply live on tools/call. Re-run the same scenarios, but
cap at ~2–3 rounds and confirm with the user — past that you overfit to one tester's quirks.Fallbacks (don't skip the test)
- Can't connect in-session: ask the user to run a test agent in their own client against the server (task brief only, restricted to its tools) and paste back the transcript; iterate from that.
- Last resort — self-test: the main agent calls the tools itself. Weaker, since it knows the design; to stay honest, use only the server's tools, ignore what you know of the filters and tool choices, and follow the brief literally. A smoke check, not a blind eval.
- Self-hosted: identical method, but the only levers are the tool set and field filtering. If runs fail because descriptions are unclear or parameters get misused, that is the signal to switch to Managed (choose-variant.md).
Tool customizations (Managed MCP Server only)
toolCustomizations (a) replaces a tool's generic API
description with task-specific guidance and (b) constrains its parameters. Self-hosted has no equivalent
for built-in tools — for context you'd use CLI flags (--storeKey, etc.).tools/list; parameter rules are enforced on
tools/call.Shape
"toolCustomizations": [
{
"tool": "read_product_search", // required; must be one of the server's tools
"description": "…", // optional: overrides the default description entirely
"parameters": [ // optional: per-parameter overrides
{
"name": "storeKey", // required; must exist in the tool's input schema
"description": "…", // optional
"const": "main-store", // optional: fixes the value; agent cannot change it
"default": "main-store", // optional: fills the value when the agent omits it
"allowedTypes": ["main-store", "secondary-store"] // optional: restricts to an allowed set
}
]
}
]
Rules (enforced; violating them is rejected at write time)
- Each entry needs at least one of
descriptionorparameters. - Each parameter override needs at least one of
description,const,default,allowedTypes. constis mutually exclusive with bothallowedTypesanddefault.toolmust be one of the server'stools; each parameternamemust resolve to a property in that tool's input schema (value types must match the schema). Duplicatetoolentries or duplicate parameternames are rejected.- Description overwrites the default (no append) — you own the full text.
What each parameter lever is for
const— remove a decision entirely. The agent never sees a choice and can't get it wrong. Use for anything that is fixed for this agent: a store key,staged: false, a channel, a currency.allowedTypes— narrow a choice to the values that make sense for the task, without hardcoding one.default— supply a sensible value the agent may still override (e.g. a pagelimit).
What parameter overrides can't do
update_*
tools, which funnel many differently-typed update actions through a single actions array — some harmless
for a low-trust caller, others not. There is no documented way to restrict which action types that array
accepts, so granting an update_* tool grants every action it accepts.update_* tool as safely scoped just because you filtered its response fields.If you need this narrowed, attempt an override on the nested action path and see whether the API accepts it — a rejection at write time is a definitive answer, and it's cheap to test. Only claim it works after you've seen it work.
Writing a good description override
Your AI agent won't have the original tool description available. Base your new tool description on the existing one, so that your AI agent has all the information it needs to use the tool correctly. — Customize for AI accuracy
tools/list,
discover-tools.md), then write one that keeps what was working and adds what's
missing. This is about not losing information, not about staying generic.A strong override covers:
- What it does in one line, scoped to the agent's job.
- When to use / when not to use — steer away from tempting-but-wrong tools.
- Key parameters the agent must set, with example values.
- What it returns at a high level (especially after filtering — describe the trimmed shape).
- Project-specific knowledge that makes the tool usable here — this is the highest-value part of an override and the one a generic API description can never give.
Example (sell-off agent's search tool, for a project whose catalog you've inspected):
Search the catalog for products to discount. Use this to find candidates by category or attribute —
NOT to read a single known product (use read_products with an id).
This project's top-level categories are: Road, Mountain, E-Bikes, Apparel, Accessories.
Useful filter attributes: frameSize (enum: xs|s|m|l|xl), wheelSize (in inches), color, brand.
Returns a compact list of { id, name, key, totalInventory } (other fields are filtered out).
Set `limit` to at most 50. Prices are not included here — read them separately if needed.
Keep it tight: the override competes for the same context window the tools are meant to save. Include the project facts that change how the agent searches — not the whole category tree or every attribute.
Confirm the description with the user — they know which categories, attributes, and vocabulary their agent should lean on, and can catch a misleading example before it ships.