DEV Community

Cover image for Deprecating My Symfony Profiler MCP Bundle: Right About the Problem, Half-Wrong About the Solution
Hamdi LAADHARI
Hamdi LAADHARI

Posted on

Deprecating My Symfony Profiler MCP Bundle: Right About the Problem, Half-Wrong About the Solution

In March 2025 I built mcp-profiler-bundle, a Symfony bundle that let AI coding agents read the Symfony Profiler: the requests your app just served, the queries they ran, the exceptions they threw. The bet was simple. An agent that can see what the app actually did should debug it better than one that can only read the source.

This week I deprecated it. Symfony now ships the official version of that idea, and rereading my code for the deprecation notice left me with an uncomfortable verdict: I was right about the problem, and half-wrong about the solution.

What the bundle did

MCP, the Model Context Protocol, is a JSON-RPC protocol that lets a client like Claude Desktop, Cursor or Cline call tools on a local server. My bundle added a bin/console mcp:server:run command that spoke it over stdio and exposed four tools: profiler:list, profiler:get_by_token, profiler:get_collectors and profiler:get_collector.

There was no official PHP SDK back then. My first commit leaned on a community library, james2037/mcp-php-server, which only existed as dev-master. By the 0.1.0 tag on 31 March 2025 it was gone, and the whole protocol fit in one match:

// Command/RunMCPServerCommand.php
$payload = json_decode($line, true, JSON_THROW_ON_ERROR);
$method = $payload['method'] ?? null;

$response = match ($method) {
    'initialize' => $this->sendInitialize(),
    'tools/list' => $this->sendToolsList(),
    'tools/call' => $this->callTool($payload['params'] ?? []),
    'notifications/initialized' => null,
    default => $this->sendProtocolError(\sprintf('Method "%s" not found', $method)),
};
Enter fullscreen mode Exit fullscreen mode

Hold on to that first line. It comes back later.

It found some users: 11 stars, 2 forks and roughly 5,900 Packagist installs. Version 0.2.0 added multi-kernel (APP_ID) support a week after launch, and in February 2026 an outside contributor, Rhodri Pugh, sent a PHP 8.4 fix.

Right about the problem

The idea is official now. In September 2025, Symfony announced the official PHP MCP SDK, built with The PHP Foundation and Anthropic's MCP team. On 23 December 2025, Symfony AI Mate shipped its first release, about nine months after my 0.1.0: an MCP dev server giving coding agents access to a Symfony app's internals. The container came first, profiler access landed in 0.3 in January 2026, and it handles multi-kernel apps too.

Symfony's spotlight on Mate opens on the same premise: an agent can read all your code and still not know what happened on the last request. Mate is past 100,000 installs a month.

Half-wrong about the solution

