vonage
PHP + MCP · PHPTek 2026

PHPTek 2026

PHP + MCP

Building MCP Servers: Extend AI Applications with PHP

Chris Tankersley · Vonage

Who Am I?

Chris Tankeresley

Developer Relations Tooling Manager

We build stuff for developers

Our Northern Star

Be Where The Developers Are

  • Support the languages our customers use
  • Develop the tools they want to use
  • Be in their communities
  • Be in their editor

Our Northern Star

AI Isn't Going

  • Don't think of it as a human replacement
  • Using AI as an enabler and magnifier
  • It's a tool

The framing problem

Every MCP tutorial starts the same way.


$ pip install mcp
$ npx create-mcp-server
      

If you're a PHP dev, the implicit message has been: "rebuild your stack to participate."

That's a false choice. And it's been false for almost a year.

The stakes

If AI integration requires a Python rewrite, the integration doesn't happen.

The way it feels

AI is a Python/Node thing. We weren't invited. The legacy stays legacy.

The way it actually is

PHP runs WordPress, Magento, Drupal, every Laravel SaaS, every internal admin nobody talks about. MCP changes the equation in both directions.

What you'll leave with

The shape of the next 55 minutes.

  • A mental model for what MCP is and isn't
  • A map of the PHP MCP library landscape — no "right answer" agenda
  • A walkthrough of a minimal server you can copy from
  • A walkthrough of a PHP MCP client — so your app can be the agent
  • Production patterns: auth, transport, security, deployment
  • A short list of resources to start tonight

Section 2 · 7 min

What MCP
actually is.

The elevator pitch

MCP is USB-C for AI.

An open standard for connecting AI applications to your data and tools. Released by Anthropic in November 2024, now governed under LF Projects.

One socket. Many devices: Claude, Cursor, Codex, Cline, agent frameworks.

The shape of the protocol

JSON-RPC 2.0 over a transport. That's it.

┌────────────────┐ ┌────────────┐ ┌──────────────┐ │ HOST │ │ CLIENT │ │ SERVER │ │ Claude/Cursor │ ←──→ │ one per │ ←──→ │ what YOU │ │ or your PHP │ │ server │ │ write │ │ agent app │ │ │ │ │ └────────────────┘ └────────────┘ └──────────────┘

The protocol is symmetric — same JSON, both directions. You'll build both sides today.

The three primitives

Tools do. Resources are. Prompts guide.

Tools

Actions the model can invoke. "Refund this order." Model-controlled.

Resources

Read-only data. Files, configs, query results. URI-addressed. App-controlled.

Prompts

Reusable templates the user can pick from the host UI.

Server-initiated communication

The server can also call back.

  • Sampling — ask the host to run an LLM completion on the server's behalf
  • Elicitation — ask the user for input mid-flow (new in 2025-11-25)
  • Progress notifications — stream "37% done" during long tools
  • Logging — push structured logs to the client

This is what makes MCP feel agentic instead of REST-with-extra-steps.

Transports — only two matter

stdio

Server runs as a subprocess. Client speaks over stdin/stdout. Local CLIs, dev loops, single-user tools.

Streamable HTTP

One endpoint, JSON-RPC over POST, optional SSE upgrade. Production. Multi-user. Remote.

Legacy SSE-only is deprecated. The 2026 roadmap deliberately keeps this surface small.

Section 3 · 4 min

Why this
matters to a
PHP dev.

The integration treadmill

Before MCP: build the same Slack integration five times.

Each AI assistant that wants to talk to your app needs a bespoke integration. A Claude plugin. Then a Cursor command. Then a Copilot extension. Then…

After MCP: write one server. Every current and future MCP client speaks to it.

Same shift OpenAPI brought to HTTP APIs — but for AI clients.

Your code is the moat

Your authorization layer is your safety net.

Business logic, validations, RBAC, audit trails — all of that already lives in your PHP app. MCP lets you expose it without rewriting it.

