Skip to content

Work with MCP servers

The MCP Servers LTS Update includes several built-in tools. Additionally, you can create your own capabilities (tools, prompts, and resources) to expose custom features to AI agents through your MCP servers.

MCP server capabilities

The Ibexa DXP MCP server framework (ibexa/mcp) is built on top of the official PHP SDK for MCP (mcp/sdk).

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

Tools

The Ibexa\Contracts\Mcp\Attribute\McpTool attribute declares a method as an MCP tool. It accepts the following optional arguments:

The framework automatically builds an inputSchema from the method arguments and their types. To customize or extend the generated schema, you can:

If an argument is an enum, its possible values are listed in the schema (UntitledSingleSelectEnumSchema).

Prompts

MCP servers can also provide prompt templates to help users interact with AI agents connected to the server.

Methods that return a prompt are marked with the Ibexa\Contracts\Mcp\Attribute\McpPrompt attribute.

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 name - if not set, method name is used
  • description (optional) - human-readable prompt description
  • icons (optional) - array of Mcp\Schema\Icon instances
    For more information, see the icons specification.
  • meta (optional) - rarely used free-form array for additional metadata
    For more information, see the _meta specification.

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. To add argument descriptions, use DocBlock @param tags, it's mapped to the description defined by the PromptArgument schema.

Example

To keep the example focused on MCP server configuration and capability creation, it doesn't interact with the Ibexa DXP repository.

Create user account

In this example, the MCP server uses JWT tokens created with a dedicated user account.

In Ibexa DXP's back office, create a user in the Guest accounts user group, with login ibexa-example and password Ibexa-3xample.

Configure MCP server

This example introduces an MCP server named example, with a single tool called greet. The server:

  • is enabled on the default repository
  • is available in all SiteAccesses
  • is accessible with the path /mcp/example
    For example:
    • http://localhost/mcp/example
    • http://localhost/admin/mcp/example
  • uses file storage for both discovery cache and sessions

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.

In a new config/packages/mcp.yaml file, define a new MCP server for the default repository and assign it to all SiteAccesses:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
ibexa:
    repositories:
        default:
            mcp:
                example:
                    path: /mcp/example
                    enabled: true
                    description: 'Example MCP Server'
                    instructions: 'Use this server to greet someone.'
                    discovery_cache: cache.tagaware.filesystem
                    session:
                        type: psr16
                        directory: cache.tagaware.filesystem
    system:
        default:
            mcp:
                servers:
                    - example

An ibexa.mcp.example route is now available:

1
php bin/console debug:router ibexa.mcp.example

Create capability class

Create an ExampleCapabilities class that implements McpCapabilityInterface.

The class contains:

  • a method marked with an McpTool attribute that associates it to the example server as greet tool
  • a method marked with an McpPrompt attribute that provides a prompt template to users
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php declare(strict_types=1);

namespace App\Mcp;

use Ibexa\Contracts\Mcp\Attribute\McpPrompt;
use Ibexa\Contracts\Mcp\Attribute\McpTool;
use Ibexa\Contracts\Mcp\McpCapabilityInterface;
use Mcp\Schema\Icon;
use Mcp\Schema\ToolAnnotations;

final readonly class ExampleCapabilities implements McpCapabilityInterface
{
    /**
     * @param string $name The name of the person to greet
     *
     * @return array<string, string>
     */
    #[McpTool(
        servers: ['example'],
        name: 'greet',
        description: 'Greet a user by name',
        annotations: new ToolAnnotations(
            readOnlyHint: true,
            destructiveHint: false,
            idempotentHint: true,
            openWorldHint: false,
        ),
        icons: [new Icon(
            src: 'https://openmoji.org/data/color/svg/1F44B.svg',
        )],
        outputSchema: [
            'type' => 'object',
            'properties' => [
                'general' => [
                    'type' => 'string',
                    'description' => 'the safe way to greet someone',
                ],
                'close' => [
                    'type' => 'string',
                    'description' => 'when you\'re close to the person, like friends or relatives',
                ],
                'morning' => [
                    'type' => 'string',
                    'description' => 'when it\'s in the morning',
                ],
                'afternoon' => [
                    'type' => 'string',
                    'description' => 'when it\'s the afternoon',
                ],
                'evening' => [
                    'type' => 'string',
                    'description' => 'when it\'s late in the day',
                ],
            ],
        ],
    )]
    public function greetByName(string $name): array
    {
        return [
            'general' => sprintf('Hello, %s!', $name),
            'close' => sprintf('Hey, %s!', $name),
            'morning' => sprintf('Good morning, %s!', $name),
            'afternoon' => sprintf('Good afternoon, %s!', $name),
            'evening' => sprintf('Good evening, %s!', $name),
        ];
    }

    /**
     * @param string $name The name you want to be greeted by
     *
     * @return array<string, mixed>
     */
    #[McpPrompt(
        servers: ['example'],
        name: 'greet',
        description: 'Prompt to invoke the `greet` tool',
        icons: [new Icon(
            src: 'https://openmoji.org/data/color/svg/1F91D.svg',
        )],
    )]
    public function getGreetPrompt(string $name): array
    {
        return [
            'role' => 'user',
            'content' => [
                'type' => 'text',
                'text' => "Hi. My name is $name. Please, greet me.",
            ],
        ];
    }
}

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. For more information, see tools configuration.

For the prompt, the servers parameter is required. Therefore, the example prompt must use it to be associated with the example server.

During development and testing, you may need to clear the cache to ensure that new or modified capabilities are properly re-discovered. In this example, use the following command:

1
php bin/console cache:pool:clear cache.tagaware.filesystem

Cache clearing

During development, clear caches aggressively. The following commands clear all cache types, regardless of where they are stored:

1
2
php bin/console cache:clear
php bin/console cache:pool:clear --all

Create MCP server list command

To check the MCP server configuration, create a small command that uses the MCP server configuration registry injected through McpServerConfigurationRegistryInterface and autowiring:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php declare(strict_types=1);

namespace App\Command;

use Ibexa\Contracts\Mcp\McpServerConfigurationRegistryInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(name: 'app:mcp:server_list', description: 'List MCP servers')]
class McpServerListCommand
{
    public function __construct(private readonly McpServerConfigurationRegistryInterface $configRegistry)
    {
    }

    public function __invoke(SymfonyStyle $io): int
    {
        foreach($this->configRegistry->getServerConfigurations() as $serverConfiguration) {
            $io->title($serverConfiguration->identifier);
            dump($serverConfiguration);
        }

        return Command::SUCCESS;
    }
}

Perform curl test

To test the example MCP server, a sequence of curl commands is used to simulate the communication between an AI client and the MCP server.

  • Ask for a JWT token through REST.
  • Initialize a connection to the MCP server.
  • Validate the MCP Session ID.
  • List the available tools.
  • Call a tool.

jq, grep, and sed are also used to parse or display outputs.

First, use the shell script to set the Ibexa DXP's base URL and user credentials as variables for easier reuse:

1
2
3
baseUrl='http://localhost' # Adapt to your test case
username='ibexa-example'
password='Ibexa-3xample'

Before you can communicate with the MCP server, you must first request a JWT token through the REST API:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
curl -s -X 'POST' \
  "$baseUrl/api/ibexa/v2/user/token/jwt" \
  -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\": \"$username\",
          \"password\": \"$password\"
        }
      }" > response.tmp.txt

cat response.tmp.txt | jq
jwtToken=$(cat response.tmp.txt | jq -r .JWT.token)
rm response.tmp.txt
1
2
3
4
5
6
7
{
  "JWT": {
    "_media-type": "application/vnd.ibexa.api.JWT+json",
    "_token": "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz1234567890ABCD.EFGHIJKL-MNOPQRSTUVWXYZ12345678901234567890",
    "token": "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz1234567890ABCD.EFGHIJKL-MNOPQRSTUVWXYZ12345678901234567890"
  }
}

Then, perform initialization to get an MCP session ID:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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" \
  -H "Authorization: Bearer $jwtToken" \
  -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "initialize",
        "params": {
          "protocolVersion": "2025-03-26",
          "capabilities": {},
          "clientInfo": {
            "name": "test-curl-client",
            "version": "1.0.0"
          }
        }
      }' > response.tmp.txt

sed '$d' response.tmp.txt
tail -n 1 response.tmp.txt | jq
mcpSessionId=$(cat response.tmp.txt | grep 'Mcp-Session-Id:' | sed 's/Mcp-Session-Id: \([0-9a-f-]*\).*/\1/')
rm response.tmp.txt
1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, Authorization, Accept
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Mcp-Session-Id
Cache-Control: no-cache, private
Content-Type: application/json
Date: Tue, 28 Apr 2026 09:53:27 GMT
Mcp-Session-Id: 12345678-9abc-def0-1234-56789abcdef0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "logging": {},
      "completions": {},
      "prompts": {
        "listChanged": true
      },
      "resources": {
        "listChanged": true
      },
      "tools": {
        "listChanged": true
      }
    },
    "serverInfo": {
      "name": "example",
      "version": "1.0.0",
      "description": "Example MCP Server"
    },
    "instructions": "Use this server to greet someone."
  }
}

Validate the initialization:

1
2
3
4
5
6
7
curl -s -i -X 'POST' "$baseUrl/mcp/example" \
  -H "Authorization: Bearer $jwtToken" \
  -H "Mcp-Session-Id: $mcpSessionId" \
  -d '{
        "jsonrpc": "2.0",
        "method": "notifications/initialized"
      }'
1
2
3
4
5
HTTP/1.1 202 Accepted
Access-Control-Allow-Headers: Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, Authorization, Accept
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Mcp-Session-Id

Get the list of tools:

1
2
3
4
5
6
7
8
curl -s -X 'POST' "$baseUrl/mcp/example" \
  -H "Authorization: Bearer $jwtToken" \
  -H "Mcp-Session-Id: $mcpSessionId" \
  -d '{
        "jsonrpc": "2.0",
        "id": 2,
        "method": "tools/list"
      }' | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "greet",
        "inputSchema": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string",
              "description": "The name of the person to greet"
            }
          },
          "required": [
            "name"
          ]
        },
        "description": "Greet a user by name",
        "annotations": {
          "readOnlyHint": true,
          "destructiveHint": false,
          "idempotentHint": true,
          "openWorldHint": false
        },
        "icons": [
          {
            "src": "https://openmoji.org/data/color/svg/1F44B.svg"
          }
        ],
        "outputSchema": {
          "type": "object",
          "properties": {
            "general": {
              "type": "string",
              "description": "the safe way to greet someone"
            },
            "close": {
              "type": "string",
              "description": "when you're close to the person, like friends or relatives"
            },
            "morning": {
              "type": "string",
              "description": "when it's in the morning"
            },
            "afternoon": {
              "type": "string",
              "description": "when it's the afternoon"
            },
            "evening": {
              "type": "string",
              "description": "when it's late in the day"
            }
          }
        }
      }
    ]
  }
}

Call the greet tool:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
curl -s -X 'POST' "$baseUrl/mcp/example" \
  -H "Authorization: Bearer $jwtToken" \
  -H "Mcp-Session-Id: $mcpSessionId" \
  -d '{
        "jsonrpc": "2.0",
        "id": 3,
        "method": "tools/call",
        "params": {
          "name": "greet",
          "arguments": {
            "name": "World"
          }
        }
      }' | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n    \"general\": \"Hello, World!\",\n    \"close\": \"Hey, World!\",\n    \"morning\": \"Good morning, World!\",\n    \"afternoon\": \"Good afternoon, World!\",\n    \"evening\": \"Good evening, World!\"\n}"
      }
    ],
    "isError": false,
    "structuredContent": {
      "general": "Hello, World!",
      "close": "Hey, World!",
      "morning": "Good morning, World!",
      "afternoon": "Good afternoon, World!",
      "evening": "Good evening, World!"
    }
  }
}

Get the list of prompts:

1
2
3
4
5
6
7
8
curl -s -X 'POST' "$baseUrl/mcp/example" \
  -H "Authorization: Bearer $jwtToken" \
  -H "Mcp-Session-Id: $mcpSessionId" \
  -d '{
        "jsonrpc": "2.0",
        "id": 4,
        "method": "prompts/list"
      }' | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "prompts": [
      {
        "name": "greet",
        "description": "Prompt to be greeted by the `greet` tool",
        "arguments": [
          {
            "name": "name",
            "description": "The name you want to be greeted by",
            "required": true
          }
        ],
        "icons": [
          {
            "src": "https://openmoji.org/data/color/svg/1F91D.svg"
          }
        ]
      }
    ]
  }
}

