DEV Community

Cover image for How to create an MCP server with Symfony
Edouard Courty
Edouard Courty

Posted on

How to create an MCP server with Symfony

Model Context Protocol (MCP) is the new big thing in the AI revolution.

Large Language Models (LLMs) are powerful, but they do not have access to the outside world: they cannot navigate on the Internet, browse their host machine’s file system, access an external database…

However, these LLMs can be enhanced with capacities, which are called tools.

These tools are not built into the LLM. They’re provided by you, the developer.
The way it works is the Agent (An app using an LLM to function) is given, in addition to the prompt, a list of external tools which can be used to fulfill the user’s need.

The LLM then decides if it’s able to fulfill the request on its own (like craft a piece of text) or if it needs to use a tool that matches its needs in the provided list.

The Model Context Protocol defines a way of communicating between what could be called “agents” (Clients) and external apps (Servers).

The official MCP specification states MCP servers should handle HTTP communication using JSON-RPC.
These 3 methods should be handled by any server:

  • initialize — Allows the version and capabilities exchange between the client and server,
  • tools/list — Returns the server’s available tools, each containing a title, description, and input format,
  • tools/call — Executes a tool given its name and arguments.

The MCP Server Bundle

Creating an MCP server with Symfony just became very, very easy.

The MCP Server Bundle is a plug-and-play Symfony bundle that does all the heavy-lifting for you. It handles the transport and presentation layer, allowing you to focus solely on building useful tools.

It handles the JSON-RPC methods the MCP specification provides (initialization, tool listing and tool calling), using the tools you built within your project.

🧰 Getting started

To start building an MCP server, install the MCP Server Bundle in your Symfony project.

composer require ecourty/mcp-server-bundle
Enter fullscreen mode Exit fullscreen mode

After this, if not using Symfony Flex, add the bundle to your bundles.php file.

<?php

declare(strict_types=1);

return [
    ...
    Ecourty\McpServerBundle\McpServerBundle::class => ['all' => true],
];
Enter fullscreen mode Exit fullscreen mode

Set up the routing by creating a mcp.yaml file to your config/routes directory like so:

mcp_controller:
  path: /mcp
  controller: mcp_server.entrypoint_controller
Enter fullscreen mode Exit fullscreen mode

🛠️ Le’s build a tool!

Building your first tool is pretty straightforward.

The bundle provides an AsTool PHP attribute, which you can add to a class to make it available as a tool within the MCP server.
The attribute is used to define the tool name, description, and some optional flags called annotations.

This attribute will make the class become a registered tool, given the following conditions are met:

  • The class has the AsTool attribute
  • The class has an __invoke method which returns an instance of ToolResult
  • The __invoke method should have only one parameter, typed with a class which public properties contain OpenAPI attributes and optionally assertions (which will be enforced)

Let’s build a tool that retrieves the content of a URL!
Let’s build the input schema first:

<?php

namespace App\Schema;


use OpenApi\Attributes as OA;
use Symfony\Component\Validator\Constraints as Assert;

class RetrieveURLContent
{
    #[Assert\Url]
    #[OA\Property(type: 'string', format: 'uri', nullable: false)]
    public string $url;
}
Enter fullscreen mode Exit fullscreen mode

Now, let’s build the actual tool!

<?php

namespace App\Tool;

use App\Schema\RetrieveURLContent;
use Ecourty\McpServerBundle\Attribute\AsTool;
use Ecourty\McpServerBundle\Attribute\ToolAnnotations;
use Ecourty\McpServerBundle\IO\TextToolResult;
use Ecourty\McpServerBundle\IO\ToolResult;

#[AsTool(
    name: 'get_url_content',
    description: 'Retrieves the content of a URL',
    annotations: new ToolAnnotations( // Optional, provides more information about the tool's behavior
        title: 'Retrieve URL Content',
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: false,
        openWorldHint: true,
    )
)]
class RetrieveURLContentTool
{
    public function __invoke(RetrieveURLContent $payload): ToolResult
    {
        $url = $payload->url;
        $content = file_get_contents($url);

        if ($content === false) {
            return new ToolResult(
                elements: [
                    new TextToolResult('The URL did not return anything')
                ],
                isError: true,
            );
        }

        return new ToolResult([
            new TextToolResult($content),
        ]);
    }
}

Enter fullscreen mode Exit fullscreen mode

You’re done! 🥳

Your tool should be registered in your Symfony app and be available for any MCP client.

📡 Testing your MCP Server

You can now use the debug command to see if your tool was created properly:

$ bin/console debug:mcp-tools

MCP Tools Debug Information
===========================

------------------ --------------------------------- -----------------------------------
 Name               Description                       Input Schema                                           
------------------ --------------------------------- -----------------------------------
 get_url_content    Retrieves the content of a URL    App\Schema\RetrieveURLContent     
Enter fullscreen mode Exit fullscreen mode

Perfect, now your tool is ready to be used by MCP clients!

Try to call http://localhost:<port>/mcp with the tools/list JSON-RPC method, and you should receive a JSON response listing your available tools.

POST http://localhost:8080/mcp
Request
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list",
    "params": {}
}

Response
{
    "jsonrpc": "2.0",
    "id": "list",
    "result": {
        "tools": [
            {
                "name": "get_url_content",
                "description": "Retrieves the content of a URL",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "url": {
                            "type": "string",
                            "format": "uri",
                            "nullable": false
                        }
                    }
                },
                "annotations": {
                    "title": "Retrieve URL Content",
                    "readOnlyHint": true,
                    "destructiveHint": false,
                    "idempotentHint": false,
                    "openWorldHint": true
                }
            }
        ]
    },
    "error": null
}
Enter fullscreen mode Exit fullscreen mode

🚀 Going Further

The MCP Server Bundle provides cool features to help you build MCP servers, such as the ability to listen to events, write custom JSON-RPC method handing logic, or create custom tool results.

If you want to know more about how this works under the hood, feel free to visit the repo, and why not make a contribution if you want to participate in the project!

Using MCP servers in practice

Alright. You’ve built your server, you created a few tools, but what next?

You need to give access to your MCP server to your client.
Check the documentation for:

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.