Skip to content
Open
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
240 changes: 168 additions & 72 deletions docs/ai/mcp/mcp_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

The [[= product_name =]] MCP server framework (`ibexa/mcp`) is built on top of the [official PHP SDK for MCP (`mcp/sdk`)](https://github.com/modelcontextprotocol/php-sdk).

A PHP class that implements MCP server capabilities such as tools, prompts, or resources, must:
A PHP class that implements MCP server capabilities such as tools, prompts, or resources must:

- implement [`Ibexa\Contracts\Mcp\McpCapabilityInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Mcp-McpCapabilityInterface.html) so that it can be scanned for capabilities
- use attributes from the [`Ibexa\Contracts\Mcp\Attribute` namespace](/api/php_api/php_api_reference/namespaces/ibexa-contracts-mcp-attribute.html) to declare capabilities
Expand All @@ -25,7 +25,7 @@

- `servers` - array of server identifiers the tool is assigned to
<br>For more information, see [tools configuration](mcp_config.md#tool-configuration).
- `name` - tool codename - if not set, function name is used
- `name` - tool codename - if not set, the function name is used

Check notice on line 28 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L28

[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_usage.md", "range": {"start": {"line": 28, "column": 58}}}, "severity": "INFO"}
- `title` - tool title for user interfaces - if not set, the `name` is used
- `description` - tool description, used by AI agents to understand the tool's purpose
- `icons` - array of [`Mcp\Schema\Icon`](https://github.com/modelcontextprotocol/php-sdk/blob/main/src/Schema/Icon.php) instances
Expand Down Expand Up @@ -53,7 +53,7 @@
It accepts several arguments that describe how the prompt is used:

- `servers` - array of server identifiers exposing this prompt - required for prompts
- `name` (optional) - prompt codename - if not set, method name is used
- `name` (optional) - prompt codename - if not set, the method name is used

Check notice on line 56 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L56

[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_usage.md", "range": {"start": {"line": 56, "column": 69}}}, "severity": "INFO"}
- `title` (optional) - prompt title - if not set, `name` is used
- `description` (optional) - human-readable prompt description
- `icons` (optional) - array of [`Mcp\Schema\Icon`](https://github.com/modelcontextprotocol/php-sdk/blob/main/src/Schema/Icon.php) instances
Expand All @@ -63,7 +63,7 @@

The framework automatically builds the `arguments` array from the method arguments and their types.
Prompt method arguments must be strings to comply with the [`GetPromptRequestParams` schema](https://modelcontextprotocol.io/specification/2025-11-25/schema#getpromptrequestparams).
To add argument descriptions, use DocBlock `@param` tags, it's mapped to the `description` defined by the [`PromptArgument` schema](https://modelcontextprotocol.io/specification/2025-11-25/schema#promptargument).
To add argument descriptions, use DocBlock `@param` tags, which are mapped to the `description` defined by the [`PromptArgument` schema](https://modelcontextprotocol.io/specification/2025-11-25/schema#promptargument).

Check notice on line 66 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L66

[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_usage.md", "range": {"start": {"line": 66, "column": 65}}}, "severity": "INFO"}

## Example

Expand Down Expand Up @@ -99,7 +99,7 @@
[[= include_code('code_samples/mcp/config/packages/mcp.yaml') =]]
```

Adapt the `allowed_hosts` to your case, for example, if you want to use the DDEV `.ddev.site` domain instead of its `127.0.0.1` address equivalent.
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.

An `ibexa.mcp.example` route is now available:

Expand All @@ -113,15 +113,15 @@

The class contains:

- a method marked with an [`McpTool` attribute](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Mcp-Attribute-McpTool.html) that associates it to the `example` server as `greet` tool
- a method marked with an [`McpTool` attribute](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Mcp-Attribute-McpTool.html) that associates it with the `example` server as the `greet` tool
- a method marked with an [`McpPrompt` attribute](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Mcp-Attribute-McpPrompt.html) that provides a prompt template to users

``` php
[[= include_code('code_samples/mcp/src/Mcp/ExampleCapabilities.php') =]]
```

In this example, the `servers` attribute parameter associates only this tool with the `example` server.
Alternatively, you can assign all tools from the class to a server by using the `tools` parameter in server configuration.
Alternatively, you can assign all tools from the class to a server by using the `tools` parameter in the server configuration.
For more information, see [tools configuration](mcp_config.md#tool-configuration).

For the prompt, the `servers` parameter is required.
Expand Down Expand Up @@ -249,7 +249,7 @@
You can even use the inspector as a DDEV add-on with [`michtio/ddev-mcp-inspector`](https://github.com/michtio/ddev-mcp-inspector).
You still need to ask for a JWT token through REST or GraphQL APIs, and use it in the MCP Inspector configuration to connect to the server.

You can use a Web interface to obtain the JWT token:
You can use a web interface to obtain the JWT token:

Check notice on line 252 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L252

[Ibexa.SimplerWords] 'get(s)' is a simpler alternative to 'obtain'
Raw output
{"message": "[Ibexa.SimplerWords] 'get(s)' is a simpler alternative to 'obtain'", "location": {"path": "docs/ai/mcp/mcp_usage.md", "range": {"start": {"line": 252, "column": 32}}}, "severity": "INFO"}

- [REST live documentation](rest_api_authentication.md#jwt-token-obtained-through-rest-documentation)
- [GraphiQL](graphql.md#jwt-authentication)
Expand Down Expand Up @@ -283,21 +283,23 @@

![Right panel of MCP Inspector with a list of prompts obtained from the MCP server, and the test of the `greet` prompt](img/mcp-inspector-greet-prompt.png "MCP Inspector `greet` prompt test")

### Perform Copilot CLI test
### Perform Copilot or Claude Code test

Check notice on line 286 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L286

[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_usage.md", "range": {"start": {"line": 286, "column": 5}}}, "severity": "INFO"}

#### Add MCP server to Copilot CLI
You can test your MCP server with [Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/copilot-cli/about-copilot-cli) or [Claude Code CLI](https://code.claude.com/docs/en/overview), as illustrated here, or with any other agent or interface.

For the sake of the [Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/copilot-cli/about-copilot-cli) test in this example, you configure the MCP server in an `.mcp.json` file at the [[= product_name =]] project root.
This way it is only available for a session opened from there.
#### Add MCP server to agent CLI

For the sake of the agent test, in this example, you configure the MCP server in an `.mcp.json` file at the [[= product_name =]] project root.
This way, it is only available for a session opened from there.

You can handle the JWT token for this test in the following ways:

- Hard code the JWT token into the configuration and update it at every expiration.
- Wrap a JWT token request and an MCP server call into a script.
- [Hard-code the JWT token](#hard-coded-variant) into the configuration and update it at every expiration.
- [Wrap a JWT token request and an MCP server call into a script](#fully-scripted-variant).

##### Hard coded variant
##### Hard-coded variant

The hard coded JWT token configuration in `.mcp.json` looks as follows:
The hard-coded JWT token configuration in `.mcp.json` looks as follows:

``` json
[[= include_code('code_samples/mcp/http.mcp.json') =]]
Expand All @@ -307,12 +309,24 @@
You can request a token by using the GraphiQL web interface or a `curl` command, and then edit the file manually.
Alternatively, you can configure a shell script to request the JWT token, extract it from the response, and replace it in the file.

When Copilot complains that it can't communicate with the MCP server:
When Copilot or Claude Code complains that it can't communicate with the MCP server:

- Update the JWT token in the `.mcp.json` file.
- Reload the MCP servers in Copilot CLI with one of these methods:
- Run `/mcp reload` command to reload all MCP servers.
- Run `/mcp disable ibexa-example` and `/mcp enable ibexa-example` to only reload the `ibexa-example` server.
=== "Copilot CLI"

- Update the JWT token in the `.mcp.json` file.
- Reload the MCP servers in Copilot CLI with one of these methods:
- Run `/mcp reload` command to reload all MCP servers.
- Run `/mcp disable ibexa-example` and `/mcp enable ibexa-example` to only reload the `ibexa-example` server.

!!! note "Reloading multiple MCP servers"

If you have several MCP servers enabled globally, reloading all of them at the same time can be time-consuming.
Consider reloading them one by one.

=== "Claude Code CLI"

- Update the JWT token in the `.mcp.json` file.
- Run `/mcp reconnect ibexa-example` command to reconnect the `ibexa-example` MCP server.

##### Fully scripted variant

Expand All @@ -330,92 +344,174 @@
[[= include_code('code_samples/mcp/mcp-ibexa-example-wrapper.sh') =]]
```

When Copilot complains that it can't communicate with the MCP server, reload the MCP servers in Copilot CLI with one of these methods:
When the agent complains that it can't communicate with the MCP server, reload it:

- Run `/mcp reload` command to reload all MCP servers.
- Run `/mcp disable ibexa-example` and `/mcp enable ibexa-example` to only reload the `ibexa-example` server.
=== "Copilot CLI"

!!! note "Reloading multiple MCP servers"
Reload the MCP servers in Copilot CLI with one of these methods:

If you have several MCP servers enabled globally, reloading all of them at the same time can be time consuming.
Consider reloading them one by one.
- Run `/mcp reload` command to reload all MCP servers.
- Run `/mcp disable ibexa-example` and `/mcp enable ibexa-example` to only reload the `ibexa-example` server.

#### Run MCP server test with Copilot CLI
!!! note "Reloading multiple MCP servers"

Launch Copilot CLI at the project root, where the `.mcp.json` file is located:
If you have several MCP servers enabled globally, reloading all of them at the same time can be time-consuming.
Consider reloading them one by one.

```bash
cd /path/to/project
copilot
```
=== "Claude Code CLI"

Run `/mcp reconnect ibexa-example` command to reconnect the `ibexa-example` MCP server.

#### Run MCP server test with Copilot CLI or Claude Code CLI

Check notice on line 365 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L365

[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_usage.md", "range": {"start": {"line": 365, "column": 6}}}, "severity": "INFO"}

Launch the agent CLI at the project root, where the `.mcp.json` file is located:

Check notice on line 367 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L367

[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_usage.md", "range": {"start": {"line": 367, "column": 70}}}, "severity": "INFO"}

=== "Copilot CLI"

```bash
cd /path/to/project
copilot
```

=== "Claude Code CLI"

```bash
cd /path/to/project
claude
```

If prompted, confirm that you trust the files in this folder.
You may choose to have your choice remembered for the future.

Run the `/mcp show ibexa-example` to check the MCP server status and details:
You can check the MCP server status and details with the `/mcp` command:

```text
MCP Server: ibexa-example
=== "Copilot CLI"

Type: http
URL: http://localhost/mcp/example
Status: ✓ Connected
Source: /path/to/project/.mcp.json
Run the `/mcp show ibexa-example` command to check the MCP server status and details:

Tools (1/1 enabled):
✓ greet: Greet a user by name
```
``` text
MCP Server: ibexa-example

Type: stdio
Command: bash
Status: ✓ Connected
Source: /path/to/project/.mcp.json

Tools (1/1 enabled):
✓ greet: Greet a user by name (70 tokens)
```

=== "Claude Code CLI"

You can prompt Copilot to greet you.
Run the `/mcp` command and select ibexa-example to check the MCP server status and details:

``` text
Ibexa-example MCP Server

Check warning on line 409 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L409

[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.
Raw output
{"message": "[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.", "location": {"path": "docs/ai/mcp/mcp_usage.md", "range": {"start": {"line": 409, "column": 29}}}, "severity": "WARNING"}

Status: ✔ connected

Check warning on line 411 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L411

[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.
Raw output
{"message": "[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.", "location": {"path": "docs/ai/mcp/mcp_usage.md", "range": {"start": {"line": 411, "column": 34}}}, "severity": "WARNING"}
Command: bash

Check warning on line 412 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L412

[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.
Raw output
{"message": "[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.", "location": {"path": "docs/ai/mcp/mcp_usage.md", "range": {"start": {"line": 412, "column": 27}}}, "severity": "WARNING"}
Args: mcp-ibexa-example-wrapper.sh

Check warning on line 413 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L413

[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.
Raw output
{"message": "[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.", "location": {"path": "docs/ai/mcp/mcp_usage.md", "range": {"start": {"line": 413, "column": 51}}}, "severity": "WARNING"}
Config location: /path/to/project/.mcp.json

Check warning on line 414 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L414

[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.
Raw output
{"message": "[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.", "location": {"path": "docs/ai/mcp/mcp_usage.md", "range": {"start": {"line": 414, "column": 49}}}, "severity": "WARNING"}
Capabilities: tools

Check warning on line 415 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L415

[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.
Raw output
{"message": "[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.", "location": {"path": "docs/ai/mcp/mcp_usage.md", "range": {"start": {"line": 415, "column": 24}}}, "severity": "WARNING"}
Tools: 1 tool
```

You can prompt the agent to greet you.
It should use the `greet` tool to do so.

- You can ask it "Please, greet me." and it might respond with a request for your name if it doesn't already know it.
- You can also give it another name to greet, like in the prompt template.
- You can also give it another name to greet, as shown in the prompt template.

During a morning session, the interaction could look like this:

``` text
❯ Hi. Please, greet me.
=== "Copilot CLI"

◐ The user wants to be greeted. I'll use the ibexa-example-greet tool, but I don't know their name. Let me ask them first... actually, the tool requires a name. Let me ask the user for their name.
```text
❯ Hi. Please, greet me.

Wait, actually I can just greet them without knowing their name using a general greeting, but the tool requires a name parameter. Let me ask for their name.
◐ The user wants to be greeted. I'll use the ibexa-example-greet tool, but I don't know their name. Let me ask them first... actually, the tool requires a name. Let me ask the user for their name.

● Asked user What's your name?
└ Pulley
Wait, actually I can just greet them without knowing their name using a general greeting, but the tool requires a name parameter. Let me ask for their name.

● Greet a user (MCP: ibexa-example) · name: "Pulley"
└ {"general":"Hello, Pulley!","close":"Hey, Pulley!","morning":"Good morning, P...
● Asked user What's your name?
└ Pulley

● Good morning, Pulley! ☀️
● Greet a user (MCP: ibexa-example) · name: "Pulley"
└ {"general":"Hello, Pulley!","close":"Hey, Pulley!","morning":"Good morning, P...

❯ Hi. Please, greet me. We're close friends.
● Good morning, Pulley! ☀️

◐ The user wants a close friend greeting. From the previous response, I can see the "close" greeting is "Hey, Pulley!"
❯ Hi. Please, greet me. We're close friends.

● Greet a user (MCP: ibexa-example) · name: "Pulley"
└ {"general":"Hello, Pulley!","close":"Hey, Pulley!","morning":"Good morning, P...
◐ The user wants a close friend greeting. From the previous response, I can see the "close" greeting is "Hey, Pulley!"

● Hey, Pulley! 😄
● Greet a user (MCP: ibexa-example) · name: "Pulley"
└ {"general":"Hello, Pulley!","close":"Hey, Pulley!","morning":"Good morning, P...

❯ Please, greet someone named "ElePHPant" in a general way (so I could reuse it at any time)
● Hey, Pulley! 😄

◐ The user wants a general greeting for someone named "ElePHPant".
❯ Please, greet someone named "ElePHPant" in a general way (so I could reuse it at any time)

● greet (MCP: ibexa-example) · name: "ElePHPant"
└ {"general":"Hello, ElePHPant!","close":"Hey, ElePHPant!","morning":"Good morn...
◐ The user wants a general greeting for someone named "ElePHPant".

● Hello, ElePHPant! 🐘
● greet (MCP: ibexa-example) · name: "ElePHPant"
└ {"general":"Hello, ElePHPant!","close":"Hey, ElePHPant!","morning":"Good morn...

❯ Please, greet someone named "ElePHPant" like it's 22:30
● Hello, ElePHPant! 🐘

◐ 22:30 is evening time, so I'll use the evening greeting.
❯ Please, greet someone named "ElePHPant" like it's 22:30

● greet (MCP: ibexa-example) · name: "ElePHPant"
└ {"general":"Hello, ElePHPant!","close":"Hey, ElePHPant!","morning":"Good morn...
◐ 22:30 is evening time, so I'll use the evening greeting.

● Good evening, ElePHPant! 🌙
```
● greet (MCP: ibexa-example) · name: "ElePHPant"
└ {"general":"Hello, ElePHPant!","close":"Hey, ElePHPant!","morning":"Good morn...

● Good evening, ElePHPant! 🌙
```

=== "Claude Code CLI"

```text
❯ Hi. Please, greet me.

⏺ What's your name?

✻ Worked for 3s

❯ Pulley

Called ibexa-example

⏺ Hello, Pulley! 👋

✻ Churned for 4s

❯ Hi. Please, greet me. We're close friends now.

Called ibexa-example

⏺ Hey, Pulley! 👋

✻ Baked for 4s

❯ Please, greet someone named "ElePHPant" in a general way (so I could reuse it at any time)

Called ibexa-example

⏺ Hello, ElePHPant!

✻ Brewed for 5s

❯ Please, greet someone named "ElePHPant" like it's 22:30

⏺ That falls under the "evening" variant: Good evening, ElePHPant!

Check warning on line 507 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L507

[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.
Raw output
{"message": "[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.", "location": {"path": "docs/ai/mcp/mcp_usage.md", "range": {"start": {"line": 507, "column": 71}}}, "severity": "WARNING"}

✻ Sautéed for 2s
```

The agent's reflections, reaction times, and final responses, including the improvised emojis, may differ from those examples.
The key point is that the agent decides to use the `greet` tool, calls it with the right argument, and then uses the call result in its final output.

The Copilot's reflection and its final response, including the improvised emoji, may differ from this example.
The key point is that Copilot CLI decides to use the `greet` tool, calls it with the right argument, displays the call result, and then uses it in its final output.
You can fine-tune the prompt, or remove unnecessary variants if needed.
For example, you could instruct the agent to always use the time-of-day variants, or simply remove the `general` and `close` variants.

Check warning on line 516 in docs/ai/mcp/mcp_usage.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/ai/mcp/mcp_usage.md#L516

[Ibexa.VerySimply] Avoid using 'simply'.
Raw output
{"message": "[Ibexa.VerySimply] Avoid using 'simply'.", "location": {"path": "docs/ai/mcp/mcp_usage.md", "range": {"start": {"line": 516, "column": 86}}}, "severity": "WARNING"}
Removing what's unnecessary is more efficient than extending the instructions.
Loading