Skip to main content

MCP server bridging Symfony/PHP projects with LLMs via Claude Desktop and other MCP clients

Project description

symfony-php-mcp

A production-ready Model Context Protocol (MCP) server that bridges your Symfony/PHP project with LLMs such as Claude. It exposes tools that let the AI read your project's routes, services, Twig templates, and PHP source code — without ever needing direct filesystem access from the LLM itself.

Claude Desktop / Claude Code
        │
        │  MCP (stdio)
        ▼
symfony-php-mcp  ──► reads ──► composer.json, symfony.lock, services.yaml, *.twig
                 ──► runs  ──► php bin/console debug:router / debug:container

Table of Contents


Features

Tool What it does PHP needed?
get_project_overview Reads composer.json + symfony.lock – PHP version, Symfony version, all packages No
find_route Runs debug:router --format=json, filters by URL pattern and HTTP method Yes
analyze_twig Finds a .twig file and extracts extends, include, block, macros No
list_services Reads config/services.yaml or runs debug:container --format=json Optional
read_code_context Reads a PHP file, strips doc-block comments to save tokens No

Quick Start

# Requires uv — https://docs.astral.sh/uv/
SYMFONY_PROJECT_ROOT=/path/to/your/symfony/app uvx symfony-php-mcp

The server speaks MCP over stdio and is designed to be launched by your MCP client (Claude Desktop, Claude Code, etc.), not run manually.


Installation

Via uvx (recommended)

uvx runs the package from the PyPI / GitHub registry with no permanent install:

// claude_desktop_config.json
{
  "mcpServers": {
    "symfony": {
      "command": "uvx",
      "args": ["symfony-php-mcp"],
      "env": {
        "SYMFONY_PROJECT_ROOT": "/path/to/your/symfony-app"
      }
    }
  }
}

Install from GitHub (before it's on PyPI):

{
  "mcpServers": {
    "symfony": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/maschmann/symfony-php-mcp", "symfony-mcp"],
      "env": {
        "SYMFONY_PROJECT_ROOT": "/path/to/your/symfony-app"
      }
    }
  }
}

Via local clone

git clone https://github.com/maschmann/symfony-php-mcp
cd symfony-php-mcp
uv sync
{
  "mcpServers": {
    "symfony": {
      "command": "uv",
      "args": ["run", "--project", "/path/to/symfony-php-mcp", "symfony-mcp"],
      "env": {
        "SYMFONY_PROJECT_ROOT": "/path/to/your/symfony-app"
      }
    }
  }
}

Configuration

Configuration is resolved in priority order: environment variables → .symfony-mcp.json → built-in defaults.

Environment Variables

Set these in your MCP client's env block.

Variable Default Description
SYMFONY_PROJECT_ROOT current working directory Required. Absolute path to your Symfony project (the directory containing composer.json).
PHP_EXECUTABLE php PHP binary or wrapper command. Use ddev php, lando php, or a full path like /usr/bin/php8.3.
DOCKER_CONTAINER (none) Docker container name. When set, commands run as docker exec <container> php …. Takes precedence over PHP_EXECUTABLE.
DOCKER_EXEC_USER (none) Optional -u <user> for docker exec. Useful when the container runs PHP as www-data.
CONSOLE_PATH bin/console Path to bin/console relative to SYMFONY_PROJECT_ROOT. Rarely needs changing.
COMMAND_TIMEOUT 30 Seconds before a PHP subprocess is killed. Increase for large projects or slow containers.

Project-local config file (.symfony-mcp.json)

Place this file in your Symfony project root to commit PHP runtime preferences alongside the project code. Great for teams using DDEV or Docker Compose.

{
  "php_executable": "php",
  "docker_container": null,
  "docker_exec_user": null,
  "console_path": "bin/console",
  "command_timeout": 30
}

Example for a DDEV project:

{
  "php_executable": "ddev php",
  "command_timeout": 60
}

Example for a Docker Compose project:

{
  "docker_container": "my-project-php-1",
  "docker_exec_user": "www-data",
  "command_timeout": 45
}

Configuration priority

Environment variables (MCP client env block)
        ↓  (override)
.symfony-mcp.json  (in SYMFONY_PROJECT_ROOT)
        ↓  (override)
Built-in defaults

Environment variables always win. This means you can commit a .symfony-mcp.json with sensible defaults for your team while still being able to override them per-machine via env vars.


Claude Desktop Setup

The claude_desktop_config.json file lives at:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

Local PHP

{
  "mcpServers": {
    "symfony": {
      "command": "uvx",
      "args": ["symfony-php-mcp"],
      "env": {
        "SYMFONY_PROJECT_ROOT": "/home/alice/projects/my-symfony-app"
      }
    }
  }
}

Docker

Works with any Docker Compose project. The container must be running when Claude Desktop starts (or before you use the tools).

