Skip to content

Commit ac57b9b

Browse files
camilleislassechr-hertel
authored andcommitted
Migrate MCP Bundle from symfony/mcp-sdk to official mcp/sdk
- Replace symfony/mcp-sdk dependency with official mcp/sdk - Refactor Server creation to use Server::make() builder pattern - Convert tool definitions from interfaces to #[McpTool] attributes - Implement automatic tool discovery in src/ directory - Update STDIO transport command to mcp:server - Simplify service configuration using native SDK patterns - Remove redundant ServerFactory and routes.php files - Add custom LogicException for bundle-specific errors - Update all documentation (README, CHANGELOG, index.rst) - Fix composer.json description - Add .idea/ to .gitignore Resolves #526 Fabbot + php-cs-fixer DOCtor-RST Fix RouteLoader registration to always create when transports enabled Remove .idea - Add autoconfiguration for #[McpTool] attribute with mcp.tool tag - Create McpToolPass compiler pass using ServiceLocatorTagPass::register() - Inject Service Locator into ServerBuilder via setContainer() Fix PR review comments for MCP Bundle migration - Remove prefer-stable from root composer.json - Move CHANGELOG content to 0.1 section and remove BC BREAK labels - Use ServerBuilder::class and Server::class in services configuration - Fix "client vs server" comment in documentation - Use ServerBuilder::class in test instead of string Remove symfony/mcp-sdk Remove symfony/mcp-sdk ref in claude reamdde et phpstan Rewrite CHANGELOG.md Use [] === $taggedServices instead of empty($taggedServices) Use use Symfony\Component\Routing\Exception\LogicException; instead of custom Exception Add MCP capabilities support to Symfony bundle This commit implements Model Context Protocol (MCP) support by adding: - Prompts: System instructions for AI context using #[McpPrompt] - Resources: Static data access using #[McpResource] - Resource Templates: Dynamic resources with parameters using #[McpResourceTemplate] Implementation includes: - Auto-configuration for MCP attributes with proper tagging - Compiler passes extending AbstractMcpPass for service registration - Documentation updates with examples and usage patterns - Demo examples showcasing each capability type Technical details: - Refactored auto-configuration into registerMcpAttributes() method - Created AbstractMcpPass to eliminate code duplication - Added proper error handling for timezone validation - Resource Templates ready but await MCP SDK handler implementation Apply PHP CS Fixer to demo MCP examples - Add Symfony license headers to demo files - Fix code style and formatting - Add trailing commas where appropriate Add pagination_limit and instructions configuration options - Add pagination_limit option to control MCP list responses (default: 50) - Add instructions option for server description to help LLMs - Update services.php to pass both options to ServerBuilder - Add comprehensive tests for new configuration options - Update documentation with examples and usage - Update demo configuration with practical examples Both options map directly to ServerBuilder methods: - setPaginationLimit(int) - setInstructions(string) Add dedicated MCP logger with configurable Monolog integration - Create monolog.logger.mcp service with dedicated channel - Update ServerBuilder to use MCP-specific logger instead of generic logger - Add comprehensive logging documentation with configuration examples - Include examples for different environments (dev/prod) and handlers (file/Slack) - Add test coverage for MCP logger service creation and configuration The MCP logger uses Monolog's logger prototype pattern and can be customized by users through standard Monolog configuration in their applications. Add EventDispatcher support and event system documentation - Configure Symfony EventDispatcher for MCP SDK event handling - Add comprehensive event system documentation with examples - Add test coverage for EventDispatcher configuration - Fix PHPStan type assertion for ChildDefinition Migrate MCP Bundle from SSE to official StreamableHttpTransport - Replace custom SSE implementation with MCP SDK's StreamableHttpTransport - Change sse transport to http in configuration and code - Simplify DependencyInjection compiler passes - Add HTTP session management (file/memory store options) - Update documentation and tests for HTTP transport
1 parent 4225995 commit ac57b9b

File tree

14 files changed

+704
-249
lines changed

14 files changed

+704
-249
lines changed

CHANGELOG.md

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,23 @@ CHANGELOG
44
0.1
55
---
66

