diff --git a/.wordlist.txt b/.wordlist.txt
index 2d4afa6535..0bbac890e5 100644
--- a/.wordlist.txt
+++ b/.wordlist.txt
@@ -6,6 +6,7 @@ ADDR
ADR
ADRs
AES
+AI
AOF
API's
APIRequestContext
@@ -72,6 +73,7 @@ AppServer
AppServers
AppSystem
Archlinux
+ArrayAdapter
ArrayFacade
AssociationField
AsyncAws
@@ -340,6 +342,7 @@ DomainExceptions
Dotenv
DynamoDB
ECDSA
+ECONNREFUSED
ENUM
ENUMS
EOL
@@ -441,6 +444,7 @@ GCM
GDPR
GIFs
GLB
+GPT
GenericPage
GenericPageLoader
GitLab
@@ -565,6 +569,8 @@ LongText
LongTextField
Lychee
MACOSX
+MCP
+MCPs
MDN
MJML
MOV
@@ -589,12 +595,14 @@ ManyToOne
ManyToOneAssociation
ManyToOneAssociationField
MapState
+McpHelloWorld
MediaDataSelection
MediaDataSet
MediaFolderDataSet
MediaType
MemcachedSessionHandler
MenuOffcanvasPagelet
+MerchantAssistant
Mercure
MessageAware
MessageQueue
@@ -710,6 +718,7 @@ PHPStorm
PHPUnit
PHPunit
PII
+PIM
POC
POS
PRs
@@ -958,6 +967,7 @@ StorefrontController
StorefrontPage
StorefrontResponse
Storer
+Streamable
StringField
StringFields
Struct
@@ -975,6 +985,9 @@ SwagB
SwagBasicExample
SwagBasicExampleTheme
SwagDigitalSalesRooms
+SwagMcpAdminUsers
+SwagMcpDevTools
+SwagMcpMerchantAssistant
SwagMigrationBundleExample
SwagMyPlugin
SwagMyPluginSW
@@ -1034,6 +1047,7 @@ UDP
UI
UID
UML
+URIs
USD
UUID
UUIDs
@@ -1143,6 +1157,7 @@ activateShopwareTheme
adr
afterSort
ag
+ai
ajax
allowlist
allowlists
@@ -1380,6 +1395,7 @@ dr
dragTo
dropdown
dropdowns
+dryRun
dsr
dunglas
duplications
@@ -1642,6 +1658,7 @@ minimalistic
mixin
mixins
mocksArentStubs
+modelcontextprotocol
modifiability
modularity
monday
@@ -1893,6 +1910,7 @@ shopName
shopUrl
shopware
shopware's
+shopwareLabs
shopwarelabs
shopwarepartners
shorthands
@@ -1945,6 +1963,8 @@ subfolder
suboptimal
subprocessor
subprocessors
+subrequest
+substring
supervisord
svg
sw
@@ -2017,6 +2037,7 @@ uniqBy
unminified
unprefixed
unregister
+unrequested
unsecure
unserialize
unstorage
diff --git a/assets/mcp-allowlist-clean.png b/assets/mcp-allowlist-clean.png
new file mode 100644
index 0000000000..d549ef6580
Binary files /dev/null and b/assets/mcp-allowlist-clean.png differ
diff --git a/assets/mcp-allowlist-collapsed.png b/assets/mcp-allowlist-collapsed.png
new file mode 100644
index 0000000000..cd77de913d
Binary files /dev/null and b/assets/mcp-allowlist-collapsed.png differ
diff --git a/assets/mcp-integrations-allowlist-selection-modal.png b/assets/mcp-integrations-allowlist-selection-modal.png
new file mode 100644
index 0000000000..0714e398cf
Binary files /dev/null and b/assets/mcp-integrations-allowlist-selection-modal.png differ
diff --git a/assets/mcp-integrations-edit-mcp-allowlist.png b/assets/mcp-integrations-edit-mcp-allowlist.png
new file mode 100644
index 0000000000..d2e5e3e4e5
Binary files /dev/null and b/assets/mcp-integrations-edit-mcp-allowlist.png differ
diff --git a/assets/mcp-permissions-privilege-hint.png b/assets/mcp-permissions-privilege-hint.png
new file mode 100644
index 0000000000..2921526c65
Binary files /dev/null and b/assets/mcp-permissions-privilege-hint.png differ
diff --git a/guides/development/extensions/index.md b/guides/development/extensions/index.md
index f7027a1841..8dfd2a13a9 100644
--- a/guides/development/extensions/index.md
+++ b/guides/development/extensions/index.md
@@ -48,6 +48,13 @@ This comparison table helps you decide which Shopware extension type best fits y
Extensions must explicitly support target Shopware versions. Review the [Upgrades and Migrations](../../upgrades-migrations/index.md) section before releasing updates to ensure compatibility with upcoming core changes.
:::
+## MCP Server extensibility
+
+Both plugins and apps can contribute custom tools, prompts, and resources to Shopware's built-in [MCP Server](../tooling/mcp-server/index.md). This lets AI clients access your extension's capabilities alongside core platform tools.
+
+- [Extend the MCP Server via Plugin](../../plugins/plugins/mcp-server.md)
+- [Extend the MCP Server via App](../../plugins/apps/mcp-server.md)
+
## Extension guides
These guides provide essential information on how to create, configure, and extend your store with Shopware extensions:
diff --git a/guides/development/tooling/index.md b/guides/development/tooling/index.md
index 68c29d6fb2..5f2b75f118 100644
--- a/guides/development/tooling/index.md
+++ b/guides/development/tooling/index.md
@@ -20,3 +20,5 @@ Shopware provides official tools that support the full lifecycle of a Shopware p
- For IDE support, Shopware provides a [PHPStorm plugin](shopware-toolbox.md) and [VS Code extension](https://marketplace.visualstudio.com/items?itemName=shopware.shopware-lsp).
- [Shopware CLI](../../../products/cli/index.md): The central command-line tool for working with Shopware projects and extensions, including scaffolding, builds, validation, packaging, Store interaction, CI support, and development workflows such as watchers and [formatting](../../../products/cli/formatter.md).
+
+- [MCP Server](./mcp-server/index.md): A native Model Context Protocol server that lets AI clients (Claude Desktop, Cursor, Claude Code) interact with a Shopware shop through tools, resources, and prompts. Extensible via plugins and apps.
diff --git a/guides/development/tooling/mcp-server/best-practices.md b/guides/development/tooling/mcp-server/best-practices.md
new file mode 100644
index 0000000000..18d43b0dc0
--- /dev/null
+++ b/guides/development/tooling/mcp-server/best-practices.md
@@ -0,0 +1,259 @@
+---
+nav:
+ title: Best Practices
+ position: 40
+
+---
+
+# Best Practices
+
+Lessons learned building Shopware's MCP server. These principles apply to any MCP extension (plugin, bundle, or app) that exposes a complex domain to AI agents.
+
+## Tools
+
+[Tools](./mcp-concepts.md#tools) are the primary surface area of an MCP server. Getting their design right has the biggest impact on agent reliability.
+
+### Design for outcomes, not operations
+
+Design tools around what the agent wants to achieve, not around raw CRUD operations.
+
+**Instead of** exposing generic create/read/update/delete tools and expecting the agent to chain them correctly, wrap common multi-step workflows into a single tool with flattened parameters.
+
+For example, `shopware-order-state` wraps order, transaction, and delivery state changes into a single call with `orderNumber` and per-entity action parameters, instead of requiring three or more separate state-machine tool calls.
+
+**When to add an outcome tool:** If an agent needs three or more tool calls to accomplish a single user intent, that workflow is a candidate for a dedicated outcome tool.
+
+### Keep the generic tools too
+
+Outcome tools cover the common 80%. The generic entity tools (`shopware-entity-search`, `shopware-entity-upsert`, `shopware-entity-delete`) cover the remaining 20%: edge cases, ad-hoc queries, and entities without dedicated workflow tools. Do not remove the generic layer when you add higher-level tools.
+
+### Flatten parameters
+
+AI agents struggle with deeply nested JSON structures. Every level of nesting increases hallucination risk. Tool parameters should be flat strings, numbers, and booleans wherever possible.
+
+If complex input is unavoidable (e.g., search criteria), accept it as a JSON string parameter and parse it server-side. This gives the agent a single string to construct rather than a nested object tree.
+
+### Default to dryRun=true on all write tools
+
+All write tools should default to `dryRun=true`. This lets the agent preview the result before committing and catches errors early. The agent calls again with `dryRun=false` to persist.
+
+Agents tend to be overconfident; they will call a write tool on the first attempt without verifying parameters. A dry-run default forces a two-step pattern that catches mistakes before they cause damage.
+
+### Validate inputs before writing
+
+If a tool accepts a name that maps to an internal enum, registry, or state machine (event names, action names, state transitions), validate it before writing. The database may silently accept invalid values that produce broken data at runtime.
+
+### Limit tool count
+
+Each tool added to the MCP server increases the context window consumed by tool descriptions. More tools means more tokens spent before the agent even starts reasoning. Shopware core ships 11 built-in `shopware-*` tools, and plugins like SwagMcpMerchantAssistant add more, so keeping a single integration lean matters.
+
+The practical approach is to use **multiple integrations with scoped tool allowlists** rather than one integration that gets everything. For example:
+
+- **Merchant integration:** order state, system config, media upload, theme config. What a store manager needs day-to-day.
+- **Developer integration:** entity search, entity schema, aggregate, system config read. What a developer or CI pipeline needs for inspection and debugging.
+- **App-specific integration:** only the tools relevant to a specific workflow or external system.
+
+Each integration sees only its allowed tools, resources, and prompts, so each AI session starts with a smaller, more focused context. Configure allowlists under **Settings → Integrations → Edit MCP Allowlist**.
+
+Every registered tool also consumes tokens from the agent's context window for the entire session — not only when called. Each tool schema (name, description, parameters) costs roughly **550–1,400 tokens** depending on complexity. Some clients enforce their own hard caps (Cursor limits the total to 40 tools across all connected MCP servers). Scoped integrations with a small allowlist keep sessions fast and predictable.
+
+When everything is enabled, the modal also shows inline privilege gaps, meaning the integration's role does not actually cover what its allowlist exposes:
+
+
+
+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"`)
+- Use resources instead of tools for static reference data
+
+### Keep responses compact
+
+Tool responses are injected directly into the agent's context window, so large responses consume a significant portion of the token budget.
+
+When a response exceeds 100 KB, `McpToolResponse` does not inline it. Instead it stores the payload in a session-scoped cache and returns a `shopware://tool-result/{uuid}` URI in `_meta.resourceUri`. The agent can fetch the full content via `resources/read`. The `_meta` block also carries `responseSize` and `note` explaining why the result was deferred. For responses above 20 KB (but under the cap), `_meta.responseSize` is included as a size hint so the agent can learn to use tighter parameters next time.
+
+The goal is still to keep responses small. Deferred resources still cost tokens when the agent fetches them, and some clients may not follow up automatically.
+
+- **Separate entity rows from aggregations.** Aggregations (especially `terms` or `date-histogram` with many buckets) can produce thousands of entries. Mixing them with entity rows in one response compounds the problem — use `shopware-entity-aggregate` for aggregations and `shopware-entity-search` for records, never both in one tool call.
+- **Use `McpEntityIncludes` in plugin tools.** If your tool returns DAL entity data via `JsonEntityEncoder`, use the `McpEntityIncludes` trait. It automatically strips unrequested associations, thumbnails, and translated duplicates, keeping responses compact without manual field filtering.
+- **Paginate large result sets.** Return a bounded `limit` and let the agent increment `page` rather than returning everything at once.
+
+### Use consistent response shapes
+
+Extend `McpToolResponse` to get consistent `{"success": true, "data": ..., "_meta": ...}` and `{"success": false, "error": "..."}` envelopes. Agents learn one pattern and apply it everywhere.
+
+### Write tool descriptions for agent routing
+
+The `description` field on `#[McpTool]` is the only signal the agent uses to pick between similar tools. Write it for routing accuracy, not as documentation.
+
+**Lead with the trigger phrases the user will say.** Agents pattern-match on user wording. A description that opens with "The correct tool for count, sum, average, and other aggregate questions" routes correctly when the user asks "how many products?". A description that opens with "Run aggregations over any Shopware entity" does not.
+
+**Use negative phrasing to break ties.** When two tools share keywords (e.g., `shopware-entity-search` and `shopware-entity-aggregate` both work on entities), the agent will gravitate to the description that is more concrete. Spell out the contrast directly:
+
+> "Use this — NOT shopware-entity-search — for any 'how many', 'total value', or 'average' query. The search tool's `_meta.total` is pagination metadata, not a reporting count."
+
+**Do not reference other tools as prerequisites unless they truly are.** A description that ends with "Use shopware-foo-read to check current values first" trains the agent to call the read tool even when it should not. If a tool is genuinely a prerequisite, declare it with `#[McpToolDependsOn]` instead of with prose.
+
+**Mention the use cases the user will name.** If a prompt is "upload this image as a product cover", the description should contain the phrase "product cover" — and clarify that no extra parameter is required to satisfy that case. Otherwise the agent infers it cannot fulfill the request and returns no tool selection.
+
+**Make required parameters truly required.** Leave a parameter without a PHP default only if every prompt that should call this tool will include it. If the parameter is something the user often does not say (a sales channel UUID, a tax ID), give it a default of `''` or `null` and validate inside the method. Required-but-missing parameters cause some agents to refuse the tool call entirely instead of asking the user.
+
+**Test descriptions with an LLM, not just a code reviewer.** A description that reads well to a developer can route badly. Run a small fixture set through the agent you target (Claude, GPT-4o) and compare expected versus selected tool. The cost of a routing failure is the user does not get the tool they wanted; the cost of running the evaluation is a few hundred tokens.
+
+### Tool descriptions are baked into the DI container
+
+Shopware reads `#[McpTool]` attributes at container compile time. Changing a description requires `bin/console cache:clear` for the new text to reach the MCP endpoint. If your changes do not show up in `bin/console debug:mcp` or in `tools/list`, clear the container cache first.
+
+### Use the system prompt as the disambiguation override layer
+
+Some routing decisions cannot be solved at the description level alone — for example, when two tools have legitimate overlap in keywords or when an unusual phrasing is common in your domain. Use the `shopware-context` prompt (or your own `#[McpPrompt]`) for these:
+
+```text
+### Counting and aggregating (entity-aggregate vs entity-search)
+- "How many products are there?" → shopware-entity-aggregate (count aggregation), NOT entity-search
+- "List all orders from the last 7 days" → shopware-entity-search (date-range filter, NOT aggregate)
+- Rule: any question asking for a NUMBER (count, total, sum, average) → always shopware-entity-aggregate
+- Rule: any question asking to LIST, SHOW, or RETRIEVE records → always shopware-entity-search
+```
+
+The system prompt is fetched fresh on every session, so updates take effect without a cache clear. Tool descriptions are static and ship with the integration; the system prompt is the place to encode evolving guidance.
+
+### Write actionable error messages
+
+When a tool fails, the error message is the agent's only signal for recovery. Make it actionable:
+
+**Bad:** `"Error: not found"`
+
+**Good:** `"Order not found. Verify the order number with shopware-entity-search on the 'order' entity, or provide an orderId (UUID) instead."`
+
+## Resources
+
+[Resources](./mcp-concepts.md#resources) expose stable reference data without consuming tool-call budget.
+
+### Use resources for reference data
+
+Use MCP resources for lists of valid values and configuration lookups:
+
+- Entity names: `shopware://entities`
+- Valid state machine transitions: `shopware://state-machines`
+- Sales channel IDs: `shopware://sales-channels`
+- Business event names: `shopware://business-events`
+
+Without resources, the agent would need a tool call to discover each of these, adding latency and consuming tool-call budget.
+
+### Keep resources small
+
+Resources are loaded into the agent's context. A resource that returns thousands of lines defeats the purpose. If a data set is large or dynamic, use a tool with search and filter parameters instead.
+
+### When to use a resource vs. a tool
+
+Use a **resource** when:
+
+- The data is read-only and identified by a stable URI
+- No agent guidance is needed about when or how to use it
+
+Use a **tool** when:
+
+- You need the `description` field to guide agent behavior (resources have no description)
+- The operation has parameters beyond a single identifier
+- The data involves writes or dynamic queries
+
+When in doubt, prefer a tool: the description field is your primary lever for shaping agent behavior.
+
+## Prompts
+
+[Prompts](./mcp-concepts.md#prompts) set up the agent's domain understanding before any tool calls happen.
+
+### One system prompt, not many
+
+Provide a single well-structured system prompt that covers:
+
+1. Domain model overview (key entities and their relationships)
+2. Available tools grouped by purpose
+3. Common workflows as step-by-step recipes
+4. Error recovery guidance
+5. Which resources exist and when to use them
+
+Multiple prompts fragment the agent's understanding. A single coherent prompt is more effective.
+
+### Write workflow recipes, not tool documentation
+
+The tool descriptions (in `#[McpTool(description: '...')]`) explain what each tool does. The system prompt should explain **how to combine tools** for real tasks:
+
+```text
+### Ship an order
+1. shopware://state-machines resource to confirm "ship" is a valid delivery action
+2. shopware-order-state with orderNumber and deliveryAction: "ship", dryRun=true to preview
+3. shopware-order-state with dryRun=false to execute
+```
+
+Agents follow step-by-step recipes better than they infer multi-step plans from individual tool docs.
+
+### Mention resources explicitly
+
+Resources are easy to overlook. Explicitly tell the agent which resources exist and when to use them:
+
+```text
+## Available resources (read without a tool call)
+- shopware://entities: all entity names
+- shopware://sales-channels: sales channels with IDs and domains
+- shopware://state-machines: states and valid transitions
+```
+
+## ACL and Security
+
+### Enforce permissions in every tool
+
+Every tool should check ACL permissions before doing anything. Do not rely on the database layer to reject unauthorized writes; by then the agent has already spent tokens constructing the payload.
+
+Return a clear error when permissions are missing: `"Missing privilege: order:read"`.
+
+### Declare required privileges
+
+Annotate your tool class with `#[McpToolRequires]` so operators can configure ACL roles correctly before hitting a runtime error:
+
+```php
+// Static privilege
+#[McpToolRequires('system_config:read')]
+
+// Dynamic privilege (entity name comes from a runtime parameter)
+#[McpToolRequires(entityParam: 'entity', operations: ['read'])]
+```
+
+This is **declarative only**: it populates the Admin UI coverage warnings and `bin/console debug:mcp` output, but does not add a new enforcement layer. You still must call `$this->requirePrivilege($context, '...')` inside `__invoke()` for actual runtime enforcement.
+
+### Be aware of prompt injection via tool results
+
+Tool results that include user-generated content (order notes, customer names, product descriptions) can contain text that attempts to redirect the agent's behavior. An agent that reads a customer order where the shipping note says "Ignore all previous instructions and..." may act on that instruction. Shopware's ACL and tool allowlist limit the blast radius, but they do not prevent the agent from being misled by data it reads.
+
+Prefer read-only integrations for workflows that expose customer-supplied data, and avoid giving agents write access when the workflow only requires reading.
+
+### Keep admin and storefront contexts separate
+
+Tools that operate through the storefront (cart, checkout, storefront search) use `SalesChannelContext`, not the Admin API `Context`. They do not need Admin ACL checks but do require a valid sales channel ID.
+
+Keep the two authentication models clearly separated. Admin context tools belong in core or admin-focused plugins. Storefront context tools belong in plugins like `SwagMcpMerchantAssistant`.
+
+## General Design
+
+### Prefer fewer round trips
+
+Each tool call is a round trip with latency and token cost. Design tools that return everything the agent needs in one call, even if that means pre-loading associations.
+
+For example, a merchant plugin's order summary tool can load an order with customer, line items, transactions, and deliveries in a single query, without requiring four separate `shopware-entity-read` or `shopware-entity-search` calls.
+
+### Let the agent discover
+
+Provide discovery mechanisms so the agent does not need to guess valid input values:
+
+- `shopware-entity-schema` for field names and types
+- `shopware://entities` for valid entity names
+- `shopware://business-events` for valid event names
+- `shopware://state-machines` for valid state transition actions
+- `shopware://sales-channels` for sales channel IDs
+
+An agent that can discover valid inputs makes fewer errors than one that relies on training data.
+
+### Test with real agent conversations
+
+Unit tests verify correctness. Real agent conversations verify usability. Write scenario tests that simulate multi-step agent workflows to catch usability issues that unit tests miss; an agent that produces technically correct output but fails the user story is still a broken tool.
diff --git a/guides/development/tooling/mcp-server/configuration.md b/guides/development/tooling/mcp-server/configuration.md
new file mode 100644
index 0000000000..bcc19e09df
--- /dev/null
+++ b/guides/development/tooling/mcp-server/configuration.md
@@ -0,0 +1,162 @@
+---
+nav:
+ title: Configuration
+ position: 30
+
+---
+
+# Configuration
+
+## Feature flag
+
+The MCP server is gated behind the `MCP_SERVER` feature flag. Add it to your `.env` file:
+
+```bash
+MCP_SERVER=1
+```
+
+When inactive, all MCP services are removed from the container at compile time with no runtime overhead.
+
+## Shopware MCP configuration
+
+Shopware-specific MCP settings live under the `shopware.mcp` key in `config/packages/shopware.yaml` or any config file loaded in your application:
+
+```yaml
+shopware:
+ mcp:
+ allowed_tools: [] # Empty = all tools allowed. List tool names to restrict globally.
+ app_tool_timeout: 10 # Timeout in seconds for app webhook tool calls.
+```
+
+### Global tool allowlist
+
+`allowed_tools` is an installation-wide safety switch. It restricts which tools are available across **all** integrations at compile time:
+
+```yaml
+shopware:
+ mcp:
+ allowed_tools:
+ - shopware-entity-schema
+ - shopware-entity-search
+ - shopware-system-config-read
+```
+
+An empty list (the default) means no compile-time restriction; all registered tools are available. The per-integration allowlist in the Admin UI is the primary control for day-to-day access management.
+
+:::info Per-integration allowlist
+For production use, manage tool access per integration under **Settings → Integrations → Edit MCP Allowlist**. The global `allowed_tools` is a coarse safety switch, not the main product control.
+
+The per-integration allowlist is stored in the `integration.mcp_allowlist` column as a JSON object with `tools`, `resources`, and `prompts` keys. `null` per key means all capabilities of that type are allowed; a JSON array restricts to the listed names; an empty array `[]` means no capabilities of that type are accessible.
+
+**Scope:** the allowlist only applies to integration-authenticated requests, i.e. those using `sw-access-key` + `sw-secret-access-key` headers or an OAuth `client_credentials` token minted for an integration access key (`SWIA...`). Admin user bearer tokens (issued via password or refresh-token grant with `client_id = administration`) bypass the allowlist entirely and see all capabilities, subject only to the user's ACL.
+:::
+
+## MCP bundle configuration
+
+The underlying `symfony/mcp-bundle` is configured in `config/packages/mcp.php`. Shopware ships this file and it is loaded automatically when the `MCP_SERVER` feature flag is active. You do not need to create or modify it for standard setups.
+
+## Session store
+
+MCP sessions track an ongoing conversation across multiple requests. The client performs an `initialize` handshake first, then sends subsequent `tools/call` requests referencing that session ID. Session data must survive between requests.
+
+Shopware defaults to a file-based session store that writes to `%kernel.cache_dir%/mcp-sessions/`.
+
+| Store | Multi-worker | Multi-server | Backend |
+|---|---|---|---|
+| `file` (default) | No | No | `%kernel.cache_dir%/mcp-sessions/` |
+| `memory` | No | No | Per-process RAM |
+| `cache` (avoid) | No in dev | No | `cache.app` (ArrayAdapter in dev) |
+| `framework` (unusable in Shopware) | Yes | Yes | Requires active PHP session, not available because the Admin API is stateless |
+| Custom Redis store (recommended for production) | Yes | Yes | Redis / Valkey |
+
+### Production: Redis session store
+
+The file store works on a single machine. In a multi-server or Kubernetes environment, `initialize` and subsequent tool calls may land on different workers that do not share a local filesystem. Switch to Redis:
+
+**`config/services.yaml`:**
+
+```yaml
+services:
+ mcp.session.cache_psr16:
+ class: Symfony\Component\Cache\Psr16Cache
+ arguments: ['@cache.mcp_sessions']
+
+ mcp.session.store:
+ class: Mcp\Server\Session\Psr16SessionStore
+ arguments:
+ - '@mcp.session.cache_psr16'
+ - 3600 # TTL in seconds
+```
+
+**`config/packages/framework.yaml`:**
+
+```yaml
+framework:
+ cache:
+ pools:
+ cache.mcp_sessions:
+ adapter: cache.adapter.redis_tag_aware
+ provider: 'redis://your-redis-host:6379'
+ default_lifetime: 3600
+```
+
+If you already have a Redis/Valkey connection configured for Shopware, point `provider` at that same DSN to avoid opening a second connection.
+
+## ACL and permissions
+
+All MCP tool operations respect the integration's Admin API ACL role. To restrict what an MCP client can do:
+
+1. Create an ACL role in **Settings → Users & Permissions → Roles** with only the required permissions.
+2. Assign that role to the integration (omit `--admin` when creating via CLI).
+3. Under **Settings → Integrations → Edit MCP Allowlist**, enable only the tools needed for this integration.
+
+The Admin UI surfaces two helpers for getting ACL right:
+
+- The **Role detail** page shows a banner when the role is assigned to MCP-enabled integrations. Click **Show MCP tool requirements** to open the MCP Tool Requirements modal, which lists every privilege required by the allowed tools. Switch between **By Permission** (per-entity view with Grant buttons) and **By Tool** (per-tool view). Use **Grant all missing** to add the missing privileges in one click:
+
+
+
+- The **Edit MCP Allowlist** modal shows a coverage warning when the assigned role is missing privileges required by an allowed tool:
+
+
+
+## CLI: `debug:mcp`
+
+List all registered capabilities:
+
+```bash
+bin/console debug:mcp
+```
+
+The output shows four columns: **Name**, **Source**, **Dependencies**, and **Privileges**. It reads from the live server registry and covers core tools, plugin tools, and app tools in one view.
+
+Filter by capability type:
+
+```bash
+bin/console debug:mcp --tools # tools only
+bin/console debug:mcp --prompts # prompts only
+bin/console debug:mcp --resources # resources only
+```
+
+Drill into a single capability by name:
+
+```bash
+bin/console debug:mcp shopware-entity-search
+```
+
+See the registry from a specific integration's perspective (honors its per-integration allowlist):
+
+```bash
+bin/console debug:mcp --integration=SWIA...
+```
+
+If a tool is missing from this output, it is also missing from the live endpoint. Common causes:
+
+- Plugin is not installed or activated
+- Service tag is missing (`shopware.mcp.tool`)
+- `#[McpTool]` attribute is on `__invoke()` instead of the class
+- App tool's webhook URL is not reachable
+
+## Rate limiting
+
+The MCP endpoint applies per-integration rate limiting. Each set of credentials gets its own rate limit bucket. Rate limiting protects the endpoint from brute-force attempts and runaway agent loops.
diff --git a/guides/development/tooling/mcp-server/examples.md b/guides/development/tooling/mcp-server/examples.md
new file mode 100644
index 0000000000..13011c8000
--- /dev/null
+++ b/guides/development/tooling/mcp-server/examples.md
@@ -0,0 +1,234 @@
+---
+nav:
+ title: Examples
+ position: 50
+
+---
+
+# Examples
+
+Step-by-step workflows showing how the built-in tools and resources work together. Each section shows the sequence of tool calls an AI agent would make for a common task.
+
+## Exploring the data model
+
+**Discover all entity names:**
+Read the `shopware://entities` resource with no tool call needed.
+
+**Understand an entity's fields and associations:**
+
+```json
+Tool: shopware-entity-schema
+Input: {"entity": "product"}
+```
+
+Use the schema result to understand which fields and association names are valid before building search criteria.
+
+## Searching for products
+
+**Simple term search:**
+
+```json
+Tool: shopware-entity-search
+Input: {"entity": "product", "term": "shirt", "limit": 5}
+```
+
+**Filter with criteria JSON for active products with stock above 10, sorted by name:**
+
+```json
+Tool: shopware-entity-search
+Input: {
+ "entity": "product",
+ "criteria": "{\"filter\": [{\"type\": \"multi\", \"operator\": \"AND\", \"queries\": [{\"type\": \"equals\", \"field\": \"active\", \"value\": true}, {\"type\": \"range\", \"field\": \"stock\", \"parameters\": {\"gte\": 10}}]}], \"sort\": [{\"field\": \"name\", \"order\": \"ASC\"}]}"
+}
+```
+
+**Paginate through results:**
+
+```json
+Tool: shopware-entity-search
+Input: {"entity": "product", "limit": 10, "page": 3}
+```
+
+Continue incrementing `page` until `page * limit >= _meta.total`.
+
+**Select specific fields with explicit includes:**
+
+```json
+Tool: shopware-entity-search
+Input: {
+ "entity": "product",
+ "criteria": "{\"includes\": {\"product\": [\"id\", \"name\", \"productNumber\", \"stock\"], \"product_manufacturer\": [\"id\", \"name\"]}, \"associations\": {\"manufacturer\": {}}}"
+}
+```
+
+## Working with orders
+
+**Recent orders with line items and transactions:**
+
+```json
+Tool: shopware-entity-search
+Input: {
+ "entity": "order",
+ "criteria": "{\"sort\": [{\"field\": \"createdAt\", \"order\": \"DESC\"}], \"limit\": 5, \"associations\": {\"lineItems\": {}, \"transactions\": {}}}"
+}
+```
+
+**Preview shipping a delivery:**
+
+```json
+Tool: shopware-order-state
+Input: {"orderNumber": "10001", "deliveryAction": "ship", "dryRun": true}
+```
+
+**Execute the shipment:**
+
+```json
+Tool: shopware-order-state
+Input: {"orderNumber": "10001", "deliveryAction": "ship", "dryRun": false}
+```
+
+**Preview full cancellation (order + transaction + delivery):**
+
+```json
+Tool: shopware-order-state
+Input: {"orderNumber": "10001", "orderAction": "cancel", "transactionAction": "cancel", "deliveryAction": "cancel", "dryRun": true}
+```
+
+**Cancel order and refund paid transaction:**
+
+```json
+Tool: shopware-order-state
+Input: {"orderNumber": "10001", "orderAction": "cancel", "transactionAction": "refund", "deliveryAction": "cancel", "dryRun": false}
+```
+
+Read `shopware://state-machines` first to see valid actions for each state machine state.
+
+## System configuration
+
+**Read all listing settings:**
+
+```json
+Tool: shopware-system-config-read
+Input: {"key": "core.listing"}
+```
+
+**Preview a config change:**
+
+```json
+Tool: shopware-system-config-write
+Input: {"key": "core.listing.defaultSorting", "value": "\"price-asc\"", "dryRun": true}
+```
+
+**Apply the change:**
+
+```json
+Tool: shopware-system-config-write
+Input: {"key": "core.listing.defaultSorting", "value": "\"price-asc\"", "dryRun": false}
+```
+
+## Creating and updating entities
+
+**Create a product (preview first):**
+
+```json
+Tool: shopware-entity-upsert
+Input: {
+ "entity": "product",
+ "payload": "{\"name\": \"New Product\", \"productNumber\": \"SW-NEW-001\", \"stock\": 100, \"taxId\": \"\", \"price\": [{\"currencyId\": \"\", \"gross\": 29.99, \"net\": 25.20, \"linked\": true}]}",
+ "dryRun": true
+}
+```
+
+Use `shopware://currencies` to find `currencyId`. Use `shopware-entity-search` on `tax` to find `taxId`.
+
+**Update an existing entity:**
+
+```json
+Tool: shopware-entity-upsert
+Input: {
+ "entity": "product",
+ "payload": "{\"id\": \"\", \"stock\": 50}",
+ "dryRun": false
+}
+```
+
+## Analytics and reporting
+
+**Count opt-in newsletter subscribers:**
+
+```json
+Tool: shopware-entity-aggregate
+Input: {
+ "entity": "newsletter_recipient",
+ "aggregations": "[{\"type\": \"count\", \"name\": \"total\", \"field\": \"id\"}]",
+ "filters": "[{\"type\": \"equals\", \"field\": \"status\", \"value\": \"optIn\"}]"
+}
+```
+
+**Average order value for the current month:**
+
+```json
+Tool: shopware-entity-aggregate
+Input: {
+ "entity": "order",
+ "aggregations": "[{\"type\": \"avg\", \"name\": \"avgOrderValue\", \"field\": \"amountTotal\"}]",
+ "filters": "[{\"type\": \"range\", \"field\": \"orderDateTime\", \"parameters\": {\"gte\": \"2026-04-01\"}}]"
+}
+```
+
+**Orders by month (date histogram):**
+
+```json
+Tool: shopware-entity-aggregate
+Input: {
+ "entity": "order",
+ "aggregations": "[{\"type\": \"date-histogram\", \"name\": \"ordersByMonth\", \"field\": \"orderDateTime\", \"interval\": \"month\"}]"
+}
+```
+
+## Media and appearance
+
+**Upload a product image from a URL:**
+
+```json
+Tool: shopware-media-upload
+Input: {"url": "https://example.com/images/product.jpg", "productId": ""}
+```
+
+**Upload a logo and update the theme:**
+
+**Step 1:** Upload the image:
+
+```json
+Tool: shopware-media-upload
+Input: {"url": "https://example.com/logo.svg", "fileName": "shop-logo"}
+```
+
+**Step 2:** Use the returned `mediaId` to update the theme:
+
+```json
+Tool: shopware-theme-config
+Input: {
+ "salesChannelId": "",
+ "action": "update",
+ "config": "{\"sw-logo-desktop\": {\"value\": \"\"}}",
+ "dryRun": false
+}
+```
+
+Read `shopware://sales-channels` to find `salesChannelId`.
+
+## Using resources
+
+**Find sales channel IDs:**
+Read `shopware://sales-channels` to get all sales channels with IDs, names, types, and domains.
+
+**Discover available flow events and actions:**
+Read `shopware://business-events` and `shopware://flow-actions`.
+
+**Check valid state transitions for orders:**
+Read `shopware://state-machines` to see all states and valid transition actions for the order, delivery, and transaction state machines.
+
+## Merchant workflow tools
+
+Higher-level tools for merchant operations (order summaries, customer lookup, product creation with human-readable parameters, revenue and bestseller reports, storefront search, cart management, and checkout flows) are provided by the [SwagMcpMerchantAssistant](./shopware-extensions.md#swagmcpmerchantassistant) plugin under the `merchant-*` namespace. Refer to that plugin's documentation for examples.
diff --git a/guides/development/tooling/mcp-server/extending.md b/guides/development/tooling/mcp-server/extending.md
new file mode 100644
index 0000000000..10168fc79e
--- /dev/null
+++ b/guides/development/tooling/mcp-server/extending.md
@@ -0,0 +1,224 @@
+---
+nav:
+ title: Extending the MCP Server
+ position: 60
+
+---
+
+# Extending the MCP Server
+
+You can add three capability types to the MCP server: **tools**, **prompts**, and **resources**. There are three ways to implement them: as an **app** (webhook-based, works in Shopware Cloud), a **plugin** (in-process PHP, full DAL access), or a **Symfony bundle** (in-process, always active, no install lifecycle).
+
+This page is a quick reference. For step-by-step guides, see:
+
+- [Extending via Plugin](../../../plugins/plugins/mcp-server.md)
+- [Extending via App](../../../plugins/apps/mcp-server.md)
+
+**Working examples:**
+
+- [shopwareLabs/SwagMcpAdminUsers](https://github.com/shopwareLabs/SwagMcpAdminUsers): plugin with tools, prompts, and resources for admin user management
+- [shopwareLabs/McpHelloWorld](https://github.com/shopwareLabs/McpHelloWorld): minimal app with tools, prompts, and resources
+
+## Tools
+
+Tools let the AI agent call your code to take action or fetch data.
+
+
+
+
+
+Declare in `Resources/mcp.xml`, handle via webhook POST. The full name is auto-prefixed with the app name. Use `` to help the admin UI warn operators about missing ACL roles. Use `url="/..."` to route to an app script instead of an external URL.
+
+```xml
+
+
+
+ Synchronize orders with the ERP
+
+
+
+
+ order:read
+
+
+
+```
+
+Webhook response: return a JSON string, ideally following the `{"success": bool, "data": ...}` envelope.
+
+→ [Full app guide](../../../plugins/apps/mcp-server.md): webhook protocol, signature verification, lifecycle
+
+
+
+
+
+PHP class with `#[McpTool]` on the class, tagged `shopware.mcp.tool` in `services.xml`.
+
+```php
+#[McpTool(name: 'swag-my-plugin-orders', description: 'List recent orders.')]
+#[McpToolRequires('order:read')]
+class OrdersTool extends McpToolResponse
+{
+ public function __invoke(int $limit = 10): string
+ {
+ $context = $this->contextProvider->getContext();
+ if ($error = $this->requirePrivilege($context, 'order:read')) {
+ return $error;
+ }
+ // ... query and return $this->success([...])
+ }
+}
+```
+
+→ [Full plugin guide](../../../plugins/plugins/mcp-server.md): class structure, DI, pitfalls, verification
+
+
+
+
+
+Identical PHP class and `services.xml` as a plugin. Gate service loading in the bundle's `build()` method:
+
+```php
+public function build(ContainerBuilder $container): void
+{
+ if (!Feature::has('MCP_SERVER')) {
+ return;
+ }
+ // load services.xml with shopware.mcp.tool tag
+}
+```
+
+Bundles have no install/activate lifecycle. They are always active when registered in `config/bundles.php`.
+
+→ [Plugin guide](../../../plugins/plugins/mcp-server.md): the PHP class and services.xml patterns are identical
+
+
+
+
+
+## Prompts
+
+Prompts give the AI context and instructions before tool calls start. Return an array of `{role, content}` message objects.
+
+
+
+
+
+Declare in `Resources/mcp.xml`. Webhook body uses `prompt` instead of `tool`. Return an array of message objects:
+
+```xml
+
+
+
+ Background context for ERP-synced data
+
+
+```
+
+```json
+[{"role": "user", "content": "You are working with ERP-synced Shopware data..."}]
+```
+
+→ [Full app guide](../../../plugins/apps/mcp-server.md#webhook-protocol)
+
+
+
+
+
+PHP class with `#[McpPrompt]`, tagged `shopware.mcp.prompt`. No need to extend `McpToolResponse`.
+
+```php
+#[McpPrompt(name: 'swag-my-plugin-context', description: 'Context for My Plugin tools.')]
+class MyPluginContextPrompt
+{
+ public function __invoke(): array
+ {
+ return [
+ ['role' => 'user', 'content' => 'You have access to order management tools...'],
+ ];
+ }
+}
+```
+
+→ [Full plugin guide](../../../plugins/plugins/mcp-server.md#adding-prompts)
+
+
+
+
+
+Identical to the plugin pattern. Register with `shopware.mcp.prompt` and gate on the feature flag in `build()`.
+
+
+
+
+
+## Resources
+
+Resources expose read-only reference data via a URI without using up tool call budget. Return `{uri, mimeType, text}`.
+
+
+
+
+
+Declare in `Resources/mcp.xml` with both a `uri` (MCP identifier) and a `url` (webhook endpoint). Webhook body uses `resource` instead of `tool`:
+
+```xml
+
+
+
+ Current ERP connection status
+
+
+```
+
+```json
+{"uri": "my-erp://status", "mimeType": "application/json", "text": "{\"connected\": true}"}
+```
+
+→ [Full app guide](../../../plugins/apps/mcp-server.md#webhook-protocol)
+
+
+
+
+
+PHP class with `#[McpResource]`, tagged `shopware.mcp.resource`. Returns `array{uri, mimeType, text}`.
+
+```php
+#[McpResource(uri: 'swag-my-plugin://config', name: 'swag-my-plugin-config',
+ description: 'Current plugin configuration.')]
+class MyPluginConfigResource
+{
+ public function __invoke(): array
+ {
+ return [
+ 'uri' => 'swag-my-plugin://config',
+ 'mimeType' => 'application/json',
+ 'text' => json_encode(['mode' => 'production'], \JSON_THROW_ON_ERROR),
+ ];
+ }
+}
+```
+
+→ [Full plugin guide](../../../plugins/plugins/mcp-server.md#adding-resources)
+
+
+
+
+
+Identical to the plugin pattern. Register with `shopware.mcp.resource` and gate on the feature flag in `build()`.
+
+
+
+
+
+## Summary
+
+| | App | Plugin | Bundle |
+|---|---|---|---|
+| **Tool** | `` in `mcp.xml` + webhook handler | `#[McpTool]` class + `shopware.mcp.tool` tag | Same as plugin |
+| **Prompt** | `` in `mcp.xml` + webhook returns message array | `#[McpPrompt]` class + `shopware.mcp.prompt` tag | Same as plugin |
+| **Resource** | `` in `mcp.xml` + webhook returns `{uri, mimeType, text}` | `#[McpResource]` class + `shopware.mcp.resource` tag | Same as plugin |
+| **Context access** | Via `source.shopId` in webhook body | `McpContextProvider::getContext()` | Same as plugin |
+| **DAL access** | No (remote process) | Full | Full |
+| **Lifecycle** | App install/update | Plugin install/activate | Always active |
diff --git a/guides/development/tooling/mcp-server/getting-started.md b/guides/development/tooling/mcp-server/getting-started.md
new file mode 100644
index 0000000000..f6a2f732ba
--- /dev/null
+++ b/guides/development/tooling/mcp-server/getting-started.md
@@ -0,0 +1,171 @@
+---
+nav:
+ title: Getting Started
+ position: 20
+
+---
+
+# Getting Started
+
+This guide walks you through connecting an AI client to a Shopware shop using the built-in MCP server.
+
+## Prerequisites
+
+- Shopware 6.7 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`.
+- The `MCP_SERVER` feature flag enabled (see [Configuration](./configuration.md))
+
+## Step 1: Enable the feature flag
+
+Add the following to your `.env` file:
+
+```bash
+MCP_SERVER=1
+```
+
+This activates the MCP endpoint at `/api/_mcp` and registers all tools, resources, and prompts.
+
+## Step 2: Create an integration
+
+Create a Shopware integration for the MCP client. The integration provides the credentials the client will use to authenticate.
+
+```bash
+bin/console integration:create "My MCP Client" --admin
+```
+
+This outputs an access key and secret:
+
+```bash
+SHOPWARE_ACCESS_KEY_ID=SWIA...
+SHOPWARE_SECRET_ACCESS_KEY=...
+```
+
+:::info Restrict access
+The `--admin` flag grants full Admin API access. For production use, omit `--admin`, create a dedicated ACL role with only the required permissions, and assign it to the integration. See [Configuration](./configuration.md#acl-and-permissions) for details.
+:::
+
+## Step 3: Configure your AI client
+
+### Claude Desktop and Cursor
+
+Both clients use `"type": "streamable-http"`. Add the following config to the appropriate file:
+
+| Client | Config file |
+|---|---|
+| Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
+| Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
+| Cursor (project) | `.cursor/mcp.json` in your project root |
+| Cursor (user) | `~/.cursor/mcp.json` |
+
+```json
+{
+ "mcpServers": {
+ "shopware": {
+ "type": "streamable-http",
+ "url": "https://your-shop.example.com/api/_mcp",
+ "headers": {
+ "sw-access-key": "SWIA...",
+ "sw-secret-access-key": "..."
+ }
+ }
+ }
+}
+```
+
+### Claude Code
+
+Claude Code uses `"type": "http"` — the MCP spec calls the transport `"streamable-http"`, but Claude Code only accepts the shorter form. Create `.mcp.json` in your project root:
+
+```json
+{
+ "mcpServers": {
+ "shopware": {
+ "type": "http",
+ "url": "http://localhost:8000/api/_mcp",
+ "headers": {
+ "sw-access-key": "SWIA...",
+ "sw-secret-access-key": "..."
+ }
+ }
+ }
+}
+```
+
+Or register via CLI:
+
+```bash
+claude mcp add --transport http shopware http://localhost:8000/api/_mcp \
+ --header "sw-access-key: SWIA..." \
+ --header "sw-secret-access-key: ..."
+```
+
+:::warning Keep credentials out of version control
+Never commit `.mcp.json`, `.cursor/mcp.json`, or other files containing integration credentials. These files are already listed in the `.gitignore` of the Shopware project template.
+:::
+
+### Codex
+
+Codex stores MCP servers in `config.toml`, not in a JSON file. Add the server to `~/.codex/config.toml` (global) or `.codex/config.toml` in a trusted project:
+
+```toml
+[mcp_servers.shopware]
+url = "https://your-shop.example.com/api/_mcp"
+env_http_headers = { "sw-access-key" = "SHOPWARE_MCP_ACCESS_KEY", "sw-secret-access-key" = "SHOPWARE_MCP_SECRET_KEY" }
+enabled = true
+```
+
+The `url` field tells Codex this is an HTTP MCP server. No `type` field is needed. The `env_http_headers` values are environment variable names, not the credentials themselves. Export the actual values in your shell:
+
+```bash
+export SHOPWARE_MCP_ACCESS_KEY='SWIA...'
+export SHOPWARE_MCP_SECRET_KEY='...'
+```
+
+:::info Why not `codex mcp add --url`?
+The CLI shortcut supports bearer-token auth but not custom HTTP headers. For Shopware's `sw-access-key` / `sw-secret-access-key` auth, editing `config.toml` directly is required.
+:::
+
+## Step 4: First connection
+
+After adding the configuration, open or restart your AI client and look for the Shopware MCP server in the tools panel. The first connection may take a few seconds while Shopware boots its kernel and warms up caches. If the client shows "No tools" briefly, wait a moment and refresh.
+
+Verify the server is working with the CLI:
+
+```bash
+bin/console debug:mcp
+```
+
+This lists all registered tools, prompts, and resources, the same view the AI client sees.
+
+## Authentication methods
+
+### Integration credentials (recommended)
+
+Pass `sw-access-key` and `sw-secret-access-key` as HTTP headers. Credentials are valid as long as the integration exists with no token expiration or manual refresh.
+
+### Bearer token
+
+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.
+
+## Controlling which capabilities are available
+
+By default an admin integration can call all registered tools, resources, and prompts. To restrict access:
+
+1. Go to **Settings → Integrations**
+2. Open the context menu for your integration → **Edit MCP Allowlist**
+
+
+
+3. Disable the toggle for each capability type and select only the tools, resources, and prompts this integration should use
+
+
+
+When a tool is enabled, its declared dependencies are automatically included. For example, enabling `shopware-entity-delete` also enables `shopware-entity-search` and `shopware-entity-schema` because they are required for it to work.
+
+See [Configuration](./configuration.md) for the global `allowed_tools` safety switch and session store options.
+
+## Next steps
+
+- [Tools Reference](./tools-reference.md): explore all built-in tools and resources
+- [Examples](./examples.md): try common workflows end-to-end
+- [Troubleshooting](./troubleshooting.md): fix connection and permission issues
diff --git a/guides/development/tooling/mcp-server/index.md b/guides/development/tooling/mcp-server/index.md
new file mode 100644
index 0000000000..5c3b213a65
--- /dev/null
+++ b/guides/development/tooling/mcp-server/index.md
@@ -0,0 +1,87 @@
+---
+nav:
+ title: MCP Server
+ position: 2040
+
+---
+
+# MCP Server
+
+The Model Context Protocol (MCP) is an open standard that lets AI clients (Claude Desktop, Cursor, and Claude Code) talk to external systems through a structured, tool-based interface. Instead of copy-pasting data into a chat window, an AI agent can call a tool like `shopware-entity-search` directly and receive structured results it can reason about.
+
+Shopware ships a native MCP server as part of the core platform. It exposes an endpoint at `/api/_mcp` that any MCP-compatible AI client can connect to using integration credentials.
+
+:::info Experimental
+The MCP server is behind the `MCP_SERVER` feature flag and is considered experimental until Shopware 6.8. APIs and tool names may change before the stable release.
+:::
+
+## What the MCP server provides
+
+| Capability | Details |
+|---|---|
+| **HTTP endpoint** | `POST /api/_mcp` via Streamable HTTP transport |
+| **Authentication** | Integration credentials or OAuth bearer tokens |
+| **Authorization** | Full Admin API ACL enforcement per tool call |
+| **Tool allowlist** | Per-integration tool selection in Admin UI |
+| **Rate limiting** | Per-integration rate limiting |
+| **Discovery** | `bin/console debug:mcp` lists all registered capabilities |
+| **Extensibility** | Plugins, bundles, and apps can contribute custom tools, prompts, and resources |
+
+## Architecture
+
+**Core** owns the platform foundation: the HTTP endpoint, authentication bridge, ACL enforcement, rate limiting, capability discovery, and a set of low-level data access primitives (`shopware-entity-*`, `shopware-system-config-*`).
+
+**Plugins and Symfony bundles** run in-process with full access to DAL repositories, the service container, and the Shopware plugin lifecycle. They register tools, prompts, and resources via Symfony service tags. Shopware ships [SwagMcpMerchantAssistant](./shopware-extensions.md) (merchant workflow tools) and [SwagMcpDevTools](./shopware-extensions.md) (developer diagnostics) as examples of what plugins and bundles can do, but extension developers are free to build any capability as a plugin.
+
+**Apps** register capabilities declaratively in `Resources/mcp.xml`. Shopware calls the app's endpoint over HTTP with an HMAC-signed request at runtime. Use an app when your logic runs on a remote service, needs cloud compatibility, or should deploy independently from Shopware.
+
+### Plugin or App?
+
+**Use a plugin or Symfony bundle when:**
+
+- Your tool needs direct DAL / service container access
+- You want to ship via the Shopware Marketplace
+- Your capability is tightly coupled to Shopware's install / activate lifecycle
+
+**Use an app when:**
+
+- Your logic runs on a remote service (ERP, PIM, CRM, SaaS backend)
+- You need Cloud compatibility (apps work where plugins cannot)
+- Your capability should deploy and scale independently from Shopware
+
+## Spec coverage and known limitations
+
+Shopware's MCP server is built on `symfony/mcp-bundle`, which implements the [MCP specification (2025-11-25)](https://modelcontextprotocol.io/specification/2025-11-25). The bundle may not cover every feature in the latest spec revision, and some areas of the spec are only partially implemented. Known gaps:
+
+| Area | Status |
+|---|---|
+| `listChanged` notifications for tools, prompts, and resources | Not implemented |
+| Resource templates and resource subscriptions | Not implemented |
+| Protocol-level pagination | Not implemented (Shopware uses application-level `limit`/`page`) |
+| Completion utility for prompt/URI template arguments | Not implemented |
+| `structuredContent` and `isError` in tool results | Not used; Shopware uses its own `{"success": bool, ...}` envelope |
+| ACL checks on resources | Not implemented (resources are public within the authenticated session) |
+
+If a feature you need is missing from `symfony/mcp-bundle`, check its [repository](https://github.com/symfony/mcp-bundle) for open issues and pending releases before building a workaround.
+
+## In this section
+
+| Page | What you will find |
+|---|---|
+| [MCP Concepts](./mcp-concepts.md) | What tools, resources, and prompts are and when to use each |
+| [Getting Started](./getting-started.md) | Connect your first AI client to a Shopware shop |
+| [Tools Reference](./tools-reference.md) | All built-in tools, resources, and prompts with parameters |
+| [Configuration](./configuration.md) | Feature flag, allowlist, session store, rate limiting, CLI |
+| [Best Practices](./best-practices.md) | Design principles for building MCP tools |
+| [Extending the MCP Server](./extending.md) | Tools, prompts, and resources for all three extension types side by side |
+| [Examples](./examples.md) | Step-by-step workflows for common tasks |
+| [Troubleshooting](./troubleshooting.md) | Fix common connection and permission issues |
+| [Shopware Extensions](./shopware-extensions.md) | Copilot, SwagMcpMerchantAssistant, SwagMcpDevTools, ai-coding-tools |
+
+## Extension guides
+
+To extend the MCP server with your own tools:
+
+- [Extending via Plugin](../../plugins/plugins/mcp-server.md): in-process PHP with full DAL access, Shopware lifecycle
+- [Extending via App](../../plugins/apps/mcp-server.md): webhook-based, works in Shopware Cloud
+- [Side-by-side comparison](./extending.md): all three capability types for all three extension types
diff --git a/guides/development/tooling/mcp-server/mcp-concepts.md b/guides/development/tooling/mcp-server/mcp-concepts.md
new file mode 100644
index 0000000000..c8ee56574c
--- /dev/null
+++ b/guides/development/tooling/mcp-server/mcp-concepts.md
@@ -0,0 +1,106 @@
+---
+nav:
+ title: MCP Concepts
+ position: 10
+
+---
+
+# MCP Concepts
+
+The Model Context Protocol (MCP) defines three building blocks that servers can expose: **tools**, **resources**, and **prompts**. Knowing what each one does and when to use it is the starting point for working with Shopware's MCP server or building your own extensions.
+
+The full specification is available at [modelcontextprotocol.io](https://modelcontextprotocol.io/specification/).
+
+## Tools
+
+A tool is a callable function that the AI agent invokes to take an action or retrieve data. The agent reads the tool's name and description and decides when to call it based on the user's request. Tools accept typed parameters and return structured results.
+
+```mermaid
+sequenceDiagram
+ participant Client as AI Client
+ participant SW as Shopware
+ Client->>SW: call tool with arguments
+ SW-->>Client: structured result
+```
+
+**When to use a tool:**
+
+- The agent needs to decide when and whether to call it
+- The operation requires parameters beyond a simple identifier
+- The capability involves writes, state changes, or dynamic queries
+- You want the tool description to guide agent behavior (for example, "use this before building search criteria")
+
+**Shopware examples:** `shopware-entity-search`, `shopware-entity-upsert`, `shopware-order-state`
+
+## Resources
+
+A resource is a read-only data endpoint identified by a URI. Resources expose stable reference data that the agent or client can fetch without any decision-making. Unlike tools, resources have no description to guide the agent; they are simply data at a known address.
+
+```text
+shopware://entities → list of all entity names
+shopware://sales-channels → sales channels with IDs and domains
+shopware://state-machines → states and valid transitions per machine
+```
+
+**When to use a resource:**
+
+- The data is read-only and identified by a stable, predictable URI
+- The information is needed frequently and does not change mid-session (entity names, currencies, languages)
+- You want the client or agent to load context without consuming tool-call budget
+- The dataset is small enough to fit in context (for large or dynamic data, use a tool with filter parameters instead)
+
+**Shopware examples:** `shopware://entities`, `shopware://business-events`, `shopware://flow-actions`
+
+## Prompts
+
+A prompt is a named instruction template that sets up the AI's behavior for a specific domain. Prompts explain how to combine tools for real tasks, describe entity relationships, and provide error recovery guidance. They are user-triggered (the client requests a specific prompt) rather than agent-driven.
+
+```mermaid
+sequenceDiagram
+ participant Client as AI Client
+ participant SW as Shopware
+ Client->>SW: request prompt by name
+ SW-->>Client: system instructions
+```
+
+**When to use a prompt:**
+
+- You want to provide step-by-step sequences for common tasks
+- You want to explain domain-specific concepts (entity relationships, state machine semantics)
+- You want to mention available resources and when to use them
+
+**Shopware example:** `shopware-context` explains the DAL criteria format, core entity relationships, available tools by purpose, and error recovery guidance.
+
+## Comparison
+
+| | Tool | Resource | Prompt |
+|---|---|---|---|
+| **Invocation** | Agent decides when to call | Client or agent fetches | User selects in client |
+| **Parameters** | Yes, typed and named | URI only | Optional arguments |
+| **Can write data** | Yes | No | No |
+| **Has description to guide agent** | Yes | No | Yes |
+| **Counts as a tool call** | Yes | No | No |
+| **Best for** | Actions, queries with parameters | Reference lookups, pre-loaded context | System instructions, workflow recipes |
+
+## How they work together
+
+A well-designed MCP server uses all three:
+
+1. **Resources** provide reference data (entity names, sales channel IDs, state transitions) so the agent does not waste tool calls on lookups.
+2. **Prompts** explain how to combine tools for real tasks and remind the agent which resources exist.
+3. **Tools** do the actual work: search, read, write, transition state.
+
+For example, to ship an order the agent would:
+
+1. Read `shopware://state-machines` to confirm `ship` is a valid delivery action
+2. Call `shopware-order-state` with `deliveryAction: "ship"` and `dryRun: true` to preview
+3. Call again with `dryRun: false` to execute
+
+The resource saved one tool call; the tool did the work; the prompt explained the pattern.
+
+## Next steps
+
+- [Getting Started](./getting-started.md): connect an AI client to your Shopware shop
+- [Tools Reference](./tools-reference.md): all built-in tools, resources, and prompts
+- [Best Practices](./best-practices.md): design principles for building your own tools
+- [Extending via Plugin](../../plugins/plugins/mcp-server.md): add custom tools to Shopware
diff --git a/guides/development/tooling/mcp-server/shopware-extensions.md b/guides/development/tooling/mcp-server/shopware-extensions.md
new file mode 100644
index 0000000000..64704a3ddf
--- /dev/null
+++ b/guides/development/tooling/mcp-server/shopware-extensions.md
@@ -0,0 +1,72 @@
+---
+nav:
+ title: Shopware Extensions
+ position: 80
+
+---
+
+# Shopware MCP Extensions
+
+Shopware ships several MCP-related projects beyond the core platform. This page covers what each one does, who it is for, and how to get it.
+
+## Shopware Copilot
+
+Shopware Copilot is the AI assistant embedded directly in the Shopware Administration. It is the primary production consumer of the MCP server; merchants can ask questions, get recommendations, and carry out store management tasks through a conversational interface without leaving the Admin.
+
+Copilot connects to the shop's `/api/_mcp` endpoint under the hood. No extra configuration is needed to use Copilot: once the MCP server is enabled, Copilot automatically has access to the registered tools and can use them on behalf of the merchant.
+
+## SwagMcpMerchantAssistant
+
+**Repository:** `shopware/SwagMcpMerchantAssistant`\
+**Distribution:** Shopware Marketplace\
+**Tool prefix:** `merchant-*`
+
+A Shopware plugin that extends the MCP server with merchant-focused workflow tools. Where core tools are DAL primitives (search, read, upsert, delete), the MerchantAssistant tools are high-level workflows with human-readable parameters designed for merchant operators.
+
+### Available tools
+
+| Tool | What it does |
+|---|---|
+| `merchant-order-summary` | Formatted overview of an order including customer, line items, totals, and current state |
+| `merchant-customer-lookup` | Find a customer by email address, customer number, or UUID |
+| `merchant-product-create` | Create a product with natural parameters (gross price, tax rate, currency code) and resolve IDs internally |
+| `merchant-revenue-report` | Revenue breakdown for a date range, grouped by day, week, or month |
+| `merchant-bestseller-report` | Top products by quantity sold for a date range |
+| `merchant-storefront-search` | Customer-facing product search with resolved prices and customer-specific pricing |
+| `merchant-cart-manage` | Create, inspect, and modify a cart |
+| `merchant-cart-checkout` | Complete a cart checkout |
+| `merchant-checkout-methods` | List available payment and shipping methods for a sales channel |
+
+All write tools default to `dryRun=true`.
+
+## SwagMcpDevTools
+
+**Repository:** `shopware/SwagMcpDevTools`\
+**Distribution:** Shopware CLI (`shopware extension install SwagMcpDevTools`)\
+**Tool prefix:** `swag-dev-tools-*`
+
+A Symfony bundle (not a plugin) that adds developer diagnostic tools to the MCP server. It targets environments where host-side tools cannot reach the Shopware instance directly, for example SaaS environments, staging servers, and on-premise deployments.
+
+### Diagnostic tools
+
+| Tool | What it does |
+|---|---|
+| `swag-dev-tools-log-stream` | Read recent Monolog entries from disk, filtered by level and timestamp |
+| `swag-dev-tools-log-search` | Search log files for substring matches, with optional level and filename filters |
+
+Access is read-only and controlled by MCP authentication and the per-integration allowlist. Sensitive fields (passwords, tokens, auth headers) are automatically redacted; values longer than 300 characters are truncated.
+
+Install via one line in `config/bundles.php` after requiring the package, which is simpler than plugin lifecycle management.
+
+## ai-coding-tools
+
+**Repository:** `shopwareLabs/ai-coding-tools`\
+**Distribution:** Shopware Labs (experimental)
+
+A separate project for **developer-facing** local MCP tools: code generation, testing, linting, cache clearing, and deployment operations. This is distinct from the shop's `/api/_mcp` endpoint.
+
+Where `/api/_mcp` gives AI clients access to shop data and merchant operations, `ai-coding-tools` gives AI coding assistants in your IDE access to the Shopware development environment itself. Use this when you want an AI coding assistant in your IDE to run `bin/console`, execute tests, or generate boilerplate code against a local Shopware installation.
+
+:::info Experimental
+`ai-coding-tools` lives under the `shopwareLabs` GitHub organization and is considered experimental. APIs may change without notice.
+:::
diff --git a/guides/development/tooling/mcp-server/tools-reference.md b/guides/development/tooling/mcp-server/tools-reference.md
new file mode 100644
index 0000000000..cb203d3f57
--- /dev/null
+++ b/guides/development/tooling/mcp-server/tools-reference.md
@@ -0,0 +1,307 @@
+---
+nav:
+ title: Tools Reference
+ position: 70
+
+---
+
+# Tools Reference
+
+This page documents all built-in tools, resources, and the system prompt provided by Shopware's MCP server.
+
+## Response format
+
+All core tools return a consistent JSON envelope via `McpToolResponse`:
+
+**Success:**
+
+```json
+{"success": true, "data": [...], "_meta": {"total": 42, "page": 1, "limit": 25}}
+```
+
+**Error:**
+
+```json
+{"success": false, "error": "Human-readable message telling the agent what to do next"}
+```
+
+The `_meta` field is optional and used for pagination context, dry-run status, and sales channel scope.
+
+## Dry-run behavior
+
+All write tools default to `dryRun=true`. In dry-run mode:
+
+- Changes are validated and previewed but **not persisted**
+- A database transaction is opened, the operation runs, then the transaction is rolled back
+- Flow Builder actions are suppressed (`SKIP_TRIGGER_FLOW`)
+- The response shows what would have happened
+
+Pass `dryRun=false` explicitly to commit the change.
+
+## Tool dependency graph
+
+When you enable a tool in the integration's tool allowlist, its declared dependencies are automatically included:
+
+| Tool | Depends on |
+|---|---|
+| `shopware-entity-read` | `shopware-entity-schema` |
+| `shopware-entity-search` | `shopware-entity-schema` |
+| `shopware-entity-aggregate` | `shopware-entity-schema` |
+| `shopware-entity-upsert` | `shopware-entity-schema` |
+| `shopware-entity-delete` | `shopware-entity-search` |
+| `shopware-system-config-write` | `shopware-system-config-read` |
+
+## Read Tools
+
+### shopware-entity-schema
+
+Get the field and association schema of any Shopware entity. Use this first to discover field names and types before building search criteria.
+
+**Parameters:**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `entity` | string | yes | Entity name (e.g., `product`, `order`, `customer`) |
+
+**Example:**
+
+```json
+{"entity": "product"}
+```
+
+**ACL:** None (schema introspection only).
+
+### shopware-entity-search
+
+Search entity records using Admin API criteria. Returns entity rows with pagination metadata. Does **not** return aggregation results; use `shopware-entity-aggregate` for counts, averages, and other metrics.
+
+**Response optimization:** When no `includes` are specified in the criteria, responses are automatically trimmed to scalar fields and explicitly requested associations. This strips thumbnails, extensions, and translated duplicates, keeping responses well within the 100 KB limit.
+
+**Parameters:**
+
+| Name | Type | Required | Default | Description |
+|---|---|---|---|---|
+| `entity` | string | yes | — | Entity name |
+| `criteria` | string | no | `{}` | JSON criteria object |
+| `limit` | int | no | 25 | Results per page |
+| `page` | int | no | 1 | Page number |
+| `term` | string | no | — | Full-text search term |
+
+**Criteria supports:** `filter`, `sort`, `limit`, `page`, `associations`, `includes`, `fields`, `ids`, `term`, `query`, `post-filter`, `grouping`, `total-count-mode`. Top-level `limit`, `page`, and `term` parameters override values in criteria JSON.
+
+**Pagination:** The response `_meta` block always includes `total`, `page`, and `limit`. Iterate by incrementing `page` until `page * limit >= total`.
+
+**Examples:**
+
+```json
+{"entity": "product", "term": "shirt", "limit": 5}
+```
+
+```json
+{"entity": "product", "criteria": "{\"filter\": [{\"type\": \"range\", \"field\": \"stock\", \"parameters\": {\"lte\": 5}}], \"sort\": [{\"field\": \"stock\", \"order\": \"ASC\"}]}"}
+```
+
+**ACL:** `{entity}:read`
+
+### shopware-entity-aggregate
+
+Run aggregations over any entity without fetching records. Always returns zero entity rows (`limit: 0` internally), so the response size is bounded regardless of dataset size.
+
+Use this instead of `shopware-entity-search` when you need counts, averages, sums, or bucket distributions. Mixing entity rows with aggregations in one response would risk exceeding the 100 KB limit.
+
+**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 |
+
+**Supported aggregation types:** `avg`, `sum`, `min`, `max`, `count`, `terms`, `date-histogram`, `range`, `filter`, `entity`
+
+**Response:** `{"success": true, "data": {"aggregations": {"": {...}}}}`
+
+**Examples:**
+
+```json
+{"entity": "order", "aggregations": "[{\"type\": \"avg\", \"name\": \"avgOrderValue\", \"field\": \"amountTotal\"}]"}
+```
+
+```json
+{
+ "entity": "newsletter_recipient",
+ "aggregations": "[{\"type\": \"count\", \"name\": \"total\", \"field\": \"id\"}]",
+ "filters": "[{\"type\": \"equals\", \"field\": \"status\", \"value\": \"optIn\"}]"
+}
+```
+
+**ACL:** `{entity}:read`
+
+### shopware-entity-read
+
+Read a single entity by its UUID. Use when you already have an entity ID; for searching by other fields, use `shopware-entity-search`.
+
+**Parameters:**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `entity` | string | yes | Entity name |
+| `id` | string | yes | Entity UUID |
+| `criteria` | string | no | JSON criteria for associations |
+
+**ACL:** `{entity}:read`
+
+### shopware-system-config-read
+
+Read system configuration values. Pass a domain prefix to retrieve all keys under it, or a full key for a single value.
+
+**Parameters:**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `key` | string | yes | Config key or domain prefix (e.g., `core.listing`) |
+| `salesChannelId` | string | no | Scope the read to a specific sales channel |
+
+**ACL:** `system_config:read`
+
+## Write Tools
+
+Most write tools default to `dryRun=true`. Always preview before committing. `shopware-media-upload` is the exception — it performs the upload immediately and has no dry-run mode.
+
+### shopware-entity-upsert
+
+Create or update entity data. Use `shopware-entity-schema` to discover required fields first.
+
+ACL is checked per item: payloads **without** an `id` require `{entity}:create`; payloads **with** an `id` require `{entity}:update`. An integration with only one privilege can use the tool for that subset.
+
+**Parameters:**
+
+| Name | Type | Required | Default | Description |
+|---|---|---|---|---|
+| `entity` | string | yes | — | Entity name |
+| `payload` | string | yes | — | JSON object or array of objects. Omit `id` to create; include `id` to update. |
+| `dryRun` | bool | no | `true` | Preview without persisting |
+
+**ACL:** `{entity}:create` and/or `{entity}:update`
+
+### shopware-entity-delete
+
+Delete entities by their UUIDs. Returns a cascade impact preview in dry-run mode.
+
+**Parameters:**
+
+| Name | Type | Required | Default | Description |
+|---|---|---|---|---|
+| `entity` | string | yes | — | Entity name |
+| `ids` | string | yes | — | JSON array of UUIDs |
+| `dryRun` | bool | no | `true` | Preview cascade effects |
+
+**ACL:** `{entity}:delete`
+
+### shopware-system-config-write
+
+Update a system configuration value. Shows a before/after diff in dry-run mode.
+
+:::warning Sensitive values
+`system_config` can contain SMTP credentials, payment API keys, and other sensitive data. Restrict this tool's access accordingly.
+:::
+
+**Parameters:**
+
+| Name | Type | Required | Default | Description |
+|---|---|---|---|---|
+| `key` | string | yes | — | Full config key |
+| `value` | string | yes | — | New value (JSON-encoded for complex types) |
+| `salesChannelId` | string | no | — | Scope to a sales channel |
+| `dryRun` | bool | no | `true` | Preview the diff |
+
+**ACL:** `system_config:update`
+
+### shopware-order-state
+
+Change the state of an order, its transactions, and/or its deliveries in one call. Provide at least one action parameter. See the `shopware://state-machines` resource for valid actions per state machine.
+
+**Parameters:**
+
+| Name | Type | Required | Default | Description |
+|---|---|---|---|---|
+| `orderNumber` | string | one of | — | Order number (e.g., `10001`). Mutually exclusive with `orderId` |
+| `orderId` | string | one of | — | Order UUID. Mutually exclusive with `orderNumber` |
+| `orderAction` | string | no | — | Action for the order (e.g., `cancel`, `process`, `complete`, `reopen`) |
+| `transactionAction` | string | no | — | Action for all transactions (e.g., `cancel`, `paid`, `refund`) |
+| `deliveryAction` | string | no | — | Action for all deliveries (e.g., `cancel`, `ship`, `retour`, `reopen`) |
+| `dryRun` | bool | no | `true` | Preview transitions without executing |
+
+**ACL:** `order:read` always; `order:update`, `order_transaction:update`, `order_delivery:update` per action on commit.
+
+**Examples:**
+
+```json
+{"orderNumber": "10001", "deliveryAction": "ship", "dryRun": true}
+```
+
+```json
+{"orderNumber": "10001", "orderAction": "cancel", "transactionAction": "refund", "deliveryAction": "cancel", "dryRun": false}
+```
+
+### shopware-media-upload
+
+Upload a media file from a public URL. Optionally assign it as the cover image of a product.
+
+**Parameters:**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `url` | string | yes | Public URL of the file to download |
+| `fileName` | string | no | Desired file name (defaults to basename of URL) |
+| `mediaFolderId` | string | no | UUID of the media folder |
+| `productId` | string | no | If provided, assigns the uploaded media as the product's cover image |
+
+**ACL:** `media:create`; additionally `product:update` when `productId` is provided.
+
+## Storefront Bundle Tools
+
+### shopware-theme-config
+
+Read or update theme configuration for a sales channel. Manages brand colors, logos, and fonts. This tool lives in the Storefront bundle and depends on `ThemeService`.
+
+**Parameters:**
+
+| Name | Type | Required | Default | Description |
+|---|---|---|---|---|
+| `salesChannelId` | string | yes (validated at runtime) | `""` | Sales channel UUID. Optional in the JSON schema so agents do not refuse the call when the user has not provided one; the tool returns an actionable error if empty. |
+| `action` | string | no | `"get"` | `get` or `update` |
+| `config` | string | no | `"{}"` | For `update`: JSON key-value pairs, e.g. `{"sw-color-brand-primary": {"value": "#0000ff"}}` |
+| `dryRun` | bool | no | `true` | For `update`: preview without persisting |
+
+**ACL:** `theme:read` for `get`; `theme:update` for `update`.
+
+## Resources
+
+Resources are static reference data available via MCP resource URIs. They require no tool call and do not consume tool-call budget.
+
+| URI | Description |
+|---|---|
+| `shopware://entities` | All registered entity names |
+| `shopware://sales-channels` | All sales channels with IDs, names, types, and domains |
+| `shopware://currencies` | All currencies with ISO codes, symbols, and factors |
+| `shopware://languages` | All languages with locale codes |
+| `shopware://state-machines` | All state machines with states and valid transitions |
+| `shopware://business-events` | All events that can trigger flows |
+| `shopware://flow-actions` | All flow actions available in Flow Builder |
+| `shopware://extensions` | Active plugins and bundles with additional MCP tools, including tool prefix and install command |
+
+## Prompts
+
+### shopware-context
+
+The system prompt that explains how to work with the Shopware MCP server. It covers:
+
+- Core entity relationships (product, order, customer, category)
+- How to use the DAL criteria format
+- Available tools grouped by purpose
+- Common multi-step workflows as recipes
+- Error recovery guidance
+- Best practices (use `shopware-entity-schema` first, use `dryRun`, use `includes`)
+
+Clients can request this prompt to initialize an AI session with Shopware domain knowledge.
diff --git a/guides/development/tooling/mcp-server/troubleshooting.md b/guides/development/tooling/mcp-server/troubleshooting.md
new file mode 100644
index 0000000000..955e2eff06
--- /dev/null
+++ b/guides/development/tooling/mcp-server/troubleshooting.md
@@ -0,0 +1,163 @@
+---
+nav:
+ title: Troubleshooting
+ position: 60
+
+---
+
+# Troubleshooting
+
+## Quick reference
+
+| Symptom | Likely cause | Fix |
+|---|---|---|
+| `Authentication failed. Configure your MCP client...` | Wrong or missing credentials | Check `sw-access-key` / `sw-secret-access-key` in your client config |
+| `Tool "X" is not in the allowlist...` | Tool not enabled for this integration | Settings → Integrations → Edit MCP Allowlist → enable the tool |
+| `Resource "X" is not in the allowlist...` | Resource not enabled for this integration | Settings → Integrations → Edit MCP Allowlist → enable the resource |
+| `Prompt "X" is not in the allowlist...` | Prompt not enabled for this integration | Settings → Integrations → Edit MCP Allowlist → enable the prompt |
+| `Missing privilege: {entity}:read` | Integration role lacks the permission | Assign an ACL role with the required privilege, or use `--admin` |
+| Tool missing from `tools/list` | Blocked by allowlist | Enable the tool under Edit MCP Allowlist |
+| No tools in `tools/list` at all | Allowlist is an empty array | "All tools" toggle is OFF with nothing selected. Enable tools or turn the toggle back ON |
+| Admin integration but tool still blocked | Per-integration allowlist is set | Admin bypasses ACL (layer 3) only; the allowlist (layer 2) still applies regardless |
+| All capabilities visible when using a user login token | Bearer tokens bypass the allowlist | By design. Admin user login tokens (`client_id = administration`) are not subject to allowlist filtering. Only integration credentials (`sw-access-key` / `sw-secret-access-key`) are. |
+| Tool missing entirely | Plugin inactive, missing tag, or attribute misplaced | Check `bin/console debug:mcp` |
+| `ECONNREFUSED` or "fetch failed" | Server not running or wrong URL | Start Shopware and verify the URL in your client config |
+| Client shows "Needs authentication" after failed connect | Client fell back to `/register` OAuth endpoint | Credentials or URL are wrong. Verify your `sw-access-key` and `sw-secret-access-key` and that the URL ends with `/api/_mcp` |
+
+## Connection issues
+
+### ECONNREFUSED or "fetch failed"
+
+Your MCP client cannot reach the Shopware server.
+
+1. Start the Shopware server (Docker, ddev, or your usual local setup).
+2. Verify the URL in your MCP client config matches how you access the shop (host and port).
+3. For local development, confirm the shop is reachable at the same URL in a browser before retrying the MCP client.
+
+### Client shows "Needs authentication" or falls back to `/register`
+
+Some MCP clients (e.g. Cursor) follow the OAuth 2.0 dynamic client registration flow when the primary connection fails. They automatically POST to `{server-origin}/register`, expecting a JSON error response. Shopware handles this and returns a structured error so the client can display a "Needs authentication" state instead of an opaque connection failure.
+
+If you see this state, the root cause is in your credentials or URL, not the fallback itself:
+
+1. Confirm your `sw-access-key` starts with `SWIA` (integration access key, not a user or sales channel key).
+2. Confirm your `sw-secret-access-key` matches the secret shown when the integration was created.
+3. Confirm the URL in your client config ends with `/api/_mcp` (not `/api/_action/mcp/tools` or the shop root).
+
+## Client-specific issues
+
+### Claude Code: "Does not adhere to MCP server configuration schema"
+
+Claude Code requires `"type": "http"` in `.mcp.json`. The MCP spec transport name is `"streamable-http"`, which other clients accept, but Claude Code only accepts the shorter `"http"` form. Change your config:
+
+```json
+{
+ "mcpServers": {
+ "shopware": {
+ "type": "http",
+ "url": "http://localhost:8000/api/_mcp",
+ "headers": {
+ "sw-access-key": "SWIA...",
+ "sw-secret-access-key": "..."
+ }
+ }
+ }
+}
+```
+
+### Cursor: schema lookups on every tool call
+
+Cursor reads MCP tool schema descriptor files before every tool call, adding a visible file-lookup step to each interaction. You can eliminate this by embedding tool schemas directly in a Cursor rule:
+
+1. Create `.cursor/rules/shopware-mcp-tools.mdc` in your project root.
+2. Set `alwaysApply: true` in the front matter.
+3. List all tool schemas inline so Cursor can skip the descriptor lookup.
+
+**Example rule file:**
+
+```markdown
+---
+description: Shopware MCP tool schemas, call tools directly without reading schema files first
+alwaysApply: true
+---
+
+# Shopware MCP Tools
+
+When using Shopware MCP tools, call them directly without reading schema files first.
+
+## shopware-entity-search
+Search entity records. Required: `entity` (string). Optional: `criteria` (JSON string), `limit` (int, default 25), `page` (int, default 1), `term` (string).
+
+## shopware-entity-schema
+Get field/association schema of an entity. Required: `entity` (string).
+
+## shopware-entity-read
+Read a single entity by UUID. Required: `entity` (string), `id` (string). Optional: `criteria` (JSON string).
+
+## shopware-entity-upsert
+Create or update entity data. Required: `entity` (string), `payload` (JSON string). Optional: `dryRun` (bool, default true).
+
+## shopware-entity-delete
+Delete entities. Required: `entity` (string), `ids` (JSON array string). Optional: `dryRun` (bool, default true).
+
+## shopware-entity-aggregate
+Run aggregations. Required: `entity` (string), `aggregations` (JSON string). Optional: `filters` (JSON string).
+
+## shopware-order-state
+Change order/transaction/delivery state. One of `orderNumber` or `orderId` required. Optional: `orderAction`, `transactionAction`, `deliveryAction` (strings), `dryRun` (bool, default true).
+
+## shopware-system-config-read
+Read system config. Required: `key` (string). Optional: `salesChannelId` (string).
+
+## shopware-system-config-write
+Write system config. Required: `key` (string), `value` (string). Optional: `salesChannelId` (string), `dryRun` (bool, default true).
+
+## shopware-media-upload
+Upload media from URL. Required: `url` (string). Optional: `fileName` (string), `mediaFolderId` (string), `productId` (string).
+
+## shopware-theme-config
+Read/update theme config. Required: `salesChannelId` (string), `action` ("get" or "update"). Optional: `config` (JSON string), `dryRun` (bool, default true).
+```
+
+Add any additional tools from installed plugins to this file. Tools not listed still work. Cursor falls back to reading the descriptor file.
+
+## Tool registration issues
+
+### Tool missing from `bin/console debug:mcp`
+
+If a tool does not appear in `debug:mcp` output, it will also be missing from the live endpoint.
+
+**For plugin tools:**
+
+- Confirm the plugin is installed and activated: `bin/console plugin:list`
+- Confirm the service has `` in `services.xml`
+- Confirm `#[McpTool]` is on the **class**, not on `__invoke()`
+- Run `bin/console cache:clear` after changes
+
+**For core / bundle tools:**
+
+- Confirm the directory is listed in `config/packages/mcp.php` `scan_dirs`
+- Confirm the service has the correct DI tag (`mcp.tool` for in-tree bundles)
+
+## Security layers
+
+The MCP endpoint passes every request through three independent security layers. A request must clear all three before a capability executes:
+
+```mermaid
+flowchart LR
+ A[Request] --> B{Authentication}
+ B -- fail --> E1["Authentication failed"]
+ B -- pass --> C{MCP Allowlist}
+ C -- fail --> E2["Not in allowlist"]
+ C -- pass --> D{ACL}
+ D -- fail --> E3["Missing privilege"]
+ D -- pass --> F([Capability executes])
+```
+
+**Authentication (Layer 1):** Pass `sw-access-key` and `sw-secret-access-key` headers. Obtain credentials from Settings → Integrations.
+
+**MCP Allowlist (Layer 2):** Each integration has its own allowlist covering tools, resources, and prompts. `null` per type means all capabilities of that type are accessible; an empty array `[]` means none are accessible. Configure via Settings → Integrations → Edit MCP Allowlist. The `admin` flag on an integration does **not** bypass the allowlist; it only bypasses layer 3 (ACL).
+
+This layer is only enforced for integration-authenticated requests (`sw-access-key` + `sw-secret-access-key`, or OAuth `client_credentials` for an integration key). Admin user bearer tokens (password / refresh-token grant, `client_id = administration`) skip this layer and see all capabilities.
+
+**ACL (Layer 3):** Even if a capability is in the allowlist, the integration's ACL role must have the required entity-level permissions.
diff --git a/guides/development/tooling/using-watchers.md b/guides/development/tooling/using-watchers.md
index 724bd76f51..2c18050023 100644
--- a/guides/development/tooling/using-watchers.md
+++ b/guides/development/tooling/using-watchers.md
@@ -1,6 +1,6 @@
---
nav:
- title: Hot Module Reloading using watchers
+ title: Hot Module Replacement
position: 10
---
diff --git a/guides/plugins/apps/index.md b/guides/plugins/apps/index.md
index 514f07d149..90929db230 100644
--- a/guides/plugins/apps/index.md
+++ b/guides/plugins/apps/index.md
@@ -53,3 +53,5 @@ After the base setup, continue with the path that fits your app:
- **Event-based integrations:** See [Webhook](../apps/lifecycle/webhook.md) to react to Shopware events asynchronously.
- **Feature-specific integrations:** Continue with the dedicated guides for [Payment](../apps/checkout/payment.md), [Shipping methods](../apps/checkout/shipping-methods.md), [Tax provider](../apps/checkout/tax-provider.md), or [Configuration](../apps/lifecycle/configuration.md), depending on your app’s capabilities.
+
+- **MCP tools, prompts, and resources:** See [MCP Server extension](mcp-server.md) to expose custom AI-callable capabilities via `Resources/mcp.xml` and HMAC-signed webhooks.
diff --git a/guides/plugins/apps/mcp-server.md b/guides/plugins/apps/mcp-server.md
new file mode 100644
index 0000000000..6d64ddfb2f
--- /dev/null
+++ b/guides/plugins/apps/mcp-server.md
@@ -0,0 +1,260 @@
+---
+nav:
+ title: MCP Server Extension
+ position: 80
+
+---
+
+# Extending the MCP Server via App
+
+Shopware apps can add custom tools, prompts, and resources to the MCP server through a declarative `Resources/mcp.xml` file. When an AI client calls an app tool, Shopware sends an HMAC-signed HTTP POST to your app's endpoint and returns the response to the client.
+
+Use an app when:
+
+- Your capability runs on a remote service (ERP, PIM, CRM, SaaS backend)
+- You want cloud compatibility (apps work in Shopware Cloud environments where plugins cannot run)
+- Your capability needs to scale or deploy independently from Shopware
+- You are building a SaaS integration where isolation matters more than in-process performance
+
+For in-process PHP with full DAL access, see [Extending via Plugin](../plugins/mcp-server.md). For a side-by-side comparison of all three extension types, see [Extending the MCP Server](../../development/tooling/mcp-server/extending.md).
+
+## How app capabilities work
+
+```mermaid
+sequenceDiagram
+ participant Client as AI Client
+ participant SW as Shopware
+ participant App as Your App
+ Client->>SW: call tool
+ SW->>App: HMAC-signed POST
+ App-->>SW: JSON response
+ SW-->>Client: result
+```
+
+All three capability types (tools, prompts, resources) follow the same lifecycle:
+
+| Step | Tools | Prompts | Resources |
+|---|---|---|---|
+| Declared in | `mcp.xml` | `mcp.xml` | `mcp.xml` |
+| Persisted on install | `app_mcp_tool` | `app_mcp_prompt` | `app_mcp_resource` |
+| Loaded at runtime | `AppMcpToolLoader` | `AppMcpPromptLoader` | `AppMcpResourceLoader` |
+| Executed via | HMAC-signed POST | HMAC-signed POST | HMAC-signed POST |
+
+## Naming convention
+
+Names declared in `mcp.xml` are automatically prefixed with the app name at install time:
+
+```text
+app name: "my-erp"
+declared name: "sync-orders"
+→ final tool name: "my-erp-sync-orders"
+```
+
+Names must only contain `a-zA-Z0-9_-` (no dots). The `shopware-` prefix is reserved for core tools; app names that would produce a `shopware-` prefixed capability are silently skipped.
+
+## `mcp.xml` structure
+
+Place `Resources/mcp.xml` in your app bundle:
+
+```xml
+
+
+
+
+
+
+
+ Synchronize orders with the external ERP system
+
+
+
+
+
+ order:read
+ order:update
+
+
+
+
+
+
+
+ Background context for working with ERP-synced data
+
+
+
+
+
+
+ Current ERP connection status and last sync timestamp
+
+
+
+
+```
+
+### Input schema properties
+
+Each `` maps to a JSON Schema property in the tool's `inputSchema`:
+
+| Attribute | Required | Description |
+|---|---|---|
+| `name` | yes | Parameter name |
+| `type` | yes | JSON Schema type: `string`, `integer`, `number`, `boolean`, `array`, `object` (default: `string`) |
+| `description` | no | Description shown to the AI client |
+| `required` | no | Whether the parameter is required (default: `false`) |
+
+### Required privileges
+
+List the ACL privileges your tool needs with ``. The admin UI uses this list to warn operators when an integration's role is missing privileges a tool expects.
+
+How strictly these privileges are enforced depends on the URL mode:
+
+- **External URL** (`https://...`): informational only. Shopware does not check these privileges before calling your webhook. Your app must enforce them on its own side — for example by validating `source.shopId` against what that shop is permitted to do in your backend.
+- **Internal path** (`/api/...`): enforced by Shopware. The call is dispatched as a Symfony subrequest through the normal Admin API stack, so the integration's ACL role is checked just like any other API call. Declare the privileges accurately so the Admin UI can warn operators about gaps.
+
+## URL modes: external vs internal
+
+The `url` attribute supports two modes:
+
+- **External URL** (`https://...`): Shopware sends an HMAC-signed POST to your remote endpoint. This is the default for SaaS integrations and any app hosted outside Shopware.
+- **Internal path** (`/...`): Shopware dispatches the call as a Symfony subrequest. This lets an app use [app scripts](../app-scripts/) to serve capability logic without running an external server. Example: `url="/api/script/mcp-greet"`.
+
+Use the internal-path mode when your tool is a thin wrapper around data Shopware already has. Use external URLs when you need to call out to an ERP, PIM, or any system Shopware cannot reach directly.
+
+## Webhook protocol
+
+When the AI client calls a tool, Shopware sends an HTTP POST to the URL declared in `mcp.xml`:
+
+**Request body:**
+
+```json
+{
+ "tool": "my-erp-sync-orders",
+ "arguments": {
+ "since": "2026-01-01",
+ "limit": 100
+ },
+ "source": {
+ "url": "https://shop.example.com",
+ "shopId": "abc123def456",
+ "appVersion": "1.2.0"
+ }
+}
+```
+
+| Field | Description |
+|---|---|
+| `tool` | The full tool name (app prefix + declared name) |
+| `arguments` | The arguments provided by the AI client |
+| `source.url` | The shop's base URL |
+| `source.shopId` | Unique shop identifier. Use this for multi-tenant app backends |
+| `source.appVersion` | Installed version of the app |
+
+For prompts, the request body uses `prompt` instead of `tool`, and arguments are omitted:
+
+```json
+{
+ "prompt": "my-erp-erp-context",
+ "source": {
+ "url": "https://shop.example.com",
+ "shopId": "abc123def456",
+ "appVersion": "1.2.0"
+ }
+}
+```
+
+The response must be a JSON array of message objects:
+
+```json
+[
+ {"role": "user", "content": "You are working with ERP-synced Shopware data..."}
+]
+```
+
+For resources, the request body uses `resource` instead of `tool`:
+
+```json
+{
+ "resource": "my-erp-erp-status",
+ "source": {
+ "url": "https://shop.example.com",
+ "shopId": "abc123def456",
+ "appVersion": "1.2.0"
+ }
+}
+```
+
+The response must be a single object with `uri`, `mimeType`, and `text`:
+
+```json
+{"uri": "my-erp://erp-status", "mimeType": "application/json", "text": "{\"connected\": true}"}
+```
+
+## Verifying the signature
+
+Every webhook request is signed with HMAC-SHA256 using your app secret. Always verify the `shopware-shop-signature` header before processing the request:
+
+```javascript
+// Node.js example
+const crypto = require('crypto');
+
+function verifySignature(body, signature, appSecret) {
+ const expected = crypto
+ .createHmac('sha256', appSecret)
+ .update(body)
+ .digest('hex');
+ return crypto.timingSafeEqual(
+ Buffer.from(signature),
+ Buffer.from(expected)
+ );
+}
+
+app.post('/mcp/sync-orders', (req, res) => {
+ const signature = req.headers['shopware-shop-signature'];
+ const rawBody = req.rawBody; // unparsed request body string
+
+ if (!verifySignature(rawBody, signature, process.env.APP_SECRET)) {
+ return res.status(401).json({ error: 'Invalid signature' });
+ }
+
+ const { arguments: args, source } = req.body;
+ // ... handle the tool call
+});
+```
+
+## Returning a response
+
+Return a JSON string as the tool result. Shopware forwards this to the AI client as-is:
+
+```json
+{"success": true, "data": {"synced": 42, "errors": 0}}
+```
+
+For consistency with core tools, follow the `{"success": bool, "data": ..., "_meta": ...}` / `{"success": false, "error": "..."}` convention. Shopware does not reject responses that omit `success`, but it logs a warning when the field is missing so operators can spot broken handlers.
+
+**Timeout:** App webhook calls time out after `shopware.mcp.app_tool_timeout` seconds (default: 10). Keep responses fast or use async patterns with status polling.
+
+## Locale resolution
+
+Labels and descriptions in `mcp.xml` support translations via `lang` attributes. Shopware resolves them against the system's default locale at load time. If a translation for the active locale is not found, it falls back to `en-GB`.
+
+## Installing the app
+
+Apps are installed through the standard Shopware app lifecycle (Settings → Extensions → My Apps, or via the Shopware CLI). After installation, your MCP capabilities appear automatically in the server's tool list.
+
+Verify with:
+
+```bash
+bin/console debug:mcp
+```
+
+Your tools appear with **Source: app** in the output. To see what a specific integration can reach (respecting its per-integration allowlist), pass `--integration=SWIA...` with the integration's access key.
+
+## Further reading
+
+- [MCP Concepts](../../development/tooling/mcp-server/mcp-concepts.md): tools, resources, and prompts explained
+- [Best Practices](../../development/tooling/mcp-server/best-practices.md): design principles for MCP tools
+- [Extending via Plugin](../plugins/mcp-server.md): in-process PHP alternative
+- [McpHelloWorld](https://github.com/shopwareLabs/McpHelloWorld): minimal example app registering tools, prompts, and resources via webhook
diff --git a/guides/plugins/plugins/bundle.md b/guides/plugins/plugins/bundle.md
index 8434d1bf6d..4db522d69c 100644
--- a/guides/plugins/plugins/bundle.md
+++ b/guides/plugins/plugins/bundle.md
@@ -116,6 +116,31 @@ App\YourBundleName\YourBundleName::class => ['all' => true],
//...
```
+### Optional bundles
+
+If a bundle is not always installed (for example a dev-only package or an experimental feature), wrap its registration in a guard. Without a guard the application will crash with a class-not-found error on any environment where the package is absent.
+
+**Package not always installed** — use `InstalledVersions::isInstalled()`:
+
+```php
+use Composer\InstalledVersions;
+
+if (InstalledVersions::isInstalled('vendor/your-bundle-package')) {
+ $bundles[App\YourBundleName\YourBundleName::class] = ['all' => true];
+}
+```
+
+**Experimental feature behind a Shopware feature flag** — combine both checks so the bundle is only loaded when the flag is active and the package is present. `Feature::isActive()` reads from environment variables and is safe to call in `bundles.php`:
+
+```php
+use Composer\InstalledVersions;
+use Shopware\Core\Framework\Feature;
+
+if (Feature::isActive('YOUR_FEATURE_FLAG') && InstalledVersions::isInstalled('vendor/your-bundle-package')) {
+ $bundles[App\YourBundleName\YourBundleName::class] = ['all' => true];
+}
+```
+
## Adding services, Twig templates, routes, and themes
You can add services, Twig templates, routes, etc. to your bundle like you would do in a plugin. Create `Resources/config/services.php` and `Resources/config/routes.php` files, or `Resources/views` for Twig templates. The bundle will be automatically detected and the files will be loaded.
diff --git a/guides/plugins/plugins/index.md b/guides/plugins/plugins/index.md
index e2de9d1356..4743663065 100644
--- a/guides/plugins/plugins/index.md
+++ b/guides/plugins/plugins/index.md
@@ -28,6 +28,7 @@ You will likely create a plugin when you need deep server-side integration or re
| Database changes | [Database migrations](../plugins/database/database-migrations.md) |
| Composer dependencies in a plugin | [Adding Composer dependencies](../plugins/dependencies/using-composer-dependencies.md) |
| More topics | [Plugin fundamentals](plugin-fundamentals/index.md) (logging, cache, routes, …) |
+| Add custom MCP tools, prompts, resources | [MCP Server extension](mcp-server.md) |
::: info
If your extension focuses primarily on Storefront design changes, a [theme plugin](../themes/theme-base-guide.md) is often the best choice.
diff --git a/guides/plugins/plugins/mcp-server.md b/guides/plugins/plugins/mcp-server.md
new file mode 100644
index 0000000000..b5ed2c8ccc
--- /dev/null
+++ b/guides/plugins/plugins/mcp-server.md
@@ -0,0 +1,300 @@
+---
+nav:
+ title: MCP Server Extension
+ position: 80
+
+---
+
+# Extending the MCP Server via Plugin
+
+Shopware plugins and Symfony bundles can add custom tools, prompts, and resources to the MCP server. This guide covers the plugin path: in-process PHP with full DAL access and the Shopware plugin lifecycle.
+
+Use a plugin when:
+
+- Your tool needs deep access to DAL repositories, services, or the Symfony container
+- You want to ship via the Shopware Marketplace
+- Your capability is tightly coupled to Shopware's plugin lifecycle (install, activate, deactivate)
+
+For remote/webhook-based capabilities, see [Extending via App](../apps/mcp-server.md). For a side-by-side comparison of all three extension types, see [Extending the MCP Server](../../development/tooling/mcp-server/extending.md).
+
+## Naming convention
+
+All capability names must only contain `a-zA-Z0-9_-` (no dots). Use a hyphen-separated vendor prefix to avoid conflicts:
+
+- **Core:** `shopware-{name}` (reserved; do not use in extensions)
+- **Plugin / Bundle:** `{vendor-name}-{tool-name}` (e.g., `swag-erp-sync-orders`)
+- **App:** `{app-name}-{tool-name}` (auto-prefixed)
+
+This convention applies uniformly to tools, prompts, and resources.
+
+## Plugin structure
+
+```text
+custom/plugins/SwagMyPlugin/
+├── composer.json
+└── src/
+ ├── SwagMyPlugin.php # Plugin class
+ ├── Mcp/
+ │ └── Tool/
+ │ └── MyTool.php # MCP tool class
+ └── Resources/
+ └── config/
+ └── services.xml # Service registration
+```
+
+## Step 1: Create the tool class
+
+The `#[McpTool]` attribute must be on the **class**, not on `__invoke()`. Extend `McpToolResponse` to get consistent response envelopes and built-in helpers.
+
+```php
+contextProvider->getContext();
+ if ($error = $this->requirePrivilege($context, 'order:read')) {
+ return $error;
+ }
+
+ $criteria = new Criteria();
+ $criteria->addFilter(/* ... */);
+ $criteria->setLimit($limit);
+
+ $orders = $this->orderRepository->search($criteria, $context);
+
+ return $this->success(
+ $orders->map(fn($o) => ['id' => $o->getId(), 'orderNumber' => $o->getOrderNumber()]),
+ ['total' => $orders->getTotal()]
+ );
+ }
+}
+```
+
+**Key rules:**
+
+- `#[McpTool]` goes on the class, not on `__invoke()`. The compiler pass reads class-level attributes; method-level attributes are silently ignored.
+- 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.
+- `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.
+- Return a `string` from `__invoke()`. The MCP SDK wraps the return value into the protocol response automatically.
+- Extend `McpToolResponse` to use `$this->success()` and `$this->error()` helpers.
+
+## Step 2: Declare dependencies and privileges
+
+### Tool dependencies
+
+When your tool only makes sense after the agent has used another tool, declare that relationship with `#[McpToolDependsOn]`:
+
+```php
+#[McpToolDependsOn('shopware-entity-schema')] // agent should know the schema first
+#[McpToolDependsOn('shopware-entity-search')] // and be able to search
+```
+
+The attribute is repeatable. When an operator enables your tool in the Admin UI, all declared dependencies (and their transitive dependencies) are automatically added to the integration's allowlist.
+
+Only declare a dependency when it is genuinely required; unnecessary dependencies inflate every integration's allowlist.
+
+### Required privileges
+
+Declare the ACL privileges your tool needs with `#[McpToolRequires]` so operators can configure roles correctly:
+
+```php
+// Static privilege
+#[McpToolRequires('order:read')]
+
+// Dynamic privilege (entity name comes from a runtime parameter)
+#[McpToolRequires(entityParam: 'entity', operations: ['read', 'update'])]
+```
+
+The attribute is **declarative only**: it populates the Admin UI coverage warnings and `bin/console debug:mcp` output. You still must call `$this->requirePrivilege($context, 'order:read')` inside `__invoke()` for actual runtime enforcement.
+
+## Step 3: Register the service
+
+In `src/Resources/config/services.xml`, tag the service with `shopware.mcp.tool`:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Plugin tools use `shopware.mcp.tool` (not `mcp.tool`). The `McpToolCompilerPass` 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; the MCP feature flag gates the server endpoint itself, and once it is enabled, all registered tools are available.
+
+### Available tags
+
+| Shopware tag | Purpose |
+|---|---|
+| `shopware.mcp.tool` | Register a tool |
+| `shopware.mcp.prompt` | Register a prompt |
+| `shopware.mcp.resource` | Register a resource |
+
+## Step 4: Install and verify
+
+```bash
+bin/console plugin:refresh
+bin/console plugin:install --activate SwagMyPlugin
+bin/console cache:clear
+```
+
+Verify the tool is registered:
+
+```bash
+bin/console debug:mcp
+```
+
+If the tool appears here, it is available in the live HTTP endpoint. If it does not appear, check:
+
+- Plugin is installed and active
+- Service has ``
+- `#[McpTool]` is on the class, not on `__invoke()`
+
+## Adding prompts
+
+Follow the same pattern with `#[McpPrompt]` and `shopware.mcp.prompt`:
+
+```php
+use Mcp\Capability\Attribute\McpPrompt;
+
+#[McpPrompt(name: 'swag-my-plugin-context', description: 'Context for using the My Plugin MCP tools.')]
+class MyPluginContextPrompt
+{
+ public function __invoke(): array
+ {
+ return [
+ ['role' => 'user', 'content' => 'You are working with the My Plugin Shopware extension...'],
+ ];
+ }
+}
+```
+
+## Adding resources
+
+Follow the same pattern with `#[McpResource]` and `shopware.mcp.resource`:
+
+```php
+use Mcp\Capability\Attribute\McpResource;
+
+#[McpResource(uri: 'swag-my-plugin://config', name: 'My Plugin Config', description: 'Current configuration values.')]
+class MyPluginConfigResource
+{
+ public function __invoke(): array
+ {
+ return [
+ 'uri' => 'swag-my-plugin://config',
+ 'mimeType' => 'application/json',
+ 'text' => json_encode(['key' => 'value']),
+ ];
+ }
+}
+```
+
+## Extending via Symfony bundle
+
+Symfony bundles (not Shopware plugins) follow the same `shopware.mcp.tool` tag mechanism. The key differences:
+
+- The bundle class extends `Symfony\Component\HttpKernel\Bundle\Bundle`, not `Shopware\Core\Framework\Plugin`
+- No Shopware install/activate lifecycle; the bundle is always active when registered in `config/bundles.php`
+- Gate services in the bundle's `build()` method instead of using `shopware.feature` tags:
+
+```php
+public function build(ContainerBuilder $container): void
+{
+ if (!Feature::has('MCP_SERVER')) {
+ return;
+ }
+ $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/Resources/config'));
+ $loader->load('services.xml');
+}
+```
+
+## Common pitfalls
+
+### Dots in capability names
+
+Names must only contain `a-zA-Z0-9_-`. Dots are not allowed:
+
+```php
+// Wrong
+#[McpTool(name: 'swag-my-plugin.orders', description: '...')]
+
+// Correct
+#[McpTool(name: 'swag-my-plugin-orders', description: '...')]
+```
+
+### Attribute on the wrong level
+
+`#[McpTool]` must be on the class. Placing it on `__invoke()` silently drops the tool:
+
+```php
+// Wrong (tool is silently skipped)
+class MyTool extends McpToolResponse
+{
+ #[McpTool(name: 'swag-my-plugin-orders', description: '...')]
+ public function __invoke(): string { ... }
+}
+
+// Correct
+#[McpTool(name: 'swag-my-plugin-orders', description: '...')]
+class MyTool extends McpToolResponse
+{
+ public function __invoke(): string { ... }
+}
+```
+
+### Unhandled exceptions
+
+Unhandled exceptions in `__invoke()` produce a generic MCP error (`-32603`). Catch known exceptions and return `$this->error($message)` instead:
+
+```php
+public function __invoke(string $entity): string
+{
+ try {
+ return $this->success(['result' => $data]);
+ } catch (\Throwable $e) {
+ return $this->error($e->getMessage());
+ }
+}
+```
+
+For write tools, use `$this->executeWithDryRun()`, which catches exceptions automatically and returns structured error responses.
+
+## Further reading
+
+- [MCP Concepts](../../development/tooling/mcp-server/mcp-concepts.md): tools, resources, and prompts explained
+- [Best Practices](../../development/tooling/mcp-server/best-practices.md): design principles for MCP tools
+- [Configuration](../../development/tooling/mcp-server/configuration.md): allowlist, ACL, and CLI debugging
+- [SwagMcpAdminUsers](https://github.com/shopwareLabs/SwagMcpAdminUsers): example plugin registering tools, prompts, and resources for admin user management