{
  "mcpServers": {
    "symfony": {
      "command": "uvx",
      "args": ["symfony-php-mcp"],
      "env": {
        "SYMFONY_PROJECT_ROOT": "/home/alice/projects/my-symfony-app",
        "DOCKER_CONTAINER": "my-symfony-app-php-1",
        "DOCKER_EXEC_USER": "www-data"
      }
    }
  }
}

Finding your container name: run docker ps and look at the NAMES column.

DDEV

{
  "mcpServers": {
    "symfony": {
      "command": "uvx",
      "args": ["symfony-php-mcp"],
      "env": {
        "SYMFONY_PROJECT_ROOT": "/home/alice/projects/my-symfony-app",
        "PHP_EXECUTABLE": "ddev php"
      }
    }
  }
}

Alternatively, commit a .symfony-mcp.json to your project:

{
  "php_executable": "ddev php"
}

Then the MCP config only needs SYMFONY_PROJECT_ROOT.

Lando

{
  "mcpServers": {
    "symfony": {
      "command": "uvx",
      "args": ["symfony-php-mcp"],
      "env": {
        "SYMFONY_PROJECT_ROOT": "/home/alice/projects/my-symfony-app",
        "PHP_EXECUTABLE": "lando php"
      }
    }
  }
}

Sail

Laravel Sail is a thin Docker wrapper but the pattern works for Symfony projects using a similar setup:

{
  "env": {
    "SYMFONY_PROJECT_ROOT": "/home/alice/projects/my-project",
    "DOCKER_CONTAINER": "my-project-laravel.test-1"
  }
}

Tools Reference

get_project_overview

Returns a Markdown summary of the project. Call this first to give the LLM context before using other tools.

Parameters: none

Returns:

# Symfony Project: `acme/store`
> An e-commerce platform built with Symfony

## Runtime
| Key | Value |
|-----|-------|
| PHP requirement | `>=8.2` |
| Symfony version | `7.1.3` |
| APP_ENV | `dev` |

## Installed Packages
### Symfony Components
| Package | Version | Dev? |
...

find_route

Finds routes matching a URL pattern or route name.

Parameters:

Parameter Type Required Description
url_pattern string Yes Substring or regex to match against route paths and names. E.g. /api/user, user_show, ^/admin
method string No HTTP method filter: GET, POST, PUT, PATCH, DELETE. Empty = any.

Example:

Tool: find_route
url_pattern: /api/user
method: GET
Found **3** route(s) matching `/api/user`

| Route Name | Path | Methods | Controller |
|------------|------|---------|------------|
| `api_user_list` | `/api/users` | `GET` | `UserController::list` |
| `api_user_show` | `/api/users/{id}` | `GET` | `UserController::show` |
| `api_user_me`   | `/api/user/me`    | `GET` | `UserController::me`   |

Requires: PHP + bin/console


analyze_twig

Analyses a Twig template without running PHP.

Parameters:

Parameter Type Required Description
template_name string Yes Template name as used in Twig, or a partial name. E.g. user/show.html.twig, show, user/

Example:

Tool: analyze_twig
template_name: user/show.html.twig
## Template: `templates/user/show.html.twig`

### Inheritance
Extends: `base.html.twig`

### Included / Embedded Templates
| Type | Template |
|------|----------|
| include | `_partials/user_card.html.twig` |
| include | `_partials/breadcrumb.html.twig` |

### Defined Blocks
`title`, `content`, `scripts`

### Template Variables
`user`, `posts`, `pagination`

list_services

Lists service definitions from config/services.yaml or the compiled container.

Parameters:

Parameter Type Default Description
filter_pattern string "" Regex/substring to filter by service ID or class. E.g. App\\Service, mailer, doctrine
use_container_debug bool false Use debug:container --format=json instead of YAML. Required to see auto-wired / framework services.

Example – find all mailer services:

Tool: list_services
filter_pattern: mailer
use_container_debug: true

read_code_context

Reads a PHP file with optional comment stripping to reduce token usage.

Parameters:

Parameter Type Default Description
file_path string Path relative to project root. E.g. src/Controller/UserController.php
strip_doc_comments bool true Strip /** … */ and /* … */ blocks. Saves ~20-40% tokens on typical controllers.
strip_line_comments bool false Also strip // comments. Use when you need maximum token efficiency.

Example:

Tool: read_code_context
file_path: src/Controller/UserController.php
## `src/Controller/UserController.php`

| Property | Value |
|----------|-------|
| Original  | 187 lines / 6,842 chars |
| After processing | 134 lines / 4,201 chars |
| Token savings | ~39% (block comments stripped) |

