diff --git a/code_samples/mcp/config/packages/mcp.dil.yaml b/code_samples/mcp/config/packages/mcp.dil.yaml new file mode 100644 index 0000000000..549f247161 --- /dev/null +++ b/code_samples/mcp/config/packages/mcp.dil.yaml @@ -0,0 +1,32 @@ +when@dev: + ibexa: + repositories: + default: + mcp: + data_intelligence_layer: + allowed_hosts: + - 'my-ddev-project.ddev.site' + - '127.0.0.1' +when@prod: + ibexa: + repositories: + default: + mcp: + data_intelligence_layer: + allowed_hosts: + - 'admin.example.com' +ibexa: + repositories: + default: + mcp: + data_intelligence_layer: + discovery_cache: ibexa.cache_pool + session: + type: psr16 + service: ibexa.cache_pool + prefix: dil_mcp_session_ + system: + admin_group: + mcp: + servers: + - data_intelligence_layer diff --git a/code_samples/mcp/mcp-data-intelligence-wrapper.sh b/code_samples/mcp/mcp-data-intelligence-wrapper.sh new file mode 100644 index 0000000000..b51a2ff3ff --- /dev/null +++ b/code_samples/mcp/mcp-data-intelligence-wrapper.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +export NODE_TLS_REJECT_UNAUTHORIZED=0 + +baseUrl='http://localhost' # Adapt to your test case +mcpServer="$baseUrl/admin/mcp/data-intelligence" + +jwtToken=$(curl -s -X 'POST' \ + "$baseUrl/api/ibexa/v2/user/token/jwt" \ + -H "X-Siteaccess: admin" \ + -H 'Content-Type: application/vnd.ibexa.api.JWTInput+json' \ + -H 'Accept: application/vnd.ibexa.api.JWT+json' \ + -d '{ + "JWTInput": { + "_media-type": "application/vnd.ibexa.api.JWTInput+json", + "username": "admin", + "password": "publish" + } + }' | jq -r .JWT.token) + +exec npx -y supergateway \ + --streamableHttp "$mcpServer" \ + --oauth2Bearer "$jwtToken" \ + --header 'Accept-Language: en' \ + --logLevel none 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/composer.json b/composer.json index 4afa9230aa..6d67d5d010 100644 --- a/composer.json +++ b/composer.json @@ -9,10 +9,15 @@ } }, "repositories": [ + { + "type": "vcs", + "url": "https://github.com/ibexa/data-intelligence-layer" + }, { "type": "composer", "url": "https://updates.ibexa.co" } + ], "require": { "php": "^8.3" @@ -99,7 +104,8 @@ "ibexa/ckeditor-premium": "~6.0.x-dev", "ibexa/measurement": "~6.0.x-dev", "ibexa/connector-actito": "~6.0.x-dev", - "ibexa/fastly": "~6.0.x-dev" + "ibexa/fastly": "~6.0.x-dev", + "ibexa/data-intelligence-layer": "~6.0.x-dev" }, "scripts": { "fix-cs": [ diff --git a/docs/ai/mcp/mcp_config.md b/docs/ai/mcp/mcp_config.md index 4c9386cead..a641722cf7 100644 --- a/docs/ai/mcp/mcp_config.md +++ b/docs/ai/mcp/mcp_config.md @@ -64,11 +64,27 @@ 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` +``` + +### Built-in MCP servers + +Some MCP servers are already defined on default installation. + +- `data_intelligence_layer` (`/mcp/data-intelligence`) - The [Data Intelligence Layer](data_intelligence_layer.md) MCP server is bundled with tool sets from `Ibexa\Bundle\DataIntelligenceLayer\Mcp\` namespace and expose metrics about content to AI agent TODO: like the Orchestration chat one. + It's enabled by default but not assigned to any SiteAccess and not allowing other hosts than local. For more information, see [DIL MCP server configuration](dil_config.md#mcp-server-configuration). ### MCP server options @@ -82,7 +98,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" @@ -128,6 +144,15 @@ MCP Servers LTS Update comes with the following **experimental** built-in tools: - `list_non_translated_content_ids` - lists IDs of content which have missing translations for a given language code. - `Ibexa\Mcp\Tool\SeoTools` - `get_non_seo_content_ids` - returns IDs of content items that are missing SEO optimization (no meta title tag). Useful for identifying content that needs SEO attention. +- `Ibexa\Bundle\DataIntelligenceLayer\Mcp\MetricDiscoveryTools` + - `dil_discover_metrics` - TODO +- `Ibexa\Bundle\DataIntelligenceLayer\Mcp\MetricReaderTools` + - `dil_get_metric` - TODO + - `dil_list_entity_metrics` - TODO + - `dil_query_metrics` - TODO + - `dil_count_metrics` - TODO + - `dil_get_translation_backlog` - TODO + - `dil_get_translation_subtree_coverage` - TODO ``` yaml hl_lines="5-7" [[= include_code('code_samples/mcp/mcp.matrix.yaml', 4, 7) =]] @@ -163,6 +188,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 +248,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..b399a2d3dc 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 Ibexa DXP.](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. @@ -40,3 +40,9 @@ With the MCP Servers feature, you can: MCP servers are defined specifically for each [repository](repository_configuration.md) and assigned to individual [SiteAccesses](siteaccess.md) scopes. This way you can build flexible configurations that match different contexts. + +## Built-in MCP servers + +### DIL MCP server + +The [Data Intelligence Layer](data_intelligence_layer.md) MCP server is an already set up server that expose tools to access metrics about content so an AI agent can work on content quality or translation coverage. 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/content_management/data_intelligence_layer/data_intelligence_layer.md b/docs/content_management/data_intelligence_layer/data_intelligence_layer.md new file mode 100644 index 0000000000..c5bd02a6e9 --- /dev/null +++ b/docs/content_management/data_intelligence_layer/data_intelligence_layer.md @@ -0,0 +1,25 @@ +--- +description: TODO. +month_change: true +--- + +# Data Intelligence Layer + +The Data Intelligence Layer (DIL) provides content metrics. + +[[= cards([ +"content_management/data_intelligence_layer/dil_guide", +"content_management/data_intelligence_layer/dil_config", +]) =]] + +TODO: + +- Product guide +- Install & Config +- User doc? +- API: + - PHP API + - REST API + - GraphQL API + - MCP tools +- Extend diff --git a/docs/content_management/data_intelligence_layer/dil_config.md b/docs/content_management/data_intelligence_layer/dil_config.md new file mode 100644 index 0000000000..20fc728eff --- /dev/null +++ b/docs/content_management/data_intelligence_layer/dil_config.md @@ -0,0 +1,36 @@ +--- +description: TODO. +month_change: true +--- + +# Data Intelligence Layer configuration + +## Background tasks + +Metric data are computed as a [background task using Ibexa Messenger](background_tasks.md). + +- `ibexa.data_intelligence_layer.freshness.default_interval_days`: Number of days between computation of the metrics - default is 90 days +- `ibexa.data_intelligence_layer.freshness.content_type_intervals`: Custom number of days per content type - default is empty + +```yaml +parameters: + ibexa.data_intelligence_layer.freshness.default_interval_days: 30 # Increase frequency to every 30 days for all content types + ibexa.data_intelligence_layer.freshness.content_type_intervals: # Map content type identifier to custom interval in days + article: 15 # Compute metrics for articles every 15 days +``` + +## MCP server configuration + +The MCP server `data_intelligence_layer` is already configured for the path `/mcp/data-intelligence` and is enabled by default. +It needs + +- to be associated to some SiteAccesses +- to be allowed to be accessed from other hosts than `localhost`, `127.0.0.1`, and `[::1]` +- to have discovery cache enabled in production (by default, it's disabled for development) +- to have another session storage than the default `public/var/` directory + +For example, assign it to the `admin_group`, allow your production admin domain and development domains, use the default cache pool for discovery cache and sessions: + +```yaml +[[= include_code('code_samples/mcp/config/packages/mcp.dil.yaml') =]] +``` diff --git a/docs/content_management/data_intelligence_layer/dil_guide.md b/docs/content_management/data_intelligence_layer/dil_guide.md new file mode 100644 index 0000000000..87bbb0349b --- /dev/null +++ b/docs/content_management/data_intelligence_layer/dil_guide.md @@ -0,0 +1,11 @@ +--- +description: TODO. +month_change: true +--- + +# Data Intelligence Layer product guide + +The Data Intelligence Layer (DIL) systematically collects, aggregates, and exposes editorial and content-centric metrics. +Editors and AI agents can use those metrics to identify possible content improvements. + +TODO: List available metrics and their purpose. diff --git a/docs/content_management/data_intelligence_layer/dil_mcp.md b/docs/content_management/data_intelligence_layer/dil_mcp.md new file mode 100644 index 0000000000..7b26549b58 --- /dev/null +++ b/docs/content_management/data_intelligence_layer/dil_mcp.md @@ -0,0 +1,40 @@ +--- +description: TODO. +month_change: true +--- + +# DIL MCP server + +The Data Intelligence Layer come with a built-in MCP server helpin AI agents to fetch metrics and use them to provide content improvement suggestions or take actions. + +## MCP server configuration + +You can check the service existence with the following command: + +```bash +php bin/console debug:container ibexa.mcp.server.default.data_intelligence_layer +``` + +You can check the route existence with the following command: + +```bash +php bin/console debug:router ibexa.mcp.data_intelligence_layer +``` + +This MCP server need to be associated to some SiteAccesses and allowed to be accessed from some hosts. +For details, see [Data Intelligence Layer configuration](dil_config.md#mcp-server-configuration). + +## MCP server test + +You can use the MCP server from an agent CLI command. +Like in the [Work with MCP servers example](mcp_usage.md#fully-scripted-variant), you can use a wrapper script to ease JWT token acquisition. + +```bash hl_lines="7 11 25" +[[= include_code('code_samples/mcp/mcp-data-intelligence-wrapper.sh') =]] +``` + +Notice: + +- the `admin` in MCP server URL as default [`URIElement: 1` SiteAccess matching](siteaccess_matching.md#urielement) is used in this local test example +- the `X-Siteaccess: admin` header when requiring the JWT token TODO: mention this need in mcp_usage.md example instead. +- the `Accept-Language: en` header when establishing the gateway TODO: Why is it suddenly needed? Because it's the admin SiteAccess? diff --git a/docs/product_guides/product_guides.md b/docs/product_guides/product_guides.md index 4516c3ea34..60237db2fe 100644 --- a/docs/product_guides/product_guides.md +++ b/docs/product_guides/product_guides.md @@ -17,6 +17,7 @@ Discover the primary ones with the help of product guides. Condensed content all "content_management/pages/page_builder_guide", "content_management/forms/form_builder_guide", "content_management/collaborative_editing/collaborative_editing_guide", + "content_management/data_intelligence_layer/dil_guide", "customer_management/customer_portal", "personalization/personalization_guide", "product_catalog/product_catalog_guide", diff --git a/mkdocs.yml b/mkdocs.yml index 48b9191c9c..a88768f814 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -292,6 +292,11 @@ nav: - Time field type: content_management/field_types/field_type_reference/timefield.md - URL field type: content_management/field_types/field_type_reference/urlfield.md - User field type: content_management/field_types/field_type_reference/userfield.md + - Data Intelligence Layer: + - Data Intelligence Layer: content_management/data_intelligence_layer/data_intelligence_layer.md + - DIL product guide: content_management/data_intelligence_layer/dil_guide.md + - DIL configuration: content_management/data_intelligence_layer/dil_config.md + - DIL MCP server: content_management/data_intelligence_layer/dil_mcp.md - Collaborative editing: - Collaborative editing: content_management/collaborative_editing/collaborative_editing.md - Collaborative editing product guide: content_management/collaborative_editing/collaborative_editing_guide.md