Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions code_samples/mcp/config/packages/mcp.dil.yaml
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions code_samples/mcp/mcp-data-intelligence-wrapper.sh
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion code_samples/mcp/mcp-ibexa-example-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" \
Expand All @@ -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
13 changes: 7 additions & 6 deletions code_samples/mcp/mcp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" \
Expand All @@ -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",
Expand All @@ -43,15 +44,15 @@ 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 '{
"jsonrpc": "2.0",
"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 '{
Expand All @@ -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 '{
Expand All @@ -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 '{
Expand All @@ -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 '{
Expand Down
8 changes: 7 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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": [
Expand Down
41 changes: 37 additions & 4 deletions docs/ai/mcp/mcp_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,27 @@
[[= 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.<repository_identifier>.<server_identifier>`.
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.<server_identifier>`.
You can list them by running the following command:

`php bin/console debug:router --siteaccess=<within_scope_siteaccess> ibexa.mcp`
```bash
php bin/console debug:router --siteaccess=<within_scope_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.

Check notice on line 86 in docs/ai/mcp/mcp_config.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_config.md#L86

[Ibexa.Passive] Try to avoid passive tense, when possible.
Raw output
{"message": "[Ibexa.Passive] Try to avoid passive tense, when possible.", "location": {"path": "docs/ai/mcp/mcp_config.md", "range": {"start": {"line": 86, "column": 127}}}, "severity": "INFO"}
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

Expand All @@ -82,7 +98,7 @@
| [`tools`](#tool-configuration) | array | No | `[]` | List of tool classes |
| <nobr>[`discovery_cache`](#discovery-cache)</nobr> | 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 | `[`<br><nobr>`'localhost',`</nobr><br>`'127.0.0.1',`<br>`'[::1]'`<br>`]` | Accepted `Host` headers |
| [`allowed_hosts`](#allowed-hosts) | array | No | `[`<br><nobr>`'localhost',`</nobr><br>`'127.0.0.1',`<br>`'[::1]'`<br>`]` | Accepted `Host` headers |

!!! note "New servers are disabled by default"

Expand Down Expand Up @@ -128,6 +144,15 @@
- `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) =]]
Expand Down Expand Up @@ -163,6 +188,14 @@
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.
Expand Down Expand Up @@ -215,7 +248,7 @@

``` 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'
```
8 changes: 7 additions & 1 deletion docs/ai/mcp/mcp_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
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)

Check failure on line 24 in docs/ai/mcp/mcp_guide.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_guide.md#L24

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/ai/mcp/mcp_guide.md", "range": {"start": {"line": 24, "column": 85}}}, "severity": "ERROR"}

An MCP server allows the agent to discover available tools, inspect their parameters, learn how to use them, and select the correct action.

Expand All @@ -40,3 +40,9 @@

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

Check notice on line 46 in docs/ai/mcp/mcp_guide.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_guide.md#L46

[Ibexa.SentenceCapitalizationInHeadings] Use sentence-style capitalization in headings
Raw output
{"message": "[Ibexa.SentenceCapitalizationInHeadings] Use sentence-style capitalization in headings", "location": {"path": "docs/ai/mcp/mcp_guide.md", "range": {"start": {"line": 46, "column": 5}}}, "severity": "INFO"}

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.
30 changes: 20 additions & 10 deletions docs/ai/mcp/mcp_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
description: TODO.
month_change: true
---

# Data Intelligence Layer

Check notice on line 6 in docs/content_management/data_intelligence_layer/data_intelligence_layer.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/data_intelligence_layer/data_intelligence_layer.md#L6

[Ibexa.SentenceCapitalizationInHeadings] Use sentence-style capitalization in headings
Raw output
{"message": "[Ibexa.SentenceCapitalizationInHeadings] Use sentence-style capitalization in headings", "location": {"path": "docs/content_management/data_intelligence_layer/data_intelligence_layer.md", "range": {"start": {"line": 6, "column": 3}}}, "severity": "INFO"}

The Data Intelligence Layer (DIL) provides content metrics.

[[= cards([
"content_management/data_intelligence_layer/dil_guide",
"content_management/data_intelligence_layer/dil_config",
]) =]]

TODO:

Check notice on line 16 in docs/content_management/data_intelligence_layer/data_intelligence_layer.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/data_intelligence_layer/data_intelligence_layer.md#L16

[Ibexa.Lists] Verify list formatting: Full sentences should start with uppercase and end with a period. Sentence fragments should start with lowercase and have no period.
Raw output
{"message": "[Ibexa.Lists] Verify list formatting: Full sentences should start with uppercase and end with a period. Sentence fragments should start with lowercase and have no period.", "location": {"path": "docs/content_management/data_intelligence_layer/data_intelligence_layer.md", "range": {"start": {"line": 16, "column": 1}}}, "severity": "INFO"}
- Product guide
- Install & Config
- User doc?

Check notice on line 19 in docs/content_management/data_intelligence_layer/data_intelligence_layer.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/data_intelligence_layer/data_intelligence_layer.md#L19

[Ibexa.Lists] Verify list formatting: Full sentences should start with uppercase and end with a period. Sentence fragments should start with lowercase and have no period.
Raw output
{"message": "[Ibexa.Lists] Verify list formatting: Full sentences should start with uppercase and end with a period. Sentence fragments should start with lowercase and have no period.", "location": {"path": "docs/content_management/data_intelligence_layer/data_intelligence_layer.md", "range": {"start": {"line": 19, "column": 1}}}, "severity": "INFO"}
- API:

Check notice on line 20 in docs/content_management/data_intelligence_layer/data_intelligence_layer.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/data_intelligence_layer/data_intelligence_layer.md#L20

[Ibexa.Lists] Verify list formatting: Full sentences should start with uppercase and end with a period. Sentence fragments should start with lowercase and have no period.
Raw output
{"message": "[Ibexa.Lists] Verify list formatting: Full sentences should start with uppercase and end with a period. Sentence fragments should start with lowercase and have no period.", "location": {"path": "docs/content_management/data_intelligence_layer/data_intelligence_layer.md", "range": {"start": {"line": 20, "column": 1}}}, "severity": "INFO"}
- PHP API

Check notice on line 21 in docs/content_management/data_intelligence_layer/data_intelligence_layer.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/data_intelligence_layer/data_intelligence_layer.md#L21

[Ibexa.Lists] Verify list formatting: Full sentences should start with uppercase and end with a period. Sentence fragments should start with lowercase and have no period.
Raw output
{"message": "[Ibexa.Lists] Verify list formatting: Full sentences should start with uppercase and end with a period. Sentence fragments should start with lowercase and have no period.", "location": {"path": "docs/content_management/data_intelligence_layer/data_intelligence_layer.md", "range": {"start": {"line": 21, "column": 1}}}, "severity": "INFO"}
- REST API
- GraphQL API

Check notice on line 23 in docs/content_management/data_intelligence_layer/data_intelligence_layer.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/data_intelligence_layer/data_intelligence_layer.md#L23

[Ibexa.Lists] Verify list formatting: Full sentences should start with uppercase and end with a period. Sentence fragments should start with lowercase and have no period.
Raw output
{"message": "[Ibexa.Lists] Verify list formatting: Full sentences should start with uppercase and end with a period. Sentence fragments should start with lowercase and have no period.", "location": {"path": "docs/content_management/data_intelligence_layer/data_intelligence_layer.md", "range": {"start": {"line": 23, "column": 1}}}, "severity": "INFO"}
- MCP tools
- Extend
Loading
Loading