All posts

MCP servers in PHP: what the official SDK gives you, and when to skip it

AI tooling · 4 min read ·

The Model Context Protocol has an official PHP SDK, built with The PHP Foundation and the Symfony project, and it was on GitHub's weekly PHP trending list this week. If you write PHP for a living, that means you can now expose your own application to an AI assistant without leaving your language. Before you do, it helps to know what you are actually building: a door into your system that a model gets to knock on.

MCP in two sentences

MCP is an open protocol that lets an AI application discover and call tools (actions), read resources (data) and use prompts (templates) that a server exposes. The AI app is the host, your code is the server, and they talk in structured messages instead of the model guessing at your API from documentation.

AI app (the host) starts your script messages over STDIO or HTTP, for a remote server Your PHP MCP server built with mcp/sdk Tools Resources Prompts actions, data, templates
With STDIO the host runs your PHP script as a child process and talks to it through its input and output. Nothing listens on a port.

What the PHP SDK gives you

The SDK is framework-agnostic and covers both sides: servers and clients, tools, resources and prompts, STDIO and HTTP transports, sessions and authorization. It also speaks both eras of the protocol, the older handshake that starts with initialize and the stateless 2026-07-28 revision. You install it with composer require mcp/sdk, plus symfony/finder if you want it to discover your tools by scanning your code. The README's server example is short enough to read in one go:

class Calculator
{
    /** Adds two numbers. */
    #[McpTool]
    public function add(int $a, int $b): int { return $a + $b; }
}

exit(Server::builder()
    ->setServerInfo('Calculator', '1.0.0')
    ->setDiscovery(__DIR__, ['.'], excludeDirs: ['vendor'])
    ->build()
    ->run(new StdioTransport()));

An attribute marks a method as a tool, the doc comment becomes its description, and the typed parameters become its input. A resource works the same way with #[McpResource] and a URI. One line in the README matters more than the code, though: until the first major release, the SDK is considered experimental. Pin the version and expect changes.

Because it is framework-agnostic, it runs inside a Laravel app like any other Composer package. The README lists integrations built on it for Symfony, API Platform, Drupal and others. Check whether a Laravel integration exists before you write the glue yourself.

When a server is the right tool, and when it is not

An MCP server earns its place when it gives the model something it cannot get any other way: live data from your database, or an action in your system, like looking up an order's status or opening a support ticket. It is a weak fit for static documentation. Cristian Tabacitu, who maintains Backpack for Laravel, wrote this week that the MCP server they built for querying their docs gave mixed results, because the agent has to decide to call it and usually does not. If the agent already has the information, or can read it from your code, a server adds a moving part and little else.

Every tool argument is untrusted input

A tool is an action, and the arguments come from a model that read whatever was put in front of it. Treat them exactly like a form submitted by a stranger. This part is general practice, not something the SDK does for you:

trust boundary untrusted input your rules The model picks a tool Tool call with arguments Your validation types, ranges, user Your data least privilege Rejected, logged
Typed parameters check the shape of an argument. Only your own code can check whether this user may touch this record.

Validate every argument as you would a request body. Give each tool the smallest permission that does its job: a read-only database connection for a lookup tool, one table rather than the whole schema. Never expose a tool that runs any SQL or any shell command it is handed. Decide who the caller is and authorise per user, the same way your controllers do. And log every call, so that when a tool does something surprising you can see which arguments asked for it.

An int in the method signature tells you the argument is a number. It does not tell you the number belongs to the person asking.

If you want to try it, start with one read-only tool over data your assistant cannot see today, run it over STDIO on your own machine, and read every call in the log for a week before you add a tool that writes anything. Source: the official MCP PHP SDK on GitHub.

Notes from readers

  1. No notes yet. The first one is the bravest.
Building something like this? I can help. Message me on WhatsApp