Your Eloquent models, your Symfony services, your WordPress hooks become AI-callable as-is.

And the part that drives the LLM? That can also be PHP.

A specific date worth remembering

September 5, 2025.

MCP officially blessed PHP as a first-class language. SDK maintained by Anthropic's MCP team, the PHP Foundation, and Symfony.

Started from the community php-mcp project by Kyrian Obikwelu — now an official maintainer.

10th SDK Python · TypeScript · Java · Kotlin · C# · Swift · Ruby · Rust · Go · PHP.

Section 4 · 7 min

The PHP MCP
landscape.

There is no single "right" library

Pick by where your code already lives.

Most of these support both server and client. The protocol is the same; switching libraries later is a port, not a rewrite.

Code patterns are similar across the field — attribute-driven, builder-pattern setup.

The official SDK

mcp/sdk

composer require mcp/sdk
  • Framework-agnostic. Symfony team + PHP Foundation maintain it.
  • Server and client in one package.
  • Currently v0.5.0 — pre-1.0, marked experimental. Symfony BC promise once 1.0 lands.
  • Other libraries (Laravel, CakePHP plugins) increasingly build on top of this.

Use when: you don't have a strong framework opinion, or you want to be closest to upstream.

Laravel-first

laravel/mcp

composer require laravel/mcp
  • Official Laravel package. Same team as the framework.
  • Fluent registration: Mcp::local('server')->tool(MyTool::class).
  • First-class OAuth 2.1 + Sanctum.
  • Built-in MCP Inspector route, testing helpers, middleware integration.

Use when: you're in Laravel. No reason to fight your framework.

Symfony-first

symfony/mcp-bundle

composer require symfony/mcp-bundle
  • Wraps the official SDK with Symfony service container integration.
  • Tools / resources / prompts register as services with attributes.
  • HTTP and stdio transports out of the box.
  • Pairs with symfony/ai for the client / agent side.

Use when: you're in Symfony.

Pure PHP, shared hosting friendly

logiscape/mcp-sdk-php

  • Built specifically for shared hosting — cPanel, Apache, the hosting most of the PHP world actually uses.
  • Server and client in one package.
  • Ships a web-based MCP client for testing on the same hosting.
  • OAuth 2.1 included.

Use when: you're not on a VPS — you're on shared hosting.

A few more worth knowing

LibraryNotes
pronskiy/mcpThin developer-experience layer on top of mcp/sdk.
php-mcp/server + php-mcp/clientOriginal community projects. Official SDK is the successor for new work.
swisnl/mcp-clientClient-only. Modern PHP 8.2+. Lightweight.
josbeir/cakephp-synapseCakePHP plugin.
dtyq/php-mcpFull server + client, multiple transports.

Decision tree

Picking one.

In Laravel? → laravel/mcp In Symfony? → symfony/mcp-bundle ( + symfony/ai ) On shared hosting? → logiscape/mcp-sdk-php Client-only PHP agent? → mcp/sdk or swisnl/mcp-client Anything else / from scratch → mcp/sdk (official)

The protocol is the same. Switching libraries later is a port, not a rewrite.

Section 5 · 9 min

Anatomy of a
minimal server.

Goal

One tool, one resource, stdio, Claude Desktop.

┌────────────────┐ stdio ┌──────────────┐ fn call ┌──────────────┐ │ Claude Desktop │ ────────►│ my PHP script│ ──────────► │ my domain │ │ │ │ │ │ code │ └────────────────┘ ◄────────└──────────────┘ ◄────────── └──────────────┘

Step 1 — Composer


mkdir my-mcp-server && cd my-mcp-server
composer init --no-interaction
composer require mcp/sdk
      

Step 2 — Define capabilities


<?php
use Mcp\Capability\Attribute\McpTool;
use Mcp\Capability\Attribute\McpResource;