```php
  1 | <?php
  2 |
  3 | namespace App\Controller;
...

Docker / Container Environments (in depth)

How command routing works

When DOCKER_CONTAINER is set, every PHP/console invocation becomes:

docker exec [-u <DOCKER_EXEC_USER>] <DOCKER_CONTAINER> php bin/console <args>

When PHP_EXECUTABLE is set to ddev php:

ddev php bin/console <args>

The server never modifies the project files inside the container.

Docker Compose tips

  1. Container name – use docker ps to find the exact name. For docker compose, it's usually <project>-<service>-1.

  2. File pathsSYMFONY_PROJECT_ROOT should point to the host path since the server reads files directly via the Python filesystem layer. Only debug:router / debug:container commands run inside the container.

  3. Working directory – commands are run in SYMFONY_PROJECT_ROOT on the host. If your container mounts the project at a different path, set CONSOLE_PATH accordingly — or ensure bin/console is accessible from the host path.

  4. Container not running – the server will return a helpful error. Start your containers first: docker compose up -d.

DDEV tips

# List DDEV projects
ddev list

# Ensure the project is running
ddev start

# Test PHP is accessible
ddev php --version

.symfony-mcp.json for DDEV:

{
  "php_executable": "ddev php",
  "command_timeout": 60
}

Lando tips

# Ensure the project is running
lando start

# Test PHP is accessible
lando php --version

.symfony-mcp.json for Lando:

{
  "php_executable": "lando php"
}

Development

# Clone
git clone https://github.com/maschmann/symfony-php-mcp
cd symfony-php-mcp

# Install dependencies (requires uv — https://docs.astral.sh/uv/)
uv sync

# Run the server (will block waiting for MCP stdio input)
uv run symfony-mcp

# Run tests
uv run pytest

# Lint
uv run ruff check src/
uv run ruff format --check src/

Project structure

src/symfony_mcp/
├── server.py        # FastMCP server, lifespan, tool decorators, main()
├── config.py        # ServerConfig: env vars + .symfony-mcp.json merge logic
├── executor.py      # PhpExecutor: subprocess wrapper with Docker/wrapper support
└── tools/
    ├── project.py   # get_project_overview
    ├── router.py    # find_route
    ├── twig.py      # analyze_twig
    ├── services.py  # list_services
    └── code.py      # read_code_context

Adding a new tool

  1. Create src/symfony_mcp/tools/my_tool.py with a plain function.
  2. Register it in server.py with @mcp.tool().
  3. Add a docstring – FastMCP uses it as the tool description.

Troubleshooting

"Binary not found: 'php'"

PHP is not in the PATH used by the MCP server process.

Fix options:

  • Install PHP: apt install php-cli / brew install php
  • Use a full path: PHP_EXECUTABLE=/usr/bin/php8.3
  • Use Docker: DOCKER_CONTAINER=my-php-container
  • Use DDEV: PHP_EXECUTABLE=ddev php

"Symfony console not found"

SYMFONY_PROJECT_ROOT is pointing to the wrong directory, or bin/console is missing.

Fix: Make sure SYMFONY_PROJECT_ROOT is the directory that contains both composer.json and bin/console.

ls /your/project/bin/console   # should exist

"Command timed out"

The PHP command took longer than COMMAND_TIMEOUT seconds.

Fix: Increase the timeout:

// .symfony-mcp.json
{ "command_timeout": 120 }

Or set COMMAND_TIMEOUT=120 in the MCP env block.

"Cannot inspect container"

The Docker container is not running.

Fix:

docker compose up -d           # Docker Compose
ddev start                     # DDEV
lando start                    # Lando

Claude Desktop doesn't see the server

  1. Restart Claude Desktop after editing claude_desktop_config.json.
  2. Check the MCP server logs in Claude Desktop → Settings → Developer → MCP Servers.
  3. Run the server manually to check for errors:
    SYMFONY_PROJECT_ROOT=/path/to/project uvx symfony-php-mcp
    
    It should start silently (waiting for stdio input). Any error on startup will print to stderr.

"Error parsing config/services.yaml"

Your services.yaml has a syntax error or uses YAML features not supported by PyYAML.

Fix: Use use_container_debug=true in list_services as a fallback:

Tool: list_services
filter_pattern: App\
use_container_debug: true

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

symfony_php_mcp-0.1.0.tar.gz (23.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

symfony_php_mcp-0.1.0-py3-none-any.whl (29.3 kB view details)

Uploaded Python 3

File details

Details for the file symfony_php_mcp-0.1.0.tar.gz.

File metadata

  • Download URL: symfony_php_mcp-0.1.0.tar.gz
  • Upload date:
  • Size: 23.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.0 {"installer":{"name":"uv","version":"0.11.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for symfony_php_mcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 78308e529851280ece3a17cdbfd1db1513b93da1894d67b4401fb09b6aded5a6
MD5 b5a29801cf6509143d3765b2884c33e2
BLAKE2b-256 6671370ed617d97807a4f258c47df078215e38b8e58c9dd3edd8802002c79d16

See more details on using hashes here.

File details

Details for the file symfony_php_mcp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: symfony_php_mcp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 29.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.0 {"installer":{"name":"uv","version":"0.11.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for symfony_php_mcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b54080d7d8a75d692097cbb5a442455ef217cec26f48e655db4c51c68713791c
MD5 b5f8264ebd48284ae9a9f4898fb73855
BLAKE2b-256 710275864ec86758612fa7ca56f3ec165056ab10a0f81e7569f98fc9b4502d79

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page