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
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],
];
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
🛠️ 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;
}
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),
]);
}
}
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
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
}
🚀 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.