-
Notifications
You must be signed in to change notification settings - Fork 81
IBX-11536: MCP Servers #3106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 5.0
Are you sure you want to change the base?
IBX-11536: MCP Servers #3106
Changes from 7 commits
148d07c
7307f02
c2215e5
d1b50e2
3b4d89f
a40d3dc
9ba2f81
618984f
81def20
acd9291
c3dca5f
fff74f7
c0e0b7e
ad3cc47
0836df3
736edd1
6107946
a6953e1
a73386b
ec27fdf
b588db3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| ibexa: | ||
| repositories: | ||
| default: | ||
| mcp: | ||
| example: | ||
| path: /mcp/example | ||
| enabled: true | ||
| description: 'Example MCP Server' | ||
| instructions: 'Use this server to greet someone.' | ||
| discovery_cache: cache.tagaware.filesystem | ||
| session: | ||
| type: file | ||
| directory: '%kernel.cache_dir%/mcp/sessions' | ||
| system: | ||
| default: | ||
| mcp: | ||
| servers: | ||
| - example |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| baseUrl='http://localhost' # Adapt to your test case | ||
|
|
||
| jwtToken=$(curl -s -X 'POST' \ | ||
| "$baseUrl/api/ibexa/v2/user/token/jwt" \ | ||
| -H 'Content-Type: application/json' \ | ||
| -d '{ | ||
| "JWTInput": { | ||
| "_media-type": "application/vnd.ibexa.api.JWTInput", | ||
| "username": "admin", | ||
| "password": "publish" | ||
| } | ||
| }' | jq -r .JWT.token) | ||
|
|
||
| mcpSessionId=$(curl -s -i -X 'POST' "$baseUrl/mcp/example" \ | ||
| -H "Authorization: Bearer $jwtToken" \ | ||
| -d '{ | ||
| "jsonrpc": "2.0", | ||
| "id": 1, | ||
| "method": "initialize", | ||
| "params": { | ||
| "protocolVersion": "2025-03-26", | ||
| "capabilities": {}, | ||
| "clientInfo": { | ||
| "name": "test-curl-client", | ||
| "version": "1.0.0" | ||
| } | ||
| } | ||
| }' | grep 'Mcp-Session-Id:' | sed 's/Mcp-Session-Id: \([0-9a-f-]*\).*/\1/') | ||
|
|
||
| curl -s -i -X 'POST' "$baseUrl/mcp/example" \ | ||
| -H "Authorization: Bearer $jwtToken" \ | ||
| -H "Mcp-Session-Id: $mcpSessionId" \ | ||
| -d '{ | ||
| "jsonrpc": "2.0", | ||
| "method": "notifications/initialized" | ||
| }' | ||
|
|
||
| curl -s -X 'POST' "$baseUrl/mcp/example" \ | ||
| -H "Authorization: Bearer $jwtToken" \ | ||
| -H "Mcp-Session-Id: $mcpSessionId" \ | ||
| -d '{ | ||
| "jsonrpc": "2.0", | ||
| "id": 2, | ||
| "method": "tools/list" | ||
| }' | jq | ||
|
|
||
| curl -s -X 'POST' "$baseUrl/mcp/example" \ | ||
| -H "Authorization: Bearer $jwtToken" \ | ||
| -H "Mcp-Session-Id: $mcpSessionId" \ | ||
| -d '{ | ||
| "jsonrpc": "2.0", | ||
| "id": 3, | ||
| "method": "tools/call", | ||
| "params": { | ||
| "name": "greet", | ||
| "arguments": { | ||
| "name": "World" | ||
| } | ||
| } | ||
| }' | jq |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| namespace App\mcp\src\Command; | ||
|
|
||
| use Ibexa\Contracts\Mcp\McpServerConfigurationRegistryInterface; | ||
| use Symfony\Component\Console\Attribute\AsCommand; | ||
| use Symfony\Component\Console\Command\Command; | ||
| use Symfony\Component\Console\Style\SymfonyStyle; | ||
|
|
||
| #[AsCommand(name: 'app:mcp:server_list', description: 'List MCP servers')] | ||
| class McpServerListCommand | ||
| { | ||
| public function __construct(private readonly McpServerConfigurationRegistryInterface $configRegistry) | ||
| { | ||
| } | ||
|
|
||
| public function __invoke(SymfonyStyle $io): int | ||
| { | ||
| foreach($this->configRegistry->getServerConfigurations() as $serverConfiguration) { | ||
| $io->title($serverConfiguration->identifier); | ||
| dump($serverConfiguration); | ||
| } | ||
|
|
||
| return Command::SUCCESS; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| namespace App\mcp\src\Mcp; | ||
|
|
||
| use Ibexa\Contracts\Mcp\Attribute\McpTool; | ||
| use Ibexa\Contracts\Mcp\McpCapabilityInterface; | ||
|
|
||
| final readonly class ExampleTools implements McpCapabilityInterface | ||
| { | ||
| #[McpTool(servers: ['example'], description: 'Greet a user by name')] | ||
| public function greet(string $name): string | ||
| { | ||
| return sprintf('Hello, %s!', $name); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| --- | ||
| description: TODO. | ||
| page_type: landing_page | ||
| month_change: true | ||
| --- | ||
|
|
||
| # MCP Servers | ||
|
Check notice on line 7 in docs/mcp/mcp.md
|
||
|
|
||
| MCP servers allow AI interactions with the system. | ||
| Learn more about this protocol and [[= product_name_base =]] MCP Servers: | ||
|
|
||
| [[= cards([ | ||
| ("mcp/mcp_guide", "MCP Servers guide", "TODO."), | ||
| "mcp/mcp_config", | ||
| ], columns=2) =]] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| --- | ||
| description: TODO. | ||
| month_change: true | ||
| --- | ||
|
|
||
| TODO: built-in MCP servers VS custom MCP servers | ||
|
|
||
| # Set up an MCP server | ||
|
Check notice on line 8 in docs/mcp/mcp_config.md
|
||
|
|
||
| ## JWT | ||
|
Check notice on line 10 in docs/mcp/mcp_config.md
|
||
|
|
||
| MCP servers use JWT for authentication. | ||
|
|
||
| TODO: [Enable authorization header in `config/packages/lexik_jwt_authentication.yaml`](development_security.md#jwt-authentication). | ||
|
|
||
| In `config/packages/security.yaml`, uncomment the `ibexa_jwt_mcp` firewall. | ||
|
|
||
| TODO: Config to get a JWT token in the first place. Through [REST](rest_api_authentication.md#jwt-authentication), GraphQL or something else? | ||
|
|
||
| ## MCP Server configuration | ||
|
Check notice on line 20 in docs/mcp/mcp_config.md
|
||
|
|
||
| MCP servers are configured per repository then enabled per SiteAccess scope. | ||
|
Check notice on line 22 in docs/mcp/mcp_config.md
|
||
|
|
||
| ```yaml | ||
| ibexa: | ||
| repositories: | ||
| <repository_identifier>: | ||
| mcp: | ||
| <server_identifier>: | ||
| path: <server_route_path> | ||
| enabled: true | ||
| # Server options… | ||
| discovery_cache: <cache_pool_service> | ||
| session: | ||
| type: <psr16|file|memory> | ||
| # Session options… | ||
| system: | ||
| <siteaccess_scope>: | ||
| mcp: | ||
| servers: | ||
| - <server_identifier> | ||
| ``` | ||
|
|
||
| TODO: `ddev php bin/console debug:router --siteaccess=<within_scope_siteaccess>` should list some `ibexa.mcp.<server_identifier> GET|POST|DELETE|OPTIONS <server_route_path>` | ||
|
|
||
| TODO: Maybe explain that routes are built automatically from MCP server `path` configs thank to `config/routes/ibexa_mcp.yaml` and `\Ibexa\Bundle\Mcp\Routing\McpRouteLoader` | ||
|
Check notice on line 46 in docs/mcp/mcp_config.md
|
||
|
|
||
| ### MCP server options | ||
|
Check notice on line 48 in docs/mcp/mcp_config.md
|
||
|
|
||
| | Option | Type | Required | Default | Description | | ||
| |-------------------|---------|----------|---------|-----------------------------------------------| | ||
| | `path` | string | Yes | | MCP server endpoint path | | ||
| | `enabled` | boolean | No | `false` | Whether the server is enabled | | ||
|
Check notice on line 53 in docs/mcp/mcp_config.md
|
||
| | `version` | string | No | `1.0.0` | MCP server version | | ||
| | `description` | string | No | `null` | Human-readable server description | | ||
| | `instructions` | string | No | `null` | Instructions dedicated for LLM interaction | | ||
| | `discovery_cache` | string | Yes | | PSR-6 ou PSR-16 cache pool service identifier | | ||
|
Check failure on line 57 in docs/mcp/mcp_config.md
|
||
| | `session` | object | Yes | | Session storage configuration | | ||
|
|
||
| Notice that a server is disabled by default, it needs to be explicitly enabled. | ||
|
Check notice on line 60 in docs/mcp/mcp_config.md
|
||
|
|
||
| ### MCP server discovery cache | ||
|
Check notice on line 62 in docs/mcp/mcp_config.md
|
||
|
|
||
| TODO | ||
|
|
||
| ### MCP server session storage | ||
|
Check notice on line 66 in docs/mcp/mcp_config.md
|
||
|
|
||
| #### Options | ||
|
|
||
| | Option | Type | Default | Description | | ||
| |-------------|---------|----------|---------------------------------------------------| | ||
| | `type` | enum | `memory` | Session store type: `psr16`, `file`, or `memory` | | ||
| | `service` | string | `null` | PSR-16 cache service ID for `psr16` session store | | ||
| | `prefix` | string | `mcp_` | Key prefix for `psr16` session store | | ||
| | `directory` | string | `null` | Directory path for `file` session store | | ||
| | `ttl` | integer | `3600` | Session TTL in seconds | | ||
|
|
||
| #### PSR-16 | ||
|
Check notice on line 78 in docs/mcp/mcp_config.md
|
||
|
|
||
| Sessions are stored using a PSR-16 compatible cache implementation. Requires service option pointing to a valid cache service ID. | ||
|
Check notice on line 80 in docs/mcp/mcp_config.md
|
||
|
|
||
| ```yaml | ||
| session: | ||
| type: psr16 | ||
| service: cache.redis.mcp | ||
| prefix: 'mcp_<server_identifier>_' | ||
| services: | ||
| cache.redis.mcp: | ||
| public: true | ||
| class: Symfony\Component\Cache\Adapter\RedisTagAwareAdapter | ||
| parent: cache.adapter.redis | ||
| tags: | ||
| - name: cache.pool | ||
|
Check notice on line 93 in docs/mcp/mcp_config.md
|
||
| clearer: cache.app_clearer | ||
| provider: 'redis://mcp.redis:6379' | ||
| namespace: 'mcp' | ||
| ``` | ||
|
|
||
| #### File | ||
|
|
||
| Sessions are persisted to the filesystem. Requires directory option to be set. | ||
|
Check notice on line 101 in docs/mcp/mcp_config.md
|
||
|
|
||
| ```yaml | ||
| session: | ||
| type: file | ||
| directory: '%kernel.cache_dir%/mcp/sessions' | ||
| ``` | ||
|
|
||
| #### Memory | ||
|
|
||
| Sessions are stored in memory. Suitable for development and STDIO transport. | ||
|
Check notice on line 111 in docs/mcp/mcp_config.md
|
||
|
|
||
| TODO: Might not work with DDEV or Docker | ||
|
|
||
| ```yaml | ||
| session: | ||
| type: memory | ||
| ``` | ||
|
|
||
| ## MCP server capabilities | ||
|
Check notice on line 120 in docs/mcp/mcp_config.md
|
||
|
|
||
| TODO: `Ibexa\Contracts\Mcp\McpCapabilityInterface` | ||
|
|
||
| TODO: `Ibexa\Contracts\Mcp\Attribute` namespace | ||
|
|
||
| ## Example | ||
|
|
||
| This example introduce an `example` MCP server with a single `greet` tool. | ||
| It's enabled on all SiteAccesses. | ||
| It's accessible with the path `/mcp/example` (for example, on `http://localhost/mcp/example` and `http://localhost/admin/mcp/example`). | ||
| It uses files for both discovery cache and session storage. | ||
|
|
||
| In a new `config/packages/mcp.yaml` file, the configuration of the MCP server: | ||
|
|
||
| ``` yaml | ||
| [[= include_file('code_samples/mcp/config/packages/mcp.yaml') =]] | ||
| ``` | ||
|
|
||
| Then, a `McpCapabilityInterface`containing a `greet` function with a `McpTool` attribute associating with the `example` server: | ||
|
|
||
| ``` php | ||
| [[= include_file('code_samples/mcp/src/Mcp/ExampleTools.php') =]] | ||
| ``` | ||
|
|
||
| To check the server configuration, a short command using the MCP server configuration registry (injected through `McpServerConfigurationRegistryInterface` and autowiring): | ||
|
Check notice on line 145 in docs/mcp/mcp_config.md
|
||
|
|
||
| ``` php | ||
| [[= include_file('code_samples/mcp/src/Command/McpServerListCommand.php') =]] | ||
| ``` | ||
|
|
||
| To test the `example` MCP server, a sequence of `curl` commands is used to simulate an AI to MCP server communication. | ||
|
Check notice on line 151 in docs/mcp/mcp_config.md
|
||
|
|
||
| - Ask for a [JWT token through REST](/api/rest_api/rest_api_reference/rest_api_reference.html#tag/User-Token/operation/api_usertokenjwt_post) | ||
| - Initialize a connection to the MCP server | ||
|
Check notice on line 154 in docs/mcp/mcp_config.md
|
||
| - Validate the MCP Session ID | ||
|
Check notice on line 155 in docs/mcp/mcp_config.md
|
||
| - List the available tools | ||
|
Check notice on line 156 in docs/mcp/mcp_config.md
|
||
| - Call a tool | ||
|
|
||
| `jq`, `grep`, and `sed` are also used to parse or display outputs. | ||
|
|
||
| The [initialization](https://modelcontextprotocol.io/specification/draft/basic/lifecycle#initialization): | ||
|
|
||
| ``` bash | ||
| [[= include_file('code_samples/mcp/mcp.sh', 0, 36) =]] | ||
| ``` | ||
|
|
||
| ``` | ||
|
Check failure on line 167 in docs/mcp/mcp_config.md
|
||
| HTTP/1.1 202 Accepted | ||
| Access-Control-Allow-Headers: Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, Authorization, Accept | ||
| Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS | ||
| Access-Control-Expose-Headers: Mcp-Session-Id | ||
| ``` | ||
|
|
||
| The [list of tools](https://modelcontextprotocol.io/specification/draft/server/tools#listing-tools): | ||
|
|
||
| ``` bash | ||
| [[= include_file('code_samples/mcp/mcp.sh', 37, 45) =]] | ||
| ``` | ||
|
|
||
| ```json | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "id": 2, | ||
| "result": { | ||
| "tools": [ | ||
| { | ||
| "name": "greet", | ||
| "inputSchema": { | ||
| "type": "object", | ||
| "properties": { | ||
| "name": { | ||
| "type": "string" | ||
| } | ||
| }, | ||
| "required": [ | ||
| "name" | ||
| ] | ||
| }, | ||
| "description": "Greet a user by name" | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The `greet` [tool usage](https://modelcontextprotocol.io/specification/draft/server/tools#calling-tools): | ||
|
|
||
| ``` bash | ||
| [[= include_file('code_samples/mcp/mcp.sh', 46) =]] | ||
| ``` | ||
|
|
||
| ```json | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "id": 3, | ||
| "result": { | ||
| "content": [ | ||
| { | ||
| "type": "text", | ||
| "text": "Hello, World!" | ||
| } | ||
| ], | ||
| "isError": false | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| TODO: Connect an AI client to the MCP server. [Copilot CLI MCP server addition](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers) is strangely asking for some OAuth ID even with a proper JWT/Bearer header. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| --- | ||
| description: TODO. | ||
| month_change: true | ||
| --- | ||
|
|
||
| # Model Context Protocol and [[= product_name_base =]] MCP Servers | ||
|
Check notice on line 6 in docs/mcp/mcp_guide.md
|
||
|
|
||
| [Model Context Protocol (MCP)](https://en.wikipedia.org/wiki/Model_Context_Protocol) is a protocol standardizing interactions between AIs and systems. | ||
|
|
||
| While [AI actions](ai_actions_guide.md) integrate AI to the back office, | ||
| [[= product_name =]]'s MCP servers offer a web interface usable by AIs outside the system. | ||
|
|
||
| `ibexa/mcp` package provides built-in MCP servers and a PHP API to create custom ones. | ||
|
|
||
| TODO: About built-in MCP servers (translations agents, SEO optimization agents,…) | ||
|
|
||
| MCP servers capabilities (tools, prompts, and resources) can be created and associated to MCP servers thanks to a PHP API mainly based on attributes. | ||
|
Check notice on line 17 in docs/mcp/mcp_guide.md
|
||
|
|
||
| MCP servers are configured per repository then enabled per SiteAccess scope, allowing for flexible configurations adapted to different contexts. | ||
|
Check notice on line 19 in docs/mcp/mcp_guide.md
|
||
|
|
||
| MCP servers have their own session storage mechanism, TODO: why, benefit,… | ||
Uh oh!
There was an error while loading. Please reload this page.