diff --git a/code_samples/mcp/mcp-ibexa-example-wrapper.sh b/code_samples/mcp/mcp-ibexa-example-wrapper.sh index ce42c09439..7baab7f2d8 100644 --- a/code_samples/mcp/mcp-ibexa-example-wrapper.sh +++ b/code_samples/mcp/mcp-ibexa-example-wrapper.sh @@ -2,6 +2,7 @@ set -e baseUrl='http://localhost' # Adapt to your test case +mcpServer="$baseUrl/mcp/example" jwtToken=$(curl -s -X 'POST' \ "$baseUrl/api/ibexa/v2/user/token/jwt" \ @@ -16,6 +17,6 @@ jwtToken=$(curl -s -X 'POST' \ }' | jq -r .JWT.token) exec npx -y supergateway \ - --streamableHttp "$baseUrl/mcp/example" \ + --streamableHttp "$mcpServer" \ --oauth2Bearer "$jwtToken" \ --logLevel none diff --git a/code_samples/mcp/mcp.sh b/code_samples/mcp/mcp.sh index 797c65983a..9d1d25d114 100644 --- a/code_samples/mcp/mcp.sh +++ b/code_samples/mcp/mcp.sh @@ -5,6 +5,7 @@ set +x baseUrl='http://localhost' # Adapt to your test case username='ibexa-example' password='Ibexa-3xample' +mcpServer="$baseUrl/mcp/example" curl -s -X 'POST' \ "$baseUrl/api/ibexa/v2/user/token/jwt" \ @@ -22,7 +23,7 @@ cat response.tmp.txt | jq jwtToken=$(cat response.tmp.txt | jq -r .JWT.token) rm response.tmp.txt -curl -s -i -X 'POST' "$baseUrl/mcp/example" \ +curl -s -i -X 'POST' "$mcpServer" \ -H "Authorization: Bearer $jwtToken" \ -d '{ "jsonrpc": "2.0", @@ -43,7 +44,7 @@ tail -n 1 response.tmp.txt | jq mcpSessionId=$(cat response.tmp.txt | grep -i 'Mcp-Session-Id:' | sed 's/Mcp-Session-Id: \([0-9a-f-]*\).*/\1/i') rm response.tmp.txt -curl -s -i -X 'POST' "$baseUrl/mcp/example" \ +curl -s -i -X 'POST' "$mcpServer" \ -H "Authorization: Bearer $jwtToken" \ -H "Mcp-Session-Id: $mcpSessionId" \ -d '{ @@ -51,7 +52,7 @@ curl -s -i -X 'POST' "$baseUrl/mcp/example" \ "method": "notifications/initialized" }' -curl -s -X 'POST' "$baseUrl/mcp/example" \ +curl -s -X 'POST' "$mcpServer" \ -H "Authorization: Bearer $jwtToken" \ -H "Mcp-Session-Id: $mcpSessionId" \ -d '{ @@ -60,7 +61,7 @@ curl -s -X 'POST' "$baseUrl/mcp/example" \ "method": "tools/list" }' | jq -curl -s -X 'POST' "$baseUrl/mcp/example" \ +curl -s -X 'POST' "$mcpServer" \ -H "Authorization: Bearer $jwtToken" \ -H "Mcp-Session-Id: $mcpSessionId" \ -d '{ @@ -75,7 +76,7 @@ curl -s -X 'POST' "$baseUrl/mcp/example" \ } }' | jq -curl -s -X 'POST' "$baseUrl/mcp/example" \ +curl -s -X 'POST' "$mcpServer" \ -H "Authorization: Bearer $jwtToken" \ -H "Mcp-Session-Id: $mcpSessionId" \ -d '{ @@ -84,7 +85,7 @@ curl -s -X 'POST' "$baseUrl/mcp/example" \ "method": "prompts/list" }' | jq -curl -s -X 'POST' "$baseUrl/mcp/example" \ +curl -s -X 'POST' "$mcpServer" \ -H "Authorization: Bearer $jwtToken" \ -H "Mcp-Session-Id: $mcpSessionId" \ -d '{ diff --git a/docs/ai/mcp/mcp_config.md b/docs/ai/mcp/mcp_config.md index 4c9386cead..8eb974bad3 100644 --- a/docs/ai/mcp/mcp_config.md +++ b/docs/ai/mcp/mcp_config.md @@ -64,11 +64,20 @@ You define MCP servers within a repository configuration and then assign those s [[= include_code('code_samples/mcp/mcp.matrix.yaml', 31, 35) =]] ``` +Servers are automatically registered as services with an ID following the pattern `ibexa.mcp.server..`. +You can list all defined servers by running the following command: + +```bash +php bin/console debug:container ibexa.mcp.server +``` + Routes are built automatically from MCP server `path` configs. Those routes are identified as `ibexa.mcp.`. You can list them by running the following command: -`php bin/console debug:router --siteaccess= ibexa.mcp` +```bash +php bin/console debug:router --siteaccess= ibexa.mcp` +``` ### MCP server options @@ -82,7 +91,7 @@ You can list them by running the following command: | [`tools`](#tool-configuration) | array | No | `[]` | List of tool classes | | [`discovery_cache`](#discovery-cache) | string | Yes | | PSR-6 or PSR-16 cache pool service identifier | | [`session`](#session-storage) | object | Yes | | Session storage configuration | -| [`allowed_hosts`](#allowed-hosts) | array | No | `[`
`'localhost',`
`'127.0.0.1',`
`'[::1]'`
`]` | Accepted `Host` headers | +| [`allowed_hosts`](#allowed-hosts) | array | No | `[`
`'localhost',`
`'127.0.0.1',`
`'[::1]'`
`]` | Accepted `Host` headers | !!! note "New servers are disabled by default" @@ -163,6 +172,14 @@ Clear the cache pool after making changes: php bin/console cache:pool:clear cache.redis.mcp ``` +!!! tip + + Use `ibexa.cache_pool` as service identifier to have the default [cache service](persistence_cache.md#cache-service). + +It can be set to `null` to disable caching to ease development, which isn't recommended for production environment. + +See another example of configuration in [Work with MCP servers](mcp_usage.md#configure-mcp-server). + ### Session storage MCP servers store session data in their own way. @@ -215,7 +232,7 @@ In this example, only requests from `admin.example.com` domain, `my-ddev-project ``` yaml [[= include_code('code_samples/mcp/mcp.matrix.yaml', 16, 16) =]] - - 'admin.example.com' - - '127.0.0.1' + - 'www.example.com' - 'my-ddev-project.ddev.site' + - '127.0.0.1' ``` diff --git a/docs/ai/mcp/mcp_guide.md b/docs/ai/mcp/mcp_guide.md index 5e822bab4f..4aa1977b43 100644 --- a/docs/ai/mcp/mcp_guide.md +++ b/docs/ai/mcp/mcp_guide.md @@ -21,7 +21,7 @@ However, when facing a specific REST or GraphQL API, an agent may misunderstand MCP servers make the discovery of available capabilities much easier. They help AI agents translate natural language prompts into concrete actions on the system. -![MCP communication diagram showing AI agent client connecting to MCP Server within Ibexa DXP.](img/mcp-com-diagram.png) +![MCP communication diagram showing AI agent client connecting to MCP server within [[= product_name =]].](img/mcp-com-diagram.png) An MCP server allows the agent to discover available tools, inspect their parameters, learn how to use them, and select the correct action. diff --git a/docs/ai/mcp/mcp_usage.md b/docs/ai/mcp/mcp_usage.md index 0f8f35534f..9741037921 100644 --- a/docs/ai/mcp/mcp_usage.md +++ b/docs/ai/mcp/mcp_usage.md @@ -91,7 +91,10 @@ The server: !!! note "Storage choice recommendations" Filesystem storage is convenient for the sake of this example and for testing. - For production, it is recommended that you use Redis or Valkey. + For production, it is recommended that you use Redis or Valkey to share cache among the cluster and improve performance. + + For development, you can set `discovery_cache: ~` to avoid clearing the cache after each change. + This example uses the filesystem storage to illustrate that you have to clear the cache pool to refresh the available capabilities, exactly as when deploying into production. In a new `config/packages/mcp.yaml` file, define a new MCP server for the `default` repository and assign it to all SiteAccesses: @@ -101,6 +104,12 @@ In a new `config/packages/mcp.yaml` file, define a new MCP server for the `defau Adapt the `allowed_hosts` to your case, for example, if you want to use the DDEV `.ddev.site` domain instead of the equivalent `127.0.0.1` address. +The server is automatically registered as a service with the ID `ibexa.mcp.server.default.example`: + +```bash +php bin/console debug:container ibexa.mcp.server.default.example +``` + An `ibexa.mcp.example` route is now available: ```bash @@ -163,16 +172,16 @@ To test the `example` MCP server, a sequence of `curl` commands is used to simul `jq`, `grep`, and `sed` are also used to parse or display outputs. -First, use the shell script to set the [[= product_name =]]'s base URL and user credentials as variables for easier reuse: +First, use the shell script to set the [[= product_name =]]'s base URL, user credentials, and MCP server URL as variables for easier reuse: ``` bash -[[= include_code('code_samples/mcp/mcp.sh', 5, 7) =]] +[[= include_code('code_samples/mcp/mcp.sh', 5, 8) =]] ``` Before you can communicate with the MCP server, you must first request a JWT token through the REST API: ``` bash -[[= include_code('code_samples/mcp/mcp.sh', 9, 23) =]] +[[= include_code('code_samples/mcp/mcp.sh', 10, 24) =]] ``` ``` json @@ -182,7 +191,7 @@ Before you can communicate with the MCP server, you must first request a JWT tok Then, perform [initialization](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization) to get an MCP session ID: ``` bash -[[= include_code('code_samples/mcp/mcp.sh', 21, 44) =]] +[[= include_code('code_samples/mcp/mcp.sh', 22, 45) =]] ``` ``` http @@ -196,7 +205,7 @@ Then, perform [initialization](https://modelcontextprotocol.io/specification/202 Validate the initialization: ``` bash -[[= include_code('code_samples/mcp/mcp.sh', 46, 52) =]] +[[= include_code('code_samples/mcp/mcp.sh', 47, 53) =]] ``` ``` http @@ -206,7 +215,7 @@ Validate the initialization: Get the [list of tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#listing-tools): ``` bash -[[= include_code('code_samples/mcp/mcp.sh', 54, 61) =]] +[[= include_code('code_samples/mcp/mcp.sh', 55, 62) =]] ``` ``` json @@ -216,7 +225,7 @@ Get the [list of tools](https://modelcontextprotocol.io/specification/2025-11-25 [Call](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#calling-tools) the `greet` tool: ``` bash -[[= include_code('code_samples/mcp/mcp.sh', 63, 76) =]] +[[= include_code('code_samples/mcp/mcp.sh', 64, 77) =]] ``` ``` json @@ -226,7 +235,7 @@ Get the [list of tools](https://modelcontextprotocol.io/specification/2025-11-25 Get the [list of prompts](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts#listing-prompts): ``` bash -[[= include_code('code_samples/mcp/mcp.sh', 78, 85) =]] +[[= include_code('code_samples/mcp/mcp.sh', 79, 86) =]] ``` ``` json @@ -236,7 +245,7 @@ Get the [list of prompts](https://modelcontextprotocol.io/specification/2025-11- [Get the prompt](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts#getting-a-prompt) of the `greet` method: ``` bash -[[= include_code('code_samples/mcp/mcp.sh', 87, 100) =]] +[[= include_code('code_samples/mcp/mcp.sh', 88, 101) =]] ``` ``` json diff --git a/docs/getting_started/install_with_ddev.md b/docs/getting_started/install_with_ddev.md index 15ca420b68..0dda9850d9 100644 --- a/docs/getting_started/install_with_ddev.md +++ b/docs/getting_started/install_with_ddev.md @@ -97,6 +97,44 @@ You can configure [Symfony Mailer]([[= symfony_doc =]]/mailer.html) to use the [ ddev config --web-environment-add MAILER_DSN=smtp://localhost:1025 ``` +#### Configure scheduled tasks (optional) + +You can [schedule tasks](install_ibexa_dxp.md#schedule-tasks) using [DDEV Cron add-on](https://addons.ddev.com/addons/ddev/ddev-cron). + +```bash +ddev add-on get ddev/ddev-cron +basedir='/var/www/html' +echo "* * * * * cd $basedir && php bin/console ibexa:cron:run --quiet --env=prod" >> .ddev/web-build/ibexa.cron +ddev restart +``` + +For more schedulable tasks and ways to schedule them, see [Additional scheduled tasks and advanced usage](install_ibexa_dxp.md#additional-scheduled-tasks-and-advanced-usage). + +You can run the following command to check Cron: + +```bash +ddev exec crontab -l +``` + +#### Configure background tasks (optional) + +You can launch [Ibexa Messenger](background_tasks.md) on DDEV project start. +Create or edit a DDEV config file, for example `.ddev/config.hooks.yaml`, and add the following [hook](https://docs.ddev.com/en/stable/users/configuration/hooks/): + +```yaml +hooks: + post-start: + - exec-host: ddev exec "php bin/console messenger:consume ibexa.messenger.transport --bus=ibexa.messenger.bus --siteaccess=admin --silent" & +``` + +You can change the verbosity and redirect the output if you want to log into a file: + +```yaml +hooks: + post-start: + - exec-host: ddev exec "php bin/console messenger:consume ibexa.messenger.transport --bus=ibexa.messenger.bus --siteaccess=admin -vv > var/log/messenger.log 2>&1" & +``` + #### Enable Mutagen (optional) If you're using macOS or Windows, you might want to enable [Mutagen](https://docs.ddev.com/en/stable/users/install/performance/#mutagen) to improve performance.