7-
* Add Symfony bundle bridging MCP-SDK with Symfony applications
8-
* Add server mode exposing Symfony tools to MCP clients:
9-
- STDIO transport via `php bin/console mcp` command
10-
- SSE (Server-Sent Events) transport via HTTP endpoints
11-
- Automatic tool discovery and registration
12-
- Integration with AI-Bundle tools
13-
* Add routing configuration for SSE endpoints:
14-
- `/_mcp/sse` for SSE connections
15-
- `/_mcp/messages/{id}` for message retrieval
16-
* Add `McpController` for handling SSE connections
7+
* Add Symfony bundle providing Model Context Protocol integration using official `mcp/sdk`
8+
* Add server mode exposing MCP capabilities to clients:
9+
- STDIO transport via `php bin/console mcp:server` command
10+
- HTTP transport via StreamableHttpTransport using configurable endpoints
11+
- Automatic capability discovery and registration
12+
- EventDispatcher integration for capability change notifications
13+
* Add configurable HTTP transport features:
14+
- Configurable endpoint path (default: `/_mcp`)
15+
- File and memory session store options
16+
- TTL configuration for session management
17+
- CORS headers for cross-origin requests
18+
* Add `McpController` for handling HTTP transport connections
1719
* Add `McpCommand` providing STDIO interface
18-
* Add bundle configuration for enabling/disabling transports
19-
* Add cache-based SSE message storage
20-
* Add service configuration for MCP server setup
21-
* Classes extending `\Symfony\AI\McpSdk\Capability\Tool\IdentifierInterface` automatically
22-
get the `mcp.tool` tag for MCP tool discovery
20+
* Add bundle configuration for transport selection and HTTP options
21+
* Add dedicated MCP logger with configurable Monolog integration
22+
* Add pagination and instructions configuration
23+
* Tools using `#[McpTool]` attribute automatically discovered
24+
* Prompts using `#[McpPrompt]` attribute automatically discovered
25+
* Resources using `#[McpResource]` attribute automatically discovered
26+
* Resource templates using `#[McpResourceTemplate]` attribute automatically discovered

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# MCP Bundle
22

3-
Symfony integration bundle for [Model Context Protocol](https://modelcontextprotocol.io/) using the Symfony AI
4-
MCP SDK [symfony/mcp-sdk](https://github.com/symfony/mcp-sdk).
3+
Symfony integration bundle for [Model Context Protocol](https://modelcontextprotocol.io/) using the official
4+
MCP SDK [mcp/sdk](https://github.com/modelcontextprotocol/php-sdk).
55

6-
**Currently only supports tools as server via Server-Sent Events (SSE) and STDIO.**
6+
**Supports MCP capabilities (tools, prompts, resources) as server via HTTP transport and STDIO. Resource templates implementation ready but awaiting MCP SDK support.**
77

88
**This Bundle is experimental**.
99
[Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html)

composer.json

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "symfony/mcp-bundle",
3-
"description": "Symfony integration bundle for Model Context Protocol (via symfony/mcp-sdk)",
3+
"description": "Symfony integration bundle for Model Context Protocol (via official mcp/sdk)",
44
"license": "MIT",
55
"type": "symfony-bundle",
66
"authors": [
@@ -16,8 +16,10 @@
1616
"symfony/framework-bundle": "^7.3|^8.0",
1717
"symfony/http-foundation": "^7.3|^8.0",
1818
"symfony/http-kernel": "^7.3|^8.0",
19-
"symfony/mcp-sdk": "@dev",
20-
"symfony/routing": "^7.3|^8.0"
19+
"mcp/sdk": "@dev",
20+
"symfony/routing": "^7.3|^8.0",
21+
"symfony/psr-http-message-bridge": "^7.3|^8.0",
22+
"php-http/discovery": "^1.20"
2123
},
2224
"require-dev": {
2325
"phpstan/phpstan": "^2.1",
@@ -37,6 +39,9 @@
3739
}
3840
},
3941
"config": {
40-
"sort-packages": true
42+
"sort-packages": true,
43+
"allow-plugins": {
44+
"php-http/discovery": true
45+
}
4146
}
4247
}

config/options.php

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,49 +16,26 @@
1616
->children()
1717
->scalarNode('app')->defaultValue('app')->end()
1818
->scalarNode('version')->defaultValue('0.0.1')->end()
19-
->scalarNode('page_size')->defaultValue(20)->end()
20-
// ->arrayNode('servers')
21-
// ->useAttributeAsKey('name')
22-
// ->arrayPrototype()
23-
// ->children()
24-
// ->enumNode('transport')
25-
// ->values(['stdio', 'sse'])
26-
// ->isRequired()
27-
// ->end()
28-
// ->arrayNode('stdio')
29-
// ->children()
30-
// ->scalarNode('command')->isRequired()->end()
31-
// ->arrayNode('arguments')
32-
// ->scalarPrototype()->end()
33-
// ->defaultValue([])
34-
// ->end()
35-
// ->end()
36-
// ->end()
37-
// ->arrayNode('sse')
38-
// ->children()
39-
// ->scalarNode('url')->isRequired()->end()
40-
// ->end()
41-
// ->end()
42-
// ->end()
43-
// ->validate()
44-
// ->ifTrue(function ($v) {
45-
// if ('stdio' === $v['transport'] && !isset($v['stdio'])) {
46-
// return true;
47-
// }
48-
// if ('sse' === $v['transport'] && !isset($v['sse'])) {
49-
// return true;
50-
// }
51-
//
52-
// return false;
53-
// })
54-
// ->thenInvalid('When transport is "%s", you must configure the corresponding section.')
55-
// ->end()
56-
// ->end()
57-
// ->end()
19+
->integerNode('pagination_limit')->defaultValue(50)->end()
20+
->scalarNode('instructions')->defaultNull()->end()
5821
->arrayNode('client_transports')
5922
->children()
6023
->booleanNode('stdio')->defaultFalse()->end()
61-
->booleanNode('sse')->defaultFalse()->end()
24+
->booleanNode('http')->defaultFalse()->end()
25+
->end()
26+
->end()
27+
->arrayNode('http')
28+
->addDefaultsIfNotSet()
29+
->children()
30+
->scalarNode('path')->defaultValue('/_mcp')->end()
31+
->arrayNode('session')
32+
->addDefaultsIfNotSet()
33+
->children()
34+
->enumNode('store')->values(['file', 'memory'])->defaultValue('file')->end()
35+
->scalarNode('directory')->defaultValue('%kernel.cache_dir%/mcp-sessions')->end()
36+
->integerNode('ttl')->min(1)->defaultValue(3600)->end()
37+
->end()
38+
->end()
6239
->end()
6340
->end()
6441
->end()

config/routes.php

Lines changed: 0 additions & 24 deletions
This file was deleted.

config/services.php

Lines changed: 18 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,67 +11,28 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

14-
use Symfony\AI\McpSdk\Capability\ToolChain;
15-
use Symfony\AI\McpSdk\Message\Factory;
16-
use Symfony\AI\McpSdk\Server;
17-
use Symfony\AI\McpSdk\Server\JsonRpcHandler;
18-
use Symfony\AI\McpSdk\Server\NotificationHandler\InitializedHandler;
19-
use Symfony\AI\McpSdk\Server\RequestHandler\InitializeHandler;
20-
use Symfony\AI\McpSdk\Server\RequestHandler\PingHandler;
21-
use Symfony\AI\McpSdk\Server\RequestHandler\ToolCallHandler;
22-
use Symfony\AI\McpSdk\Server\RequestHandler\ToolListHandler;
23-
use Symfony\AI\McpSdk\Server\Transport\Sse\Store\CachePoolStore;
14+
use Mcp\Server;
15+
use Mcp\Server\ServerBuilder;
2416

2517
return static function (ContainerConfigurator $container): void {
2618
$container->services()
27-
->set('mcp.server.notification_handler.initialized', InitializedHandler::class)
28-
->args([])
29-
->tag('mcp.server.notification_handler')
30-
->set('mcp.server.request_handler.initialize', InitializeHandler::class)
31-
->args([
32-
param('mcp.app'),
33-
param('mcp.version'),
34-
])
35-
->tag('mcp.server.request_handler')
36-
->set('mcp.server.request_handler.ping', PingHandler::class)
37-
->args([])
38-
->tag('mcp.server.request_handler')
39-
->set('mcp.server.request_handler.tool_call', ToolCallHandler::class)
40-
->args([
41-
service('mcp.tool_executor'),
42-
])
43-
->tag('mcp.server.request_handler')
44-
->set('mcp.server.request_handler.tool_list', ToolListHandler::class)
45-
->args([
46-
service('mcp.tool_collection'),
47-
param('mcp.page_size'),
48-
])
49-
->tag('mcp.server.request_handler')
19+
->set('monolog.logger.mcp')
20+
->parent('monolog.logger_prototype')
21+
->args(['mcp'])
22+
->tag('monolog.logger', ['channel' => 'mcp'])
23+
24+
->set('mcp.server.builder', ServerBuilder::class)
25+
->factory([Server::class, 'make'])
26+
->call('setServerInfo', [param('mcp.app'), param('mcp.version')])
27+
->call('setPaginationLimit', [param('mcp.pagination_limit')])
28+
->call('setInstructions', [param('mcp.instructions')])
29+
->call('setLogger', [service('monolog.logger.mcp')])
30+
->call('setEventDispatcher', [service('event_dispatcher')])
31+
->call('setSession', [service('mcp.session.store')])
32+
->call('setDiscovery', [param('kernel.project_dir'), ['src']])
5033

51-
->set('mcp.message_factory', Factory::class)
52-
->args([])
53-
->set('mcp.server.json_rpc', JsonRpcHandler::class)
54-
->args([
55-
service('mcp.message_factory'),
56-
tagged_iterator('mcp.server.request_handler'),
57-
tagged_iterator('mcp.server.notification_handler'),
58-
service('logger')->ignoreOnInvalid(),
59-
])
6034
->set('mcp.server', Server::class)
61-
->args([
62-
service('mcp.server.json_rpc'),
63-
service('logger')->ignoreOnInvalid(),
64-
])
65-
->alias(Server::class, 'mcp.server')
66-
->set('mcp.server.sse.store.cache_pool', CachePoolStore::class)
67-
->args([
68-
service('cache.app'),
69-
])
70-
->set('mcp.tool_chain', ToolChain::class)
71-
->args([
72-
tagged_iterator('mcp.tool'),
73-
])
74-
->alias('mcp.tool_executor', 'mcp.tool_chain')
75-
->alias('mcp.tool_collection', 'mcp.tool_chain')
35+
->factory([service('mcp.server.builder'), 'build'])
36+
7637
;
7738
};

0 commit comments

Comments
 (0)