class CalculatorCapabilities
{
    #[McpTool]
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }

    #[McpResource(uri: 'config://calculator/settings')]
    public function getSettings(): array
    {
        return ['precision' => 2];
    }
}
      

Method signature is the JSON Schema. Doc-comments and param names are model-readable. Treat them like product copy.

Step 3 — Wire it up


use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;

$server = Server::builder()
    ->setServerInfo('Calculator Server', '1.0.0')
    ->setDiscovery(__DIR__, ['.'])
    ->build();

$server->run(new StdioTransport());
      

setDiscovery scans the path for attribute-tagged methods. No manual registration. That's the whole server. ~25 lines including imports.

Step 4 — Connect Claude Desktop


{
  "mcpServers": {
    "calculator": {
      "command": "php",
      "args": ["/full/path/to/server.php"]
    }
  }
}
      

Drop into claude_desktop_config.json. Restart Claude. Same shape for Cursor, Cline, and most other clients.

Step 5 — Watch it work

"What's 47 plus 119?"

Claude calls your PHP function. Returns 166. Done.

It's the dumbest possible demo on purpose. Everything else in this talk is variations on this pattern.

What's happening on the wire


→ {"jsonrpc":"2.0","id":1,"method":"initialize",
    "params":{"protocolVersion":"2025-11-25", "...":"..."}}
← {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"tools":{}, "...":"..."}}}

→ {"jsonrpc":"2.0","id":2,"method":"tools/list"}
← {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"add","inputSchema":{...}}]}}

→ {"jsonrpc":"2.0","id":3,"method":"tools/call",
    "params":{"name":"add","arguments":{"a":47,"b":119}}}
← {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"166"}]}}
      

Plain text. Log it during development — it's the best debugger you have.

Section 6 · 6 min

Real-world
server use cases.

Use case

MySQL query bridge.

Expose a query(string $sql) tool that runs against a read-only DB user.

Why it works: database privileges enforce safety. The model can only do what the DB user can.

Non-technical staff can ask "how many signups did we get last week from California?" — the AI writes and runs the SQL.

Use case

Laravel / Symfony app exposure.

Wrap repository methods or Eloquent queries: findCustomer($email), recentOrders($id).

Why it works: existing policies still gate access. Sanctum / Symfony firewall tokens identify the acting user.

Support staff get an AI assistant that knows their data without bypassing authorization.

Use case

E-commerce: orders, inventory, refunds.

Read tools (inventory, order lookup) + write tools that require elicitation before executing.

Why it works: elicitation is the human-in-the-loop primitive. The server asks the user before refunding.

Real agentic workflows without trusting the model on financial transactions.

Use case

The crusty internal admin panel.

Wrap its repository methods as MCP tools. Don't touch the UI.

Why it works: you expose the intent of operations, not the raw data layer. The model talks to your domain language.

Years of business logic become AI-callable without a frontend rewrite.

Anti-pattern

Don't make MCP your new ORM.

Bad


#[McpTool]
function rawQuery(string $sql): array
{
    return DB::select($sql);
}
          

Now your model is a SQL injection vector with a co-pilot.

Good


#[McpTool]
function cancelSubscription(
    int $id,
    string $reason,
): array {
    return $this->subs->cancel($id, $reason);
}
          

Tools match business operations.

The shape of your tools is product design, not API design.

Section 7 · 7 min

The other direction:
PHP as MCP client.

Up to here, PHP was the back-end for someone else's AI. Now PHP becomes the agent.

Flip the table

