Skip to content

[MCP] Container fails to compile with symfony/mcp-bundle 0.13: non-existent service mcp.registry #8486

Description

@tacman

Summary

symfony/mcp-bundle 0.13.0 replaced its single, global mcp.registry service with one
registry per configured server (mcp.server.<name>.registry), as part of the new
multi-server support. API Platform's MCP integration still hard-references the old service id,
so with api_platform.mcp.enabled: true (the default) the container no longer compiles at all:

In CheckExceptionOnInvalidReferenceBehaviorPass.php line 116:

  The service "api_platform.mcp.list_handler" has a dependency on a non-existent
  service "mcp.registry". Did you mean this: "form.registry"?

Because this is a container-compile failure, it takes down cache:clear, cache:warmup and
every console command in any environment where McpBundle is registered — it is not limited to
requests actually reaching the MCP endpoint. And since mcp is declared with canBeDisabled(),
the integration is on by default, so no opt-in is needed to hit this.

Separately, and arguably the more important half of this report: nothing prevents the bad
combination from being installed.
api-platform/core declares only

"require-dev": {
    "mcp/sdk": "^0.6",
    "symfony/mcp-bundle": "dev-main"
}

There is no runtime constraint and no conflict entry, so composer update happily installs
mcp-bundle 0.13 alongside API Platform and the application only breaks later, at
container-compile time, with an error that does not name either package.

Environment

api-platform/core 4.3.17
symfony/mcp-bundle 0.13.0 broken, 0.12.0 OK
mcp/sdk 0.8.1 (required by mcp-bundle 0.13; API Platform's dev constraint is still ^0.6)
Symfony 8.1.5
PHP 8.5.10
api_platform.mcp enabled: true (default — not set explicitly)

Reproduction

On any API Platform app with McpBundle registered:

composer require symfony/mcp-bundle:^0.13
php bin/console cache:clear

The 0.13 config schema also changed (app + client_transports became a servers map with a
required registry key), so config/packages/mcp.yaml needs migrating first, e.g.:

mcp:
    servers:
        app:
            registry: '*'
            transports: { stdio: false, http: true }

With valid config, the failure above is what you get next.

Root cause

src/Symfony/Bundle/Resources/config/mcp/mcp.php:46 injects the removed service:

$services->set('api_platform.mcp.list_handler', ListHandler::class)
    ->args([
        service('mcp.registry'),          // <- removed in mcp-bundle 0.13
        service('api_platform.mcp.loader'),
    ])
    ->tag('mcp.request_handler');

In mcp-bundle 0.13, McpBundle::configureServer() registers one registry per server:

$registryId = \sprintf('mcp.server.%s.registry', $name);
$container->register($registryId, Registry::class)

This rename is documented in
symfony/ai UPGRADE.md, "Upgrade from 0.12 to 0.13":

The per-server services replace their singleton counterparts: mcp.registry, mcp.server.builder,
mcp.server, mcp.session.store, mcp.middleware_factory and mcp.server.controller became
mcp.server.<name>.registry, … Autowire a specific server with Mcp\Server $<name>Server.

The good news is that this is the only incompatibility I found. The
mcp.loader, mcp.request_handler and mcp.notification_handler tags all still exist in 0.13
and are still consumed globally, via TaggedIteratorArgument on each server's builder. So
mcp.php:37, events.php:44 and state.php:44 are unaffected.

Suggested fix

Worth noting that swapping in a hardcoded mcp.server.<name>.registry is not sufficient, because
tagged request handlers are added to every server's builder:

->addMethodCall('addRequestHandlers', [new TaggedIteratorArgument('mcp.request_handler')])

so a single ListHandler instance holding one registry reference would serve the wrong registry
as soon as a second server is configured. The upgrade notes give the one-to-one service rename but
don't cover this case, since it only arises for a shared, tagged request handler like this one.

mcp-bundle 0.13 does expose a service locator for it, mapping server name to registry:

$this->registerServerLocator('mcp.server_locator.registry', array_keys($servers), 'registry', $container);

So ListHandler probably wants to take mcp.server_locator.registry and resolve the registry for
the server handling the current request, rather than a single injected Registry.

I'm happy to open a PR if you can confirm the intended direction — in particular whether
ListHandler should become server-aware, or whether API Platform would rather register one
handler per configured server.

In the meantime, a conflict entry would turn this into a clear resolver error instead of a
container-compile failure:

"conflict": {
    "symfony/mcp-bundle": ">=0.13"
}

Workaround

Pin symfony/mcp-bundle to ^0.12 (and mcp/sdk to ^0.7), keeping the pre-0.13
mcp.yaml schema. Everything else in the symfony/ai 0.13 release train — ai-platform,
ai-agent, ai-bundle and the platform bridges — upgrades cleanly alongside API Platform 4.3.17;
mcp-bundle is the only package that has to be held back.

For anyone who needs mcp-bundle 0.13 more than they need the API Platform MCP tools, setting
api_platform: mcp: { enabled: false } also unblocks the container, at the cost of dropping every
#[ApiResource(mcp: [...])] tool.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions