commercetools MCP

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

Recommended: install the full commercetools plugin. It includes this Skill, every other commercetools Skill, our pre-tuned Subagents, and the commercetools Knowledge MCP — which gives AI live access to the commercetools docs, GraphQL/OpenAPI schemas, and query validation. You only install once; every Skill on this site becomes available in every session.
Install the plugin

In any Claude Code session, run the following commands one at a time:

  1. Add the commercetools AI marketplace:

    /plugin marketplace add commercetools/commercetools-ai-plugins
    
  2. Install the commercetools AI plugin (Skills + Subagents + MCP):

    /plugin install commercetools@commercetools
    
Reload plugins

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
Claude Desktop
Customize -> Personal plugins -> Create plugin -> Add marketplace -> Add commercetools/commercetools-ai-plugins. Then, click on the plugin and click Install.

Instructions Included

SKILL.md

commercetools MCP

ServerGives an agentAccess
Knowledge MCPDocumentation search, GraphQL and OpenAPI schemas, query validationPublic, no sign-in. Bundled with this plugin.
Commerce MCPTools that read and change Project dataNeeds 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

For a coding agent that reads and configures a Project while you build a solution, follow Repository connection. Each developer signs in with their Merchant Center account through a managed MCP Server, and the connection is shared through the repository.

Configure an MCP server for an agent

For an agent with one job, such as a support assistant or a promotion agent, follow Agent MCP server. It cuts the Commerce MCP down to the tools and fields the task needs, sharpens tool descriptions and parameters, creates the managed MCP Server (or emits a self-hosted configuration), and tests it with blind agent runs. The design work itself runs through the repository connection.

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

agent-server.md

Configure a focused Commerce MCP

The base Commerce MCP is a broad translation of the commercetools API: 100+ tools, each with many parameters and verbose responses. Handing all of that to an agent with one job wastes context and invites wrong tool calls. You do not build new tools — you subtract and sharpen an existing surface down to what one agent needs.

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, .env files, 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:

  1. 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 10
    
    Pass the commercetools project key and region (as in api.{region}.commercetools.com) only if already in your context; otherwise omit both. Never search files or ask the user for them.
  2. 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.
  3. 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.
  4. Discover the available tools — Pull the live inventory; never design against a memorized list → discover-tools.md.
  5. Inspect the project's real data (required) — The step that makes the config correct, per ground rule 3 → inspect-project-data.md.
  6. Curate the minimal tool set — Least privilege, grounded in step 5 → select-tools.md.
  7. 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.
  8. Emit / apply the config —
    • Managed: assemble the draft, create/update the server with the Commerce MCP's create_mcp_servers / update_mcp_servers tools, 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.
  9. 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

Fetch OpenAPI (REST) schema — inspect a resource's payload/response shape before deciding filters or which tool parameters matter:
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.
For a self-hosted config, report the emitted mcp.json and the client scopes it needs instead.

Worked example

examples/sell-off-inventory-agent.md walks the method end to end for an agent that finds high-inventory products and creates a promotion to sell them off — as both a Managed config and a self-hosted fallback.

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 all group 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 to Disabled. authenticationMode was set explicitly, not defaulted into.
  • create_mcp_servers / update_mcp_servers are 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-variant.md

Choose the variant: Managed vs self-hosted

