Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,7 @@ PHPUnit
PHPunit
PII
PIM
PKCE
POC
POS
PRs
Expand Down Expand Up @@ -1341,6 +1342,7 @@ checksums
ci
classmap
classname
claude
cleanUp
cleanUpPreviousState
clearTypeAndCheck
Expand Down
9 changes: 8 additions & 1 deletion guides/plugins/plugins/mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ class MyTool extends McpToolResponse
) {
}

/**
* @param string $email the customer's email address
* @param int $limit how many orders to return, newest first
*/
public function __invoke(string $email, int $limit = 10): string
{
$context = $this->contextProvider->getContext();
Expand Down Expand Up @@ -103,6 +107,7 @@ class MyTool extends McpToolResponse
- `title` is optional. When set, MCP clients (Claude Desktop, Cursor, etc.) display it in their tool list instead of the machine-readable `name`. Omit it if you have no better label to offer.
- Names must only contain `a-zA-Z0-9_-`.
- Parameter types on `__invoke()` are mapped to JSON schema. Supported: `string`, `int`, `float`, `bool`. Default values make parameters optional.
- Describe each parameter with a `@param` docblock on `__invoke()`. The MCP SDK copies the text into the parameter's `description` in `inputSchema`.
Comment thread
BrocksiNet marked this conversation as resolved.
Resolved
- Obtain the request context via `McpContextProvider::getContext()` injected through the constructor. Do not add a `Context` parameter to `__invoke()`. The MCP SDK does not inject it there.
- `requirePrivilege()` returns an error string on failure; check its return value with `if ($error = $this->requirePrivilege(...)) { return $error; }`. `#[McpToolRequires]` is declarative only; without this call there is no runtime enforcement.
- Never use `Context::createDefaultContext()` inside a tool. It bypasses the integration's ACL. Use `McpContextProvider::getContext()` instead.
Expand Down Expand Up @@ -179,7 +184,7 @@ return static function (ContainerConfigurator $configurator): void {
};
```

Plugin tools use `shopware.mcp.tool` (not `mcp.tool`). The MCP compiler remaps this tag to `mcp.tool` at compile time and registers the tool with the MCP server builder. You do not need a `shopware.feature` flag tag.
Plugin tools use `shopware.mcp.tool` (not `mcp.tool`). The MCP compiler remaps this tag to `mcp.tool` at compile time and assigns the tool to the Admin API server (`/api/_mcp`). You do not need a `shopware.feature` flag tag, and you do not need to list your plugin's namespace or directory in any MCP configuration.

### Available tags

Expand Down Expand Up @@ -209,6 +214,8 @@ If the tool appears here, it is registered in the live HTTP endpoint and can be
- Service is tagged with `shopware.mcp.tool`
- `#[McpTool]` is on the class, not on `__invoke()`

Since Shopware 6.7.15.0, `debug:mcp` lists tools only. Use `bin/console debug:mcp --native` to verify prompts and resources.

## Adding prompts

Follow the same pattern with `#[McpPrompt]` and `shopware.mcp.prompt`:
Expand Down
168 changes: 138 additions & 30 deletions products/tools/mcp-server/configuration.md

Large diffs are not rendered by default.

27 changes: 25 additions & 2 deletions products/tools/mcp-server/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This guide walks you through connecting an AI client to a Shopware shop using th

## Prerequisites

- Shopware 6.7.14.0 or later. On 6.7.11.0 to 6.7.13.x, set `MCP_SERVER=1` in your `.env` file first and skip the discovery section below — see [Configuration](./configuration.md).
- Shopware 6.7.14.0 or later. On 6.7.11.0 to 6.7.13.x, set `MCP_SERVER=1` in your `.env` file first and skip the discovery section below. See [Configuration](./configuration.md). Selecting toolsets in the connection URL requires 6.7.15.0 or later.
- `symfony/mcp-bundle` installed — verify with `composer show symfony/mcp-bundle`. If it is missing, ensure it is listed as a dependency in `composer.json` and run `composer install`.

## Step 1: Create an integration
Expand Down Expand Up @@ -124,7 +124,7 @@ Verify the server is working with the CLI:
bin/console debug:mcp
```

This lists the complete registered capability catalogue. A fresh AI client session initially receives only the discovery tools described in the following section.
This lists every registered tool of both MCP servers. Use `bin/console debug:mcp --native` to list prompts and resources. A fresh AI client session initially receives only the discovery tools described in the following section.

## Discover tools on demand

Expand All @@ -147,6 +147,27 @@ Enabled toolsets remain active only for the current MCP session and are tracked

The toolset names shipped by core are listed in the [Tools Reference](./tools-reference.md#toolsets).

### Select toolsets when connecting

Some clients read `tools/list` only once per connection and ignore `notifications/tools/list_changed`. claude.ai is one of them. In these clients, a toolset enabled during the conversation never becomes visible.

Since Shopware 6.7.15.0, you can name toolsets in the URL the client connects to. Their tools are then part of the first `tools/list`:

```text
https://your-shop.example.com/api/_mcp?toolsets=order,media
https://your-shop.example.com/api/_mcp?toolsets=all
```

- Separate toolset names with commas. `all` selects every toolset the principal may see.
- Unknown names are ignored. A malformed parameter never makes `tools/list` fail.
- The selection is read from every request and is not stored. No session is required, and nothing expires.
- The parameter only changes which tools are advertised. The MCP allowlist and the ACL role still decide what may be called, so a toolset outside the allowlist stays hidden.
- Without the parameter, the endpoint behaves as before.

The Store API endpoint accepts the same parameter, for example `/store-api/_mcp?toolsets=store-api`.

Prefer a short list of focused toolsets over `all`. Every advertised tool uses space in the agent's context window, and `tools/list` is paginated with 50 entries per page. A client that ignores `nextCursor` sees only the first page.

## Authentication methods

### Integration credentials (recommended)
Expand All @@ -157,6 +178,8 @@ Pass `sw-access-key` and `sw-secret-access-key` as HTTP headers. Credentials are

Standard Admin API OAuth bearer tokens also work. Obtain one via the `/api/oauth/token` endpoint. Tokens expire (default: 10 minutes), so integration credentials are preferred for persistent MCP clients. When authenticated via bearer token, the user's per-user allowlist applies (configured under **Settings → Users & Permissions → [user] → MCP tool allowlist**).

A missing, invalid, or expired token is answered with HTTP 401 and the JSON-RPC error code `-32001`. The message includes the reason, such as `Access token is expired`, so the client can refresh the token. Before Shopware 6.7.15.0, `/api/_mcp` answered these cases with HTTP 500 and `-32000`.

## Controlling which capabilities are available

By default, an admin integration is permitted to call all registered tools, resources, and prompts. Only the discovery tools are advertised at the start of a session. To restrict which capabilities can be discovered and called:
Expand Down
1 change: 1 addition & 0 deletions products/tools/mcp-server/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ The Shopware MCP Server is currently available as an experimental feature.
:::info
Introduced in Shopware 6.7.11.0
Progressive tool discovery and the removal of the `MCP_SERVER` feature flag in Shopware 6.7.14.0
Connect-time toolset selection and `symfony/mcp-bundle` 0.13 in Shopware 6.7.15.0
Based on the MCP specification
Available through the `/api/_mcp` and `/store-api/_mcp` endpoints
:::
Expand Down
16 changes: 13 additions & 3 deletions products/tools/mcp-server/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Shopware ships a native MCP server with the core platform. It exposes an endpoin
:::info Experimental
The MCP server is considered experimental until Shopware 6.8. APIs and tool names may change before the stable release.

The MCP server was introduced in Shopware 6.7.11.0 behind the `MCP_SERVER` feature flag. Shopware 6.7.14.0 removes the flag and introduces progressive tool discovery — see [Configuration](./configuration.md) for what applies to which version.
The MCP server was introduced in Shopware 6.7.11.0 behind the `MCP_SERVER` feature flag. Shopware 6.7.14.0 removes the flag and introduces progressive tool discovery. Shopware 6.7.15.0 moves to `symfony/mcp-bundle` 0.13 and adds connect-time toolset selection. See [Configuration](./configuration.md) for what applies to which version.
:::

## What the MCP server provides
Expand All @@ -28,7 +28,7 @@ The following table summarizes the MCP server's core capabilities.
| **Authorization** | Admin API ACL enforcement or the current Store API sales-channel context |
| **Tool allowlist** | Per-integration and per-user selection in Admin UI; intersected when an app forwards `sw-app-user-id` |
| **Rate limiting** | Per-principal rate limiting |
| **Discovery** | A small default tool surface with search and session-scoped toolsets |
| **Discovery** | A small default tool surface with search, session-scoped toolsets, and connect-time `?toolsets` |
| **Extensibility** | Extensions can contribute custom tools, prompts, and resources |

## Architecture
Expand All @@ -39,6 +39,14 @@ The following table summarizes the MCP server's core capabilities.

**Apps** register capabilities declaratively in `Resources/mcp.xml`. Shopware calls the app's endpoint over HTTP at runtime, using an HMAC-signed request. Use an app when your logic runs on a remote service, requires cloud compatibility, or should be deployed independently from Shopware.

### Two servers, one bundle

Shopware runs two MCP servers side by side: `admin` behind `/api/_mcp` and `store_api` behind `/store-api/_mcp`. Both have existed since Shopware 6.7.11.0. Up to 6.7.14.x, Shopware built the Store API server by hand next to the bundle's single server. Since 6.7.15.0, both are declared under the `servers` key of the `symfony/mcp-bundle` configuration. Each server has its own registry, its own session store, and its own set of protocol handlers. A session created on one endpoint is therefore not valid on the other.

The bundle's own HTTP transport is switched off for both servers. Shopware's controllers own the endpoints, so authentication, rate limiting, and the capability allowlist always run before a request reaches the bundle.

Capabilities are registered at container compile time from their service tags. Core capabilities are assigned to a server by namespace prefix. Plugin and third-party bundle capabilities are assigned by Shopware based on the tag they carry: `shopware.mcp.*` goes to the Admin API server, and `shopware.store_api_mcp.*` goes to the Store API server. App capabilities are loaded at runtime and are only served on the Admin API server.

### Plugin or App?

**Use a plugin or Symfony bundle when:**
Expand All @@ -55,7 +63,9 @@ The following table summarizes the MCP server's core capabilities.

## Spec coverage and known limitations

Shopware's MCP server is built on `symfony/mcp-bundle` (currently `~0.11.0` with `mcp/sdk ^0.7.0`) and takes the [MCP specification (2025-11-25)](https://modelcontextprotocol.io/specification/2025-11-25) as its reference. Shopware does not pin a protocol revision: the SDK negotiates it from the client's `initialize` request. The bundle may not cover every feature of the reference revision, and some areas of the spec are only partially implemented.
Shopware's MCP server is built on `symfony/mcp-bundle` and takes the [MCP specification (2025-11-25)](https://modelcontextprotocol.io/specification/2025-11-25) as its reference. Shopware 6.7.15.0 and later use `symfony/mcp-bundle ~0.13.0` with `mcp/sdk ^0.8.1`. Shopware 6.7.14.x uses `~0.11.0` with `mcp/sdk ^0.7.0`.

Shopware serves only the session-based handshake protocol era. The SDK negotiates the concrete revision from the client's `initialize` request within that era. `mcp/sdk` 0.8 also supports the stateless 2026-07-28 revision, but Shopware disables it on both endpoints. That revision issues no `Mcp-Session-Id`, and session-scoped toolsets and `listChanged` notifications depend on that header. The bundle may not cover every feature of the reference revision, and some areas of the spec are only partially implemented.

Shopware supports cursor pagination for capability lists and emits `listChanged` notifications when capabilities change. The following table lists the remaining known gaps.

Expand Down
9 changes: 7 additions & 2 deletions products/tools/mcp-server/store-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ The Store API server uses the same progressive discovery flow as the Admin API s
Since Shopware 6.7.14.0, a bare `tools/list` on `/store-api/_mcp` no longer returns the full catalogue. Even `shopware-store-api-context` is deferred behind the `store-api` toolset. Clients that expected the complete list must either follow the discovery flow or enable the toolset explicitly.
:::

Since Shopware 6.7.15.0, clients can also select toolsets in the connection URL, for example `/store-api/_mcp?toolsets=store-api`. The tools of the named toolsets are part of the first `tools/list`, without a `shopware-toolset-enable` call. See [Select toolsets when connecting](./getting-started.md#select-toolsets-when-connecting).

The Store API server keeps its sessions in its own session store, separate from the Admin API server. An `Mcp-Session-Id` from `/api/_mcp` is not valid on `/store-api/_mcp`. See [Session store](./configuration.md#session-store).
Comment thread
BrocksiNet marked this conversation as resolved.
Resolved

The server advertises `instructions` during `initialize` that state the same thing:

> This MCP server exposes Store API capabilities. All operations run in the current sales-channel context and use Store API authentication headers. The advertised tool list is not the full catalogue: if no advertised tool matches the requested action, call shopware-tool-search first instead of assuming the action is unsupported, then use shopware-toolsets-list and shopware-toolset-enable to make a matched tool callable if your client cannot invoke it inline.
Expand Down Expand Up @@ -91,7 +95,7 @@ Core registers no prompts or resources for the Store API scope.

## Sessions and rate limiting

MCP sessions and enabled toolsets use the same storage as the Admin API server, including the `mcp_toolset_session` table and its daily cleanup task — see [Session store](./configuration.md#session-store). Toolset names are not namespaced per endpoint.
MCP sessions live in the Store API server's own session store. Enabled toolsets are stored in the `mcp_toolset_session` table, which both endpoints share, including its daily cleanup task. See [Session store](./configuration.md#session-store). Toolset names are not namespaced per endpoint.

Rate limiting uses its own bucket, `mcp_store_api`: 120 requests per minute and 600 per 10 minutes, keyed on the sales channel and context token, plus an additional per-client-IP limit. The limits are deliberately tighter than the Admin API's, because a context token is cheap to rotate. See [Rate limiting](./configuration.md#rate-limiting).

Expand Down Expand Up @@ -133,4 +137,5 @@ Notes for extension developers:
- Use `StoreApiMcpContextProvider` to obtain the sales-channel context. There is no Admin API context and no `requirePrivilege()` equivalent.
- Assign a group with `#[McpToolGroup]` so clients can discover and enable your tools — the same grouping rules apply as for the Admin API. See [Assign a tool group](../../../guides/plugins/plugins/mcp-server.md#step-2-assign-a-tool-group).
- Registering the same tool name for both scopes requires two classes. The MCP SDK binds a tool to the class that carries the `#[McpTool]` attribute, which is why core has separate Admin API and Store API discovery tool classes.
- `bin/console debug:mcp` covers the Admin API server only. Store API capabilities do not appear in its output.
- `bin/console debug:mcp` lists the tools of both servers. Use `bin/console debug:mcp --scope=store-api` to show only the Store API server, and `bin/console debug:mcp --native` for prompts and resources.
- Capabilities are assigned to the Store API server by their `shopware.store_api_mcp.*` tag. No namespace or directory configuration is required.
12 changes: 7 additions & 5 deletions products/tools/mcp-server/tools-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Enable one toolset per call. There is no counterpart for disabling a toolset, an

The response sets `_meta.listChanged` to `true` and Shopware emits `notifications/tools/list_changed`. Clients that do not refresh automatically must request `tools/list` again. Enabling a toolset does not grant permission to call its tools; the effective MCP allowlist remains the security boundary.

For clients that never refresh `tools/list`, name the toolsets in the connection URL instead, for example `/api/_mcp?toolsets=entity,order`. See [Select toolsets when connecting](./getting-started.md#select-toolsets-when-connecting).

## Toolsets

Every tool belongs to exactly one group, and every group except `discovery` becomes a toolset that a client can enable. Core ships these toolsets:
Expand Down Expand Up @@ -171,11 +173,11 @@ Use this instead of `shopware-entity-search` when you need counts, averages, sum

**Parameters:**

| Name | Type | Required | Default | Description |
| -------------- | ------ | -------- | ------- | ------------------------------------- |
| `entity` | string | yes | — | Entity name |
| `aggregations` | string | yes | — | JSON array of aggregation definitions |
| `filters` | string | no | `[]` | JSON array of filter definitions |
| Name | Type | Required | Default | Description |
| -------------- | ------ | -------- | ------- | -------------------------------------------------------------------------------------- |
| `entity` | string | yes | — | Entity name |
| `aggregations` | string | yes | — | JSON array of aggregation definitions. A single object instead of an array is rejected |
| `filters` | string | no | `[]` | JSON array of filter definitions |

**Supported aggregation types:** `avg`, `sum`, `min`, `max`, `count`, `terms`, `date-histogram`, `range`, `filter`, `entity`

Expand Down
Loading