Skip to content

Build an MCP Server

Edit this page

The Model Context Protocol (MCP) lets AI assistants like Claude Desktop discover and call tools exposed by your application. In this guide you will install the MCP Bundle, create a tool, a prompt, and a resource, then test them with a client.

Prerequisites

  • A Symfony application
  • Symfony AI MCP Bundle

Step 1: Install the MCP Bundle

The MCP Bundle integrates the official mcp/sdk PHP package into your Symfony application:

1
composer require symfony/mcp-bundle

Step 2: Configure Routing

Add the MCP route to your routing configuration. This exposes the HTTP endpoint that MCP clients connect to:

1
2
3
4
# config/routes.yaml
mcp:
    resource: .
    type: mcp

Step 3: Create a Tool

Use the #[McpTool] attribute on a method to expose it as a callable tool. MCP clients see the tool name and can invoke it with parameters:

1
2
3
4
5
6
7
8
9
10
11
12
namespace App\Mcp;

use Mcp\Capability\Attribute\McpTool;

class CurrentTimeTool
{
    #[McpTool(name: 'current-time')]
    public function getCurrentTime(string $format = 'Y-m-d H:i:s'): string
    {
        return (new \DateTime('now', new \DateTimeZone('UTC')))->format($format);
    }
}

Tip

You can place multiple #[McpTool] methods in the same class to group related tools together.

Step 4: Configure Transport

Declare your server and the transports it offers. The STDIO transport is used by command-line clients; HTTP is used by web-based clients and the MCP Inspector. A server exposes only what its capability lists name, so ['*'] hands it everything you annotate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# config/packages/mcp.yaml
mcp:
    servers:
        default:
            name: 'my-app'
            version: '1.0.0'
            description: 'My Symfony MCP server'

            transports:
                stdio: true
                http: true

            http:
                path: /_mcp

            registry: '*'

Step 5: Create a Prompt

Prompts provide system instructions that AI clients can request. Use the #[McpPrompt] attribute and return an array of messages:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace App\Mcp;

use Mcp\Capability\Attribute\McpPrompt;

class TimePrompts
{
    #[McpPrompt(name: 'time-analysis')]
    public function getTimeAnalysisPrompt(): array
    {
        return [
            ['role' => 'user', 'content' => 'You are a time management expert.'],
        ];
    }
}

Step 6: Create a Resource

Resources expose static data that AI clients can read. Use the #[McpResource] attribute with a URI and a name:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace App\Mcp;

use Mcp\Capability\Attribute\McpResource;

class TimeResource
{
    #[McpResource(uri: 'time://current', name: 'current-time')]
    public function getCurrentTimeResource(): array
    {
        return [
            'uri' => 'time://current',
            'mimeType' => 'text/plain',
            'text' => (new \DateTime('now'))->format('Y-m-d H:i:s'),
        ];
    }
}

Step 7: Test with an MCP Client

To connect Claude Desktop (or any MCP-compatible client) to your server, add an entry to the client's configuration file pointing at your Symfony console command for STDIO transport:

1
2
3
4
5
6
7
{
    "mcpServers": {
        "my-app": {
            "command": "php /path/to/project/bin/console mcp:server"
        }
    }
}

For the HTTP transport, point the client at your application's MCP endpoint instead:

1
2
3
4
5
6
7
{
    "mcpServers": {
        "my-app": {
            "url": "http://localhost:8000/_mcp"
        }
    }
}

Tip

Run symfony console mcp:server to start the STDIO server manually. This is useful for debugging before connecting a client. With more than one server configured for STDIO, name the one to run: symfony console mcp:server default.

Learn More

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version