┌──────────────┐ ┌────────────┐ ┌──────────────┐ │ YOUR PHP │ stdio │ CLIENT │ json │ someone │ │ APP / agent │ ◄──────►│ (in your │ ◄──────►│ else's MCP │ │ HOST │ │ app) │ │ SERVER │ └──────────────┘ └────────────┘ └──────────────┘

Same protocol, mirrored. Your PHP app consumes MCP servers built by other people — or other teams inside your company.

This is what makes PHP a viable platform for building AI agents.

When you'd want this

  • An AI agent in Laravel/Symfony that needs to call GitHub, Slack, your CRM
  • A PHP "aggregator" orchestrating multiple internal MCP servers
  • A queue worker (Horizon, Messenger) pulling fresh data on a schedule
  • An Artisan / Symfony console command that drives an agentic workflow
  • Replacing a REST call with an MCP call — same data, AI-friendly schema for free

Anywhere you'd previously have called a REST API or shelled out to Node — call an MCP server from PHP instead.

Anatomy of a PHP MCP client


<?php
use Mcp\Client;
use Mcp\Client\Transport\StdioTransport;

$client = Client::builder()
    ->setClientInfo('My Laravel Agent', '1.0.0')
    ->setRequestTimeout(120)
    ->build();

// Connect to a local MCP server running as a subprocess
$transport = new StdioTransport(
    command: 'php',
    args: ['/path/to/server.php'],
);
$client->connect($transport);

// Discover what's available
$tools = $client->listTools();

// Use a tool
$result = $client->callTool('add', ['a' => 47, 'b' => 119]);

// Read a resource
$content = $client->readResource('config://calculator/settings');

$client->disconnect();
      

Same code, remote server


use Mcp\Client\Transport\HttpTransport;

$transport = new HttpTransport('https://your-server.example.com/mcp');
$client->connect($transport);

$tools = $client->listTools();
$result = $client->callTool('search_repos', ['q' => 'mcp']);
      

One line changes between local stdio and remote HTTP. The rest of your agent code is identical.

The agentic loop, in PHP

1. User → PHP app 2. PHP app → LLM ( question + available MCP tool defs ) 3. LLM → "Call findCustomer(email=…)" 4. PHP app → MCP server ( as MCP client ) 5. MCP svr → result 6. PHP app → LLM ( append tool result, ask for next step ) 7. LLM → final answer ( or another tool call → loop ) 8. PHP app → User
  • Symfony AI Agent (symfony/ai) wraps this loop — declare an Agent, hand it MCP clients as toolboxes.
  • Laravel: wire the LLM call yourself (Anthropic SDK, Prism, etc.) — ~80 lines for a working agent.

PHP MCP client libraries

LibraryWhen to reach for it
mcp/sdkOfficial. Server & client. Sync API. Start here.
php-mcp/clientReactPHP-based. Both sync (blocking) and async (Promise) APIs. Use when you need concurrent fan-out.
swisnl/mcp-clientModern PHP 8.2+. Client-only. Clean transport set: SSE, stdio, Process, Streamable HTTP.
logiscape/mcp-sdk-phpIncludes a client. The included web-client is a great teaching demo.

Most PHP apps are sync request/response. Async is overkill unless you're fanning out across many servers.

Same security rules, mirrored

As server: you worry about your tool inputs.
As client: you worry about the servers you trust.

A malicious or sloppy MCP server can…

  • Lie about what a tool does — prompt injection via tool descriptions
  • Bloat outputs to burn through your LLM tokens
  • Poison data your model carries into later turns

Mitigations

  • Allowlist which servers your app may connect to
  • Sandbox stdio subprocesses — restrict filesystem and network
  • Review tool descriptions before exposing third-party servers to production LLMs
  • Don't pass user PII to public/third-party servers without explicit consent

Section 8 · 7 min

Production
reality.

Transport — pick once, plan ahead

stdio

Local dev. Single-user tools. IDE plugins. Server lives as long as the host.

Streamable HTTP

Everything else. Multi-user. Remote. Behind a load balancer.

Same SDK; one line of code difference. Start stdio. Graduate to HTTP when you have users.

Sessions and state


// In-memory — fine for stdio, single instance
$server = Server::builder()->setSession(ttl: 7200)->build();

// Redis-backed — production, scaled HTTP
$server = Server::builder()
    ->setSession(new Psr16SessionStore(
        cache: new Psr16Cache($redisAdapter),
        prefix: 'mcp-',
        ttl: 3600,
    ))
    ->build();
      

For HTTP transport at scale: PSR-16 backend (Redis, Memcached). File-based works for single-server setups.

Auth — OAuth 2.1 is non-negotiable for remote

  • HTTP-exposed MCP servers must use OAuth 2.1 with Resource Indicators and PKCE.
  • Laravel MCP: Sanctum + OAuth built in. Same middleware you already use.
  • Symfony MCP Bundle: integrates with Symfony security firewall.
  • Server-to-server (no end user): bearer API keys are acceptable.

The official SDK gives you the primitives; framework bundles give you the ergonomics.

Security mindset — both directions

  • As server: tools execute code on your server. Every tool input is a public HTTP form.
  • As client: every server you connect to is untrusted code from someone else.
  • Confused-deputy: the model isn't the authenticated user — the human is. Authorize in the tool, not in the model.
  • Rate-limit per session. Log every tool call with arguments (redacted).
  • Destructive actions → gate behind elicitation.

Deployment patterns

  • PHP-FPM behind nginx — treat MCP endpoints like any other API.
  • FrankenPHP / Roadrunner / Swoole — long-lived processes match MCP's persistent-session model.
  • Long-running tools → async tasks (new in 2025-11-25) or background queues with progress notifications.
  • Containers: stdio servers as a sidecar next to your main app.
  • Observability: log JSON-RPC traffic, emit progress for tools > 2s, monitor latency / error rate / tool-call volume.

Section 9 · 2 min

What's new in
2025-11-25.

The one-year anniversary release.

The big four additions

FeatureWhat it does
Elicitation Formal human-in-the-loop. Form mode (JSON Schema) for structured data. URL mode for sensitive info that must bypass the client.
Structured output Tools declare a JSON Schema outputSchema. Clients validate; downstream tooling parses reliably.
Async tasks Any request can be tagged as a task. Client polls for status. Native answer to "tool takes 8 minutes."
Better OAuth + extensions Tighter OAuth 2.1 (Resource Indicators, DCR). First-class extension mechanism — vendors add capabilities without forking the spec.

Section 10 · 2 min

Takeaways.

What to do Monday

  1. composer require mcp/sdk — or your framework's equivalent.
  2. Wrap one thing from your existing app as a tool. Anything. Make it boring.
  3. Point Claude Desktop or Cursor at it via stdio.
  4. Flip the script: write a ~50-line PHP CLI that uses the same SDK as a client to consume someone else's MCP server.
  5. Ship to your team's local dev. Don't worry about HTTP/OAuth until you have users.

The big idea

You already know
how to build this.

The hard part was a Python problem. PHP is officially supported on both sides of MCP — server, client, agent. All your existing knowledge applies.

Resources

Official

  • SDK · github.com/modelcontextprotocol/php-sdk
  • Docs · php.sdk.modelcontextprotocol.io
  • Spec · modelcontextprotocol.io
  • Reference servers · github.com/modelcontextprotocol/servers

Framework

  • Laravel · laravel.com/docs/mcp
  • Symfony bundle · symfony.com/doc/current/ai/bundles/mcp-bundle.html
  • Symfony AI (agents) · ai.symfony.com

Hosting / client

  • Shared hosting · github.com/logiscape/mcp-sdk-php
  • Client (ReactPHP) · github.com/php-mcp/client
  • Client (modern, light) · github.com/swisnl/mcp-client

This talk

  • Slides + code · github.com/your-handle/php-mcp-phptek

Q & A

Thanks.

I'll be around, happy to answer any questions

Chris Tankersley · Vonage
chris.tankersley@vonage.com
chris@ctankersley.com
https://phpc.social/@dragonmantank
https://bsky.app/profile/dragonmantank.bsky.social