Both variants run the same Commerce MCP tools (snake_case names) and the same field-filtering engine (@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-mcp README for self-hosted; managed-server-api.md for Managed.
Version alignment: the managed Commerce MCP v4 tool set matches self-hosted Commerce MCP 4.0.0 and up. This skill assumes that shared snake_case tool set — for self-hosted, use 4.0.0+; earlier releases expose a different, older tool surface.

Default to Managed

Choose self-hosted only for the two cases the docs call out: building completely custom tools beyond customizing existing ones, or embedding the tool code directly in an AI agent framework (LangChain, Vercel AI, Mastra, …) without a dedicated MCP server. Otherwise present Managed as the recommendation.
Managed wins on two counts. The sharpening levers this skill is built around — description overrides and pinned parameters — are Managed-only. And the credential blast radius is far smaller: the agent's credential is scoped to only this MCP server, so a leak reaches just the curated tools and filtered fields. Self-hosted hands the agent process a broad CT API client whose scopes cover whole resources (e.g. all of view_products), so a leak grants direct API access that bypasses your curation and filtering entirely — the confused-deputy problem.
Not reasons to pick self-hosted: "easier to run locally" (creating the Managed server is a couple of tool calls you make for the user; Managed is the right default for local dev too) or "it matches an existing 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)

LeverSelf-hostedManagedConfigured via
Curate the tool setYesYesself-hosted: tools flag · Managed: tools (select-tools.md)
Filter / redact response fieldsYesYesself-hosted: field-filtering flag (stringified FieldFilteringManagerConfig) · Managed: jsonOutputFiltering (field-filtering.md)
Compact output formattingYesYesself-hosted: output-format flag · Managed: toolOutputFormatting
Pin operating context (store, etc.)YesYesself-hosted: store/customer/cart/business-unit/admin flags · Managed: parameter const
Override individual tool descriptionsNo, at time of writingYesManaged: toolCustomizations (tool-customizations.md)
Constrain individual tool parameters (const/default/allowedTypes)No, at time of writingYesManaged: 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

Self-hosted runs as an MCP server (stdio locally, or a remote mode). Register it in 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-commerce-mcp.md

Connect a Commerce MCP for design work

Every later step runs through a Commerce MCP connected to the user's project: the tool catalog and data inspection (steps 4–5) need its read tools, and creating or updating the agent's Managed MCP Server (step 8) needs 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

Connect through the repository connection: a Managed MCP Server the user creates in the Merchant Center, signed in with their own Merchant Center account. No API client, no secret in the chat, the environment, or the repository. Follow its steps as written — it creates the server, writes the connection, and hands the sign-in back to the user. Once 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_servers and update_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_project returns the confirmed project's key and name. A mismatch means the connection points at another project — stop.
  • create_mcp_servers and update_mcp_servers are 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. Check tools/list rather than guessing which.

Production projects

The repository connection gives a production project All read, so nothing can change there through a coding agent — and that includes creating MCP servers. Don't widen it to All. Instead, offer two options and let the user choose:
  • 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

Never add 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-tools.md

Discover the available tools (don't assume a list)

The Commerce MCP tool set grows over time, and which tools exist depends on the server's (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

Call 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.
The response is { type, majorVersion, tools[] }, where each tool carries:
FieldUse it for
nameThe exact snake_case value to put in tools — never hand-type these
descriptionThe current default text, i.e. your starting point for an override (tool-customizations.md)
operationTyperead or write — the fastest least-privilege filter (select-tools.md)
groupsWhich groups (all, read_all) the tool belongs to
Omitting 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.
The catalog returns no input schemas. It tells you which tools exist, not what parameters they take, so you can't design 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.
If 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

Coverage is not symmetric — {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_intents exists with no matching read or create.
  • create_transactions and read_transactions exist, but there is no update.
  • Verbs beyond CRUD exist: replicate_carts.
  • create_bulk / update_bulk operate 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_case verb_resource. The same tool set is shared with self-hosted Commerce MCP 4.0.0+ (choose-variant.md), so names don't differ between the two variants.
field-filtering.md

Field filtering & output formatting (both variants)

Tool responses are the biggest hidden context cost: a single product read returns variants, prices, attributes, and audit metadata the agent never uses. The primary goal is token cost — drop what the task doesn't need. Both variants use the same @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 password comes 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
}
Matcher semantics (each matcher — 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 a redact/drop rule.

Self-hosted: --fieldFiltering

Pass a stringified 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 jsonOutputFilteringSelf-hosted FieldFilteringManagerConfig
droprules with type: "filter"
redactrules with type: "redact"
fieldPathspaths
fieldNamesproperties
fieldNameContainsincludes
fieldPathsToKeepwhitelistPaths
redactedWithjsonRedactionText (URLs: urlRedactionText)
Managed defaults 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

Don't guess field names — read the real shape (inspect-project-data.md): call the tool once and inspect the payload, or fetch the resource schema with openApi-schemata.mjs / graphql-schemata.mjs.
Be careful with 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

For list-heavy tools, compact further — Managed: "toolOutputFormatting": "JsonToTabular"; self-hosted: --toolOutputFormat=tabular. This renders array results as a table instead of repeated JSON objects. Trim fields first, then format.
inspect-project-data.md

Inspect the project's real data

The commercetools data model is highly configurable: two projects expose the same resources but model their data very differently, and the generic schema can't tell you which choices a project made. Look at the real data. It shapes all three levers — which tools are correct, how to describe them, and what to filter. This is the highest-value step in the workflow.
Read it through a Commerce MCP, not raw credentials — no API client is needed here. First confirm the connected MCP can reach what you need (products, categories, custom objects, …) and points at the project the user named. If it can't, walk the user through connecting one (connect-commerce-mcp.md). As a last resort they can run the reads themselves and share the output. Never work around a missing read path with raw credentials, and never emit a tool selection, filter, or description you haven't grounded in real data.

Response shapes

Commerce MCP tools wrap commercetools API resources, so a tool's response fields map to that resource's API fields. To decide what to drop or redact (field-filtering.md), fetch the resource schema with openApi-schemata.mjs (invocation in the skill's Optional scripts section) rather than guessing field names.
The best signal of all is a real 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

Which tools to select. Feature choices map to different tools. Prices can be embedded on product variants or modelled as standalone prices (a separate resource with its own tool) — the right read tool differs, and the wrong one gives the agent a tool that returns nothing useful. If the project doesn't use a feature at all, don't include its tools (select-tools.md).
What to filter. Custom fields (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.
How to describe tools. Descriptions need project-specific knowledge baked in (tool-customizations.md); the facts below are what feeds them.

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.
managed-server-api.md

Create / update a Managed MCP Server

The config is a commercetools API resource. Create and update it through the Commerce MCP's own MCP-server tools — 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

Check tools/list on the connected Commerce MCP for create_mcp_servers and update_mcp_servers.
Then, before the first create or update, call 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. key is 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.
ModeCredentialFitsTools exposed
ClientCredentials (default)API Client with scope mcp:{projectKey}:{key}Autonomous agents, machine-to-machineEvery tool on the server
CommercetoolsIdentityThe user's own Identity account (their Merchant Center login)Human-in-the-loop assistantsOnly configured tools the signed-in user's Merchant Center permissions allow

What this changes in practice:

  • Set it in the draft, deliberately. Omitting authenticationMode silently yields ClientCredentials. Change it later with the setAuthenticationMode update action.
  • CommercetoolsIdentity needs 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.
  • ClientCredentials works for human-in-the-loop too, but then per-user filtering is your problem.

4. Create the server — create_mcp_servers

The 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
    }
  }
}
The response is the created server, including 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 to state: "Disabled". Set "state": "Enabled" in the draft or send a setState action later; a disabled server rejects every tool request.

5. Update an existing server — update_mcp_servers

Read the current 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" }
  ]
}
The response carries the new 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.
Not available through the tool: deletion. There's no 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