Get the prompt of the greet method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
curl -s -X 'POST' "$baseUrl/mcp/example" \
  -H "Authorization: Bearer $jwtToken" \
  -H "Mcp-Session-Id: $mcpSessionId" \
  -d '{
        "jsonrpc": "2.0",
        "id": 5,
        "method": "prompts/get",
        "params": {
          "name": "greet",
          "arguments": {
            "name": "Firstname Lastname"
          }
        }
      }' | jq
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "messages": [
      {
        "role": "user",
        "content": {
          "type": "text",
          "text": "Hi. My name is Firstname Lastname. Please, greet me."
        }
      }
    ]
  }
}

Perform MCP Inspector test

You can test your server with the MCP Inspector. You can even use the inspector as a DDEV add-on with craftpulse/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:

MCP server settings

In this example, the settings needed to use the MCP Inspector are as follows:

  • Transport Type: Streamable HTTP
  • URL: actual domain and server path, for example http://localhost/mcp/example
  • Connection Type: Via Proxy
  • Authentication:
    • Custom Headers:
      • Authorization
      • Bearer <JWT token>
    • OAuth 2.0 Flow: left unedited

Left panel of MCP Inspector with connection settings for MCP server

Test MCP server within MCP Inspector

In the right panel, in the Tools tab, click List Tools in the left column. The greet tool appears, preceded by its icon. You can select and test it in the right column.

Right panel of MCP Inspector with a list of tools obtained from MCP server, and the test of the greet tool

In the Prompts tab, in the left column, click List Prompts. The greet prompt appears, preceded by its icon. You can select and test it in the right column.

Right panel of MCP Inspector with a list of prompts obtained from the MCP server, and the test of the greet prompt

Perform Copilot CLI test

Add MCP server to Copilot CLI

For the sake of the Copilot CLI test in this example, you configure the MCP server in an .mcp.json file at the Ibexa DXP 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 coded variant

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "mcpServers": {
    "ibexa-example": {
      "type": "http",
      "url": "http://localhost/mcp/example",
      "headers": {
        "Authorization": "Bearer <JWT token>"
      },
      "tools": ["*"]
    }
  }
}

The .mcp.json file must be edited to update the JWT token each time it expires. 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:

  • 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.
Fully scripted variant

The wrapping script configuration in .mcp.json looks as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "mcpServers": {
    "ibexa-example": {
      "type": "stdio",
      "command": "bash",
      "args": ["mcp-ibexa-example-wrapper.sh"],
      "tools": ["*"]
    }
  }
}

mcp-ibexa-example-wrapper.sh is a script that requests a JWT token and establishes a connection with the MCP server.

For example, thanks to npx, you can do it with Supergateway without a local installation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
set -e

baseUrl='http://localhost' # Adapt to your test case

jwtToken=$(curl -s -X 'POST' \
  "$baseUrl/api/ibexa/v2/user/token/jwt" \
  -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": "ibexa-example",
          "password": "Ibexa-3xample"
        }
      }' | jq -r .JWT.token)

exec npx -y supergateway \
  --streamableHttp "$baseUrl/mcp/example" \
  --oauth2Bearer "$jwtToken" \
  --logLevel none

When Copilot complains that it can't communicate with the MCP server, 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.

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.

Run MCP server test with Copilot CLI

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

1
2
cd /path/to/project
copilot

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:

1
2
3
4
5
6
7
8
9
 MCP Server: ibexa-example

 Type:     http
 URL:      http://localhost/mcp/example
 Status:   ✓ Connected
 Source:   /path/to/project/.mcp.json

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

You can prompt Copilot 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.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
❯ Hi. Please, greet me.

◐ 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.

  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.

● Asked user What's your name?
  └ Pulley

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

● Good morning, Pulley! ☀️

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

◐ The user wants a close friend greeting. From the previous response, I can see the "close" greeting is "Hey, Pulley!"

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

● Hey, Pulley! 😄

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

◐ The user wants a general greeting for someone named "ElePHPant".

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

● Hello, ElePHPant! 🐘

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

◐ 22:30 is evening time, so I'll use the evening greeting.

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

● Good evening, ElePHPant! 🌙

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.