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..1c7e9a8ff0 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 7c3a6d244e..63e1e8b029 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..3ff4e16a0a 100644 --- a/docs/ai/mcp/mcp_usage.md +++ b/docs/ai/mcp/mcp_usage.md @@ -91,7 +91,11 @@ 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. + + `discovery_cache: ~` could have been used for developement so cache pool wouldn't have to be cleared after each change. + But it's not recommended for production. + By using the filesystem storage, this example can illustrate that cache pool needs to be cleared to have the latest capabilities 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 +105,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 +173,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 +192,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 +206,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 +216,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 +226,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 +236,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 +246,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.