On a 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.)
Build the link from the create response — mcpServer.url gives the host, id the server:
https://mc.{region}.commercetools.com/{projectKey}/settings/mcp-servers/{id}/overview/api-clients/new
Take the host of 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 no mcp: 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 (2592000 seconds). 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

The get-started guide's examples paste the bearer token straight into .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}" }
    }
  }
}
One catch: for an 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.
Full connection steps, including Cursor and n8n, and the OAuth-discovery alternative to a static token, are in the get-started guide.
repository-connection.md

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.json without touching other servers, and prints the next step. Don't use an agent's own mcp add command 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, production or live in 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

  1. Check what exists. A mcp.{region}.commercetools.com URL 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.
  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}/welcome
    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 development for a development project and --environment production for 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 --confirmed it 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.ctx signIn: there is no Merchant Center session, it expired, or it cannot call the API. Show the user needs.command (scripts/sign-in.mjs) and say that it opens a Merchant Center sign-in and runs mc-scripts through npx, 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 same mc-scripts login --mc-api-url the 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.command carries --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.
  3. 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 connection ct-{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 before read_project succeeds.

    For other agents, see their section.

Per agent

Claude Code, VS Code with Copilot and GitHub Copilot CLI share the repository's .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

Use --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_client or "Not found": Claude kept a stored sign-in for ct-{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 --settings flag is needed because the server comes from .mcp.json and 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

Use --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.
If the repository keeps its servers in .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

Use --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.toml and 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"
It adds the connection, signs in through the browser, and reopens this conversation. If they decline, stop and leave them the URL. Sign-in part only: codex mcp login ct-<projectKey>

Cursor

Per-project configuration in the project's Cursor directory. Remote servers declare a 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.mjs after 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_servers scope 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-tools.md

Select the minimal tool set

Tool curation is the highest-leverage decision: every tool you include costs context (its name + description + input schema) on every request, and every extra tool is one more thing the agent can call wrongly. This lever works in both variants — Managed mcpServer.tools and self-hosted --tools.
One server, one job. Scope the config to a single agent role, not "everything a team might need." A focused server of ~5–15 tools is the target; if you're approaching the full catalog, it isn't focused.

Two questions, not one

Task fit is only half the decision. The other half is who is on the other end of the conversation with this agent — because the same workflow warrants a different surface depending on who can steer it. An agent taking instructions from an anonymous end customer, one used by an employee, and one running unattended on a trusted internal trigger all deserve different answers, even where the task steps are identical. Anything a caller can talk the agent into doing is in the blast radius.
So after the user has described the task, ask who or what invokes this agent, and how much you trust them. Ask it openly — do not offer a menu of caller types, for the same reason ground rule 1 forbids inventing a menu of use cases. Their answer changes which write tools are defensible and how much you lean on pinned parameters (tool-customizations.md).

Method

  1. 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.
  2. 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.
  3. 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).
  4. 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).
  5. Drop anything speculative. If you're unsure, leave it out — adding it later is a one-line change (Managed: addTool; self-hosted: edit --tools).
Prefer explicit tools over broad groups in production. Groups like 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:
The minor and patch versions are applied automatically. If you have enabled all or read_all for 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
So a group widens the agent's surface with no config change, no review, and nothing in your diff. The docs take the same line for both reasons — "For security and performance, only enable the tools required for your use case, instead of using all" — and that holds however much you trust the agent: a trusted agent still gets tools you never evaluated.

Output

  • Managed: a tools array 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-and-iterate.md

Test with blind agent runs, then iterate

Inspection tells you what the data looks like; only a real run tells you whether an agent with no design context can drive the surface you built. Steps 6–8 produce a draft — this is where the config earns its shape. Loops back into steps 6–8 until the runs come back clean.

Precondition: a live, connectable server

  • Server Enabled and 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 CommercetoolsIdentity server, 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 from tools/list may 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 gets only the production task brief and nothing about the config: no chosen tools, no filter rules, no rationale, no "the intended tool for this step is…". Hand it the design and you're testing your design notes, not the surface.

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.
Cover a few scenarios (parallel is fine): the happy path; a realistic variation (different entry point / phrasing / data); an edge case the real agent will hit; and for a writing agent an out-of-scope probe — a task it should not be able to do.

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 runFix
Wrong tool chosen / unsure when to use / fumbled the callSharpen the description; add when-not-to-use (tool-customizations.md)
Needed data the response didn't includeLoosen the field filter (over-dropped) or add a missing tool (field-filtering.md, select-tools.md)
Response carried fields it never usedTighten the field filter; add JsonToTabular for list-heavy tools
Guessed a value that's fixed for this agentconst it; narrow with allowedTypes; default for soft ones
A tool was never called across all runsRemove it
Overreached / did something out of scopeRemove 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

Apply with update actions (managed-server-api.md). Refresh matters: tool set, description, and parameter changes surface via 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.
Exit criteria: the task completes using only the intended tools with first-try selection; no missing data; no unused response fields; no parameter guessing on values that should be pinned; no tool went uncalled; the out-of-scope probe (if run) fails safely.

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.md

Tool customizations (Managed MCP Server only)

After curating the tool set, sharpen each survivor. 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.).
Applied at the gateway: descriptions merge into 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 description or parameters.
  • Each parameter override needs at least one of description, const, default, allowedTypes.
  • const is mutually exclusive with both allowedTypes and default.
  • tool must be one of the server's tools; each parameter name must resolve to a property in that tool's input schema (value types must match the schema). Duplicate tool entries or duplicate parameter names 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 page limit).

What parameter overrides can't do

An override targets one named parameter of the tool's input schema. That is a poor fit for 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.
Treat this as the limit of the config as a security boundary: for a lower-trust caller, either leave the tool out, or make it explicit that the calling application must allowlist the actions it forwards. Don't present a broad 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

The description is the agent's only instruction for when and how to use the tool. Make it narrow, unambiguous, and task-specific — what this agent should use it for, not what the API does.
Start from the default text, don't discard it. An override replaces the original, so the agent never sees the default again and anything useful in it is gone unless you carry it forward:
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
Some defaults are load-bearing — search tools in particular document their own error codes and retry guidance — and an override carrying only your project-specific notes silently drops that. So read the current description first (the tool catalog or 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.
Bake in what the project actually looks like — "search products" is a different job in a grocery retailer than in a bicycle store, and the agent can't know the difference unless you tell it. For a search or query tool, fold in the facts from inspect-project-data.md: the top-level categories, the product-type attributes worth filtering/searching by (keys and allowed values), and any domain vocabulary. Same for tools touching custom fields — name the fields that matter and what they mean.

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.