diff --git a/.wordlist.txt b/.wordlist.txt index 712a09699c..f3fdc03ae2 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -611,6 +611,7 @@ ManyToOneAssociation ManyToOneAssociationField MapState McpHelloWorld +McpToolGroup MediaDataSelection MediaDataSet MediaFolderDataSet @@ -1673,9 +1674,11 @@ mapErrors mapGetters martinfowler masternode +matchedIn matchers matthiasnoback maxPurchase +maxResults mbstring mcp md @@ -2044,6 +2047,8 @@ toggleable tokenized tokenizer toolchain +toolset +toolsets tooltip tooltips totalAmount diff --git a/guides/plugins/plugins/mcp-server.md b/guides/plugins/plugins/mcp-server.md index 297a3a7593..9c7e558f1f 100644 --- a/guides/plugins/plugins/mcp-server.md +++ b/guides/plugins/plugins/mcp-server.md @@ -55,11 +55,13 @@ use Mcp\Capability\Attribute\McpTool; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; use Shopware\Core\Framework\Mcp\Attribute\McpToolDependsOn; +use Shopware\Core\Framework\Mcp\Attribute\McpToolGroup; use Shopware\Core\Framework\Mcp\Attribute\McpToolRequires; use Shopware\Core\Framework\Mcp\Context\McpContextProvider; use Shopware\Core\Framework\Mcp\Tool\McpToolResponse; -#[McpTool(name: 'swag-my-plugin-orders', title: 'Order List', description: 'List recent orders for a given customer email.')] +#[McpTool(name: 'swag-my-plugin-orders', title: 'Order List', description: 'List recent orders for a given customer email.', meta: ['deferred' => true])] +#[McpToolGroup('orders')] #[McpToolDependsOn('shopware-entity-schema')] #[McpToolRequires('order:read')] class MyTool extends McpToolResponse @@ -95,6 +97,8 @@ class MyTool extends McpToolResponse - `#[McpTool]` goes on the class, not on `__invoke()`. The MCP compiler reads class-level attributes; method-level attributes are silently ignored. - `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. +- Set `meta: ['deferred' => true]` for tools that should stay hidden from the initial `tools/list` response and be found through `shopware-tool-search` or a toolset. Use `meta: ['deferred' => false]` only for small, broadly useful discovery or bootstrap tools. +- Use `#[McpToolGroup('...')]` to assign a tool to a session toolset. Group names should describe the workflow area, for example `orders`, `erp`, or `media`. Toolsets change visibility only; the integration or user allowlist still controls access. - 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. - 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. diff --git a/products/tools/mcp-server/best-practices.md b/products/tools/mcp-server/best-practices.md index 844a4f58ae..d8fb54a75c 100644 --- a/products/tools/mcp-server/best-practices.md +++ b/products/tools/mcp-server/best-practices.md @@ -61,9 +61,12 @@ When everything is enabled, the modal also shows inline privilege gaps, meaning Allowlist modal with all tools selected, showing inline privilege warnings +Use deferred tools and toolsets for large tool catalogs. Keep only discovery and high-signal bootstrap tools visible by default. Put workflow-specific tools into groups such as `order`, `media`, or an extension-specific group, and let the client enable that group when the conversation needs it. This improves routing and context usage, but it is not a security control. The allowlist and ACL checks still decide whether the tool can be called. + Strategies to reduce tool count within a single integration: - Use an `action` parameter to multiplex related operations into one tool (e.g., `shopware-theme-config` with `action: "get" | "update"`) +- Mark specialized tools as deferred and assign them to a toolset group - Use resources instead of tools for static reference data ### Keep responses compact diff --git a/products/tools/mcp-server/configuration.md b/products/tools/mcp-server/configuration.md index aba4fbff2a..471deaa011 100644 --- a/products/tools/mcp-server/configuration.md +++ b/products/tools/mcp-server/configuration.md @@ -90,6 +90,46 @@ Admin users bypass the user side of the intersection — if the user is an admin This pattern lets the app owner control which tools the integration may ever call, while users control which of those tools they personally allow the app to use on their behalf. Neither side can grant more than what the other has permitted. +## Tool discovery + +To keep MCP sessions small, Shopware does not need to advertise every registered tool in the initial `tools/list` response. Tools can be marked as **deferred** by the server. Deferred tools stay callable when they are allowed, but they are hidden from the initial list until the client explicitly discovers or enables them. + +The default visible tools are the discovery tools and a small set of core tools that agents commonly need to get started, such as entity schema/search and tool search. Other tools can be found through tool search or enabled through a session toolset. + +### Tool search + +Use `shopware-tool-search` when the needed tool is not visible in `tools/list`. It searches the allowed tool catalog and returns matching tool definitions inline: + +```json +{"query": "order state", "maxResults": 3} +``` + +Tool search respects the current allowlist. It cannot reveal or call tools that the integration or user is not allowed to use. + +### Session toolsets + +Toolsets are session-scoped groups of related tools. They are derived from explicit tool group metadata, for example `entity`, `order`, `media`, or `system-config`. They are a visibility convenience for the current MCP session, not a permission system. + +The typical client flow is: + +1. Call `shopware-toolsets-list` to see available toolsets. +2. Call `shopware-toolset-enable` with the selected toolset name. +3. Refresh `tools/list` when the client receives the list-changed notification. + +Example: + +```json +{"toolset": "order"} +``` + +Enabling a toolset only makes tools visible if they are also allowed by the effective allowlist. The allowlist remains the hard security boundary. + +### List pagination and refresh notifications + +Capability list responses support MCP cursor pagination for tools, resources, and prompts. Clients should follow `nextCursor` until it is absent instead of assuming the first page contains every capability. + +Shopware sends MCP list-changed notifications when the visible capability set changes, for example after enabling a session toolset or after app capabilities are installed, updated, activated, deactivated, or removed. Clients should refresh `tools/list`, `resources/list`, or `prompts/list` according to the notification. + ## MCP bundle configuration The underlying `symfony/mcp-bundle` is configured in `config/packages/mcp.php`. Shopware ships this file, and Symfony loads it automatically; the `MCP_SERVER` feature flag only gates the HTTP endpoint (`/api/_mcp`), not the bundle's DI configuration. You do not need to create or modify it for standard setups. diff --git a/products/tools/mcp-server/tools-reference.md b/products/tools/mcp-server/tools-reference.md index 165d86b429..38984a11d5 100644 --- a/products/tools/mcp-server/tools-reference.md +++ b/products/tools/mcp-server/tools-reference.md @@ -51,6 +51,51 @@ When you enable a tool in the integration's tool allowlist, its declared depende | `shopware-entity-delete` | `shopware-entity-search` | | `shopware-system-config-write` | `shopware-system-config-read` | +## Discovery Tools + +Discovery tools help clients work with a small initial tool list while still finding the right tool for a task. + +### shopware-tool-search + +Search the allowed tool catalog by free-text query. Use it when a needed tool is not visible in `tools/list`. + +**Parameters:** + +| Name | Type | Required | Default | Description | +|--------------|--------|----------|---------|----------------------------------------------| +| `query` | string | yes | — | Search phrase, for example `order state` | +| `maxResults` | int | no | 3 | Maximum number of matches. Capped at 20 | + +**Response:** `{"success": true, "data": [{"tool": {...}, "score": 12.3, "matchedIn": ["name:token"]}], "_meta": {"query": "...", "totalCandidates": 42}}` + +Tool search respects the current allowlist and does not return tools that the current integration or user cannot access. + +### shopware-toolsets-list + +List MCP toolsets that can be enabled for the current session. Toolsets are derived from explicit tool group metadata and are intended to reveal related deferred tools on demand. + +**Parameters:** none. + +**Response:** `{"success": true, "data": {"toolsets": [{"name": "order", "title": "Order tools", "tools": ["shopware-order-state"], "enabled": false}]}}` + +### shopware-toolset-enable + +Enable one toolset for the current MCP session and ask the client to refresh `tools/list`. + +**Parameters:** + +| Name | Type | Required | Description | +|-----------|--------|----------|--------------------------------------------------| +| `toolset` | string | yes | Toolset name returned by `shopware-toolsets-list` | + +**Example:** + +```json +{"toolset": "order"} +``` + +Enabling a toolset is session-scoped. It changes tool visibility only; it does not bypass the integration or user allowlist. + ## Read Tools ### shopware-entity-schema