In August 2026, Mate 0.13 deleted its MCP server (PR #2380). It no longer depends on mcp/sdk, and the agent runs plain commands instead, like these from Mate's docs and its profiler skill:

vendor/bin/mate tools:list
vendor/bin/mate tools:call symfony-profiler-list --statusCode=500 --limit=5
vendor/bin/mate resources:read symfony-profiler://profile/<token>
Enter fullscreen mode Exit fullscreen mode

A bare CLI has a problem an MCP server doesn't: nothing tells the agent it exists. Mate's answer is Agent Skills β€” SKILL.md files installed into .agents/skills/ (mirrored into .claude/skills/) that say when to reach for which tool. The profiler skill reads like a triage procedure: filter for the failing request instead of scrolling, check which collectors it has, then read them in symptom order. exception first for a 5xx; time, db, then memory for a slow request.

Johannes Wachter, who made that change, then measured it: Claude Haiku, two tasks with injected performance bugs, ten runs per setup, counting whether the agent touched Mate at all. The bare CLI: 0 runs out of 20. The MCP server: 5 out of 20. The CLI with Skills and project instructions: 10 out of 10 on one task, 5 out of 10 on the harder one. He calls it a pattern from a small sample, not a proven effect, and concludes that "transport alone wasn't the deciding variable. Visibility was."

That's the half I got wrong. I treated exposing the data as the product. Tools give the agent eyes; skills tell it where to look. My bundle handed over four tools with one-line descriptions ("Lists available profiler tokens.") and left the agent to work out the rest.

Before / after

mcp-profiler-bundle (2025) Symfony AI Mate 0.13 (2026)
How the agent calls it MCP over stdio, hand-written JSON-RPC plain CLI, no server process
Needs the app to boot yes, it's a bin/console command no, reads the profiler and the dumped container from disk
Finding a profile the last N (limit) method, URL, IP, status, date range, kernel context
Collector output raw collector data or VarDumper dumps formatters for Doctrine, exceptions, logs, mail, memory, requests, timing and translations
Secrets no redaction cookies, session data, auth headers and secret env vars redacted
Prompt injection nothing output wrapped and marked as untrusted data
Telling the agent what to do four one-line tool descriptions Agent Skills plus AGENTS.md / CLAUDE.md
Beyond the profiler nothing service container, Monolog logs

The catch: my own bundle

Rereading the code was humbling. PHPStan flags the first two of these problems in CI, on a step I'd marked continue-on-error: true the day I set up CI:

 ------ -----------------------------------------------------------------------
  Line   Command/RunMCPServerCommand.php
 ------ -----------------------------------------------------------------------
  40     While loop condition is always true.
         πŸͺͺ  while.alwaysTrue
  61     Unreachable statement - code above always terminates.
         πŸͺͺ  deadCode.unreachable
  68     Constant JSON_THROW_ON_ERROR is not allowed for parameter #3 $depth
         of function json_decode.
         πŸͺͺ  argument.invalidConstant
Enter fullscreen mode Exit fullscreen mode
  • It never exits. The MCP spec expects a stdio server to quit when the client closes its input. Mine can't: on EOF, fgets() returns false, and the loop sleeps a millisecond and tries again, forever.
  • It gets JSON-RPC wrong. JSON_THROW_ON_ERROR sits in json_decode()'s $depth parameter, not $flags, so bad input decodes to null. Instead of a -32700 parse error with a null id, the client gets -32601 Method "" not found with id 0. And any other notification, like notifications/cancelled, gets an error reply, which JSON-RPC 2.0 forbids.
  • The tool names don't travel. They all contain a colon, which MCP's 2025-11-25 spec says to avoid. The Cursor screenshot in my own README shows them as profilerlist and profilerget_by_token, and the README documented different names altogether.
  • The filters got lost. My first version exposed ip, url, method and status_code. The rewrite kept them in ProfilerList::execute() but put only limit in the tool schema.
  • Nothing was redacted. The request collector holds cookies, headers and the server bag, and since Symfony's Dotenv copies .env values into $_SERVER, that includes APP_SECRET and DATABASE_URL. profiler:get_collector dumped whatever the agent asked for, unfiltered, and the optional bin/run-mcp.sh wrapper (adapted from JoliCode's MCP article) appended every response to var/mcp/stdout.log.
  • It needed a booting app. If the container didn't compile, bin/console didn't start, and neither did the server. Mate reads everything from disk, so it still answers when the app won't boot.
  • The test suite is one test. It checks that the bundle class is a bundle.

The protocol has moved on, too. MCP's 2026-07-28 revision removed the initialize handshake my match is built around and made a new server/discover method mandatory.

None of this is a verdict against MCP, by the way. symfony/mcp-bundle, for building your own MCP servers, does over 600,000 installs a month. The shift is narrower: for a dev tool the agent runs on your machine, a CLI plus skills got picked up at least as reliably, without a server process to babysit.

Wrapping up

So I retired it properly. PR #14 puts a deprecation notice at the top of the README, with the migration:

composer require --dev symfony/ai-mate
vendor/bin/mate init
composer require --dev symfony/ai-symfony-mate-extension symfony/ai-monolog-mate-extension
vendor/bin/mate discover
Enter fullscreen mode Exit fullscreen mode

mate discover also installs the skills. For frontend work, Simon AndrΓ©'s Symfony UX skills cover Stimulus, Turbo, Twig Components and Live Components.

Even deprecating it took a fix. CI had been red since June, failing on the PHP CS Fixer step, and the log on the deprecation PR showed the culprit: with no config file, PHP CS Fixer 3.95 opens an interactive init wizard, which crashes in CI with Failed to read template file. A .php-cs-fixer.dist.php turned it green.

0.2.1 is the final release, Rhodri's fix included, and the repository stays public. The lesson I'm keeping: access was the easy half. The half that makes it useful is deciding what the agent should look at, in what order, and what it must never see.

References

Top comments (0)