AGENTS.md
July 28, 2026 · View on GitHub
Project Overview
This is a PHP implementation of the Model Context Protocol (MCP), allowing applications to provide context for LLMs in a standardized way. The SDK implements both MCP clients and servers with support for stdio and HTTP transports.
Key characteristics:
- Designed for native PHP with easy installation via Composer
- Targets PHP 8.1+ with type safety (strict_types=1)
- Supports both traditional CLI/stdio and web hosting environments
- Speaks every MCP spec revision from
2024-11-05through2026-07-28(the "stateless core"); servers detect each request's era and clients probe-then-fall-back, so one codebase serves modern and legacy peers concurrently - Implements the Tasks (SEP-2663) and MCP Apps (SEP-1865) extensions — see docs/tasks.md and docs/apps.md
- Includes McpServer convenience wrapper for building fully functional MCP servers with just a few lines of PHP code
User-facing documentation is indexed in docs/README.md; the two deep guides are docs/server-dev.md and docs/client-dev.md, and the v1 → v2 changes are in docs/migration-v2.md.
Contributor-facing documentation
For non-trivial work, please also consult the governance and process docs at
the repository root: CONTRIBUTING.md (coding standards,
test stack, versioning policy), ROADMAP.md (direction and tier
self-assessment), SECURITY.md (vulnerability reporting),
GOVERNANCE.md, and the deeper guides under docs/ —
docs/testing.md,
docs/compatibility.md
(the cPanel/Apache compatibility rules), docs/dependency-policy.md,
and conformance/README.md (including the
no-shortcuts-for-conformance rule).
Development Process
The main branch carries v2 of the SDK (stable v1 lives on the 1.x
branch). v2 development is complete; ongoing direction — including the
embedding and web-integration batteries planned for the v2.x minor
line — is set by ROADMAP.md. Key rules for agents working on
the SDK:
- Research first. Before implementing a change, gather the latest details from official MCP sources (the spec repository, the ext-apps repository, the conformance suite). Repository documents reflect a point in time; the official text wins where they disagree, and the affected documents should be amended in the same change set.
- Implementation includes tests. A change is complete only when the
work is implemented, automated tests cover it per the project's testing
conventions, and
composer checkpasses (pluscomposer conformanceregression-free for protocol/transport/session/McpServer changes). - Human-initiated code review. After the agent has verified the work, the human user initiates a code review; the agent addresses findings and re-verifies.
- All commits are human-initiated. Agents must never run
git commit,git push, or tag releases. Leave verified work in the working tree for the human user to review, approve, and commit. No exceptions.
Development Testing Commands
The canonical, complete test-stack reference is docs/testing.md — installation, PHPUnit, PHPStan, both conformance tracks, single-scenario runs, and the interactive harnesses (MCP Inspector, Claude Code, OpenAI). The commands used on nearly every change:
composer install # dependencies (plus `npm install` for the pinned conformance tools)
composer check # the regression gate: PHPUnit + PHPStan — run before considering any change done
composer conformance # stable conformance track (published-spec scenarios)
composer conformance-draft # draft track (2026-07-28 RC scenarios)
When to run conformance: composer conformance must stay
regression-free for any change touching protocol handling, transports,
session management, or McpServer; add composer conformance-draft for
changes touching 2026-07-28 behavior, updating
conformance/conformance-draft-baseline.yml to reflect honest progress.
Neither is part of composer check (they need Node.js). Known failures
live in each track's baseline file with a root cause — never engineer a
workaround to make a scenario pass; see
conformance/README.md for the dual-track rules.
Building An MCP Server
The easiest and recommended way to create a new MCP server is to use the McpServer convenience wrapper. Here is a complete fully functional example that can be used as both a local MCP server or a remote MCP server.
<?php
require 'vendor/autoload.php';
use Mcp\Server\McpServer;
$server = new McpServer('example-mcp-server');
$server
->tool('add', 'Add numbers', fn(float $a, float $b) => "Sum: " . ($a + $b))
->prompt('greet', 'Greeting', fn(string $name) => "Hello, {$name}!")
->resource(uri: 'info://php', name: 'PHP Info', callback: fn() => PHP_VERSION)
->run();
When using the convenience wrapper, run() is a router that uses the stdio transport on cli applications and the HTTP transport on web servers. run() can be replaced with runStdio() to force the stdio transport, or runHttp() to force the HTTP transport.
Architecture Overview
Core Component Layers
-
Session Layer (
Shared/BaseSession.php)- Abstract base for all MCP sessions (client and server)
- Manages JSON-RPC message routing and handler registration
- Handles request/response matching via request IDs
- Maintains initialization state and protocol version negotiation
-
Client Architecture (
Client/)Client: Main entry point, detects transport (stdio vs HTTP) based on commandOrUrl parameterClientSession: Extends BaseSession, provides high-level methods (listPrompts(),callTool(), etc.)Transport/StdioTransport: Process-based transport using stdin/stdoutTransport/StreamableHttpTransport: HTTP/HTTPS transport with SSE support- Both transports speak JSON-RPC over their respective channels
-
Server Architecture (
Server/)Server: Request/notification handler registry, capability managementServerSession: Extends BaseSession, handles initialization handshakeServerRunner: Stdio runner that manages the server lifecycleHttpServerRunner: HTTP runner for web-based serversMcpServer: Convenience wrapper to simplify building MCP servers- Handlers are registered as callables:
registerHandler(string $method, callable $handler)
-
Types System (
Types/)- All MCP protocol types are implemented as typed PHP classes
- Types implement
McpModelinterface for JSON serialization/deserialization - Uses
ExtraFieldsTraitfor forward compatibility with unknown fields - Request/Response types follow JSON-RPC 2.0 specification
-
Transport Abstraction
- Stdio: Uses PHP process control (pcntl) for server process management
- HTTP: Supports both standard HTTP and Server-Sent Events (SSE) for streaming
MemoryStreamfor testing without actual I/OHttpIoInterface: SAPI adapter seam for the HTTP runner.NativePhpIo(default) wrapsheader()/echo/flush()/ob_*/connection_abortedfor cPanel/Apache/FPM;BufferedIocaptures bytes for tests or non-SAPI hosts. Pass a custom implementation viaMcpServer::httpOptions(['io' => $adapter])or theHttpServerRunnerconstructor to embed the runner in a framework (Symfony, Slim, FrankenPHP, RoadRunner).handleRequest()returns aStreamedHttpMessagewhen the streaming-SSE body was already written through the adapter during handler execution — integrators can checkinstanceof StreamedHttpMessageto skip re-emitting the body.
Handler Registration Pattern
Server-side:
$server
// Define a tool
->tool('add-numbers', 'Adds two numbers together', function (float $a, float $b): string {
return 'Sum: ' . ($a + $b);
});
Client-side:
// ClientSession provides convenience methods that internally send JSON-RPC requests
$prompts = $session->listPrompts();
$result = $session->callTool($toolName, $arguments);
Protocol Version Negotiation
The SDK speaks two protocol eras and negotiates per the spec's detection rules:
- Modern era (
2026-07-28, the latest revision): noinitializehandshake — every request carries protocol version, client info, and client capabilities in its_metaenvelope, andserver/discoveranswers capability discovery statelessly. The server detects a modern request per-request (envelope orMCP-Protocol-Versionheader) and serves it on a fresh ephemeral context; noMcp-Session-Idexists on this path. - Legacy era (
2024-11-05…2025-11-25): the classicinitializehandshake, negotiated to the highest mutually supported version, with the session header on HTTP.Version::LATEST_LEGACY_PROTOCOL_VERSIONcaps what the handshake can negotiate. - Client side:
Client::connect()probesserver/discoverfirst and falls back toinitialize(protocolMode: 'auto' | 'modern' | 'legacy'). - Version constants live in
src/Shared/Version.php; feature gating inServerSession::clientSupportsFeature()/ClientSession::supportsFeature(); response shaping for older clients inServerSession::adaptResponseForClient().
Web Hosting Considerations
The SDK is designed for typical PHP web hosting (cPanel/Apache/FPM):
- The
2026-07-28stateless model is a natural fit: every modern request is self-contained, so a fresh PHP process per request needs no persisted protocol state at all. - Legacy sessions persist to files (or another
SessionStoreInterface) between requests; the HTTP transport works without long-running processes on both eras. - Cross-process event fan-out for
subscriptions/listenusesFileSubscriptionBus; the Tasks extension'sTaskManagerstore is file-based for the same reason. - See docs/compatibility.md for the compatibility rules and
webclient/for the reference client implementation.
Testing Patterns
Tests use PHPUnit 10+ and follow these conventions:
- Test classes are marked
finaland extendPHPUnit\Framework\TestCase - Test methods include detailed docblocks explaining what is being validated
- Mock transports using
MemoryStreamorInMemoryTransportfor isolation - Focus on protocol compliance and state transitions
- Test files mirror source structure:
tests/Server/ServerSessionTest.phptestssrc/Server/ServerSession.php
Important Implementation Notes
Type Safety
- All files use
declare(strict_types=1); - Parameters and return types are strictly typed
- Use type hints on handler callables for automatic param deserialization
Error Handling
- Protocol errors throw
Mcp\Shared\McpError - Transport errors throw
RuntimeException - Invalid parameters throw
InvalidArgumentException - Errors are automatically converted to JSON-RPC error responses
Logging
- All major components accept optional PSR-3
LoggerInterface - Defaults to
NullLoggerif not provided - Examples use Monolog for demonstration
OAuth Support
- HTTP transport includes OAuth 2.1 authorization framework
- Server-side implementation available in
Server/Auth/ - Client-side implementation available in
Client/Auth/ - See
examples/server_auth/for usage
MCP Protocol Capabilities
Servers expose capabilities through handler registration:
- Prompts:
prompts/list,prompts/get - Resources:
resources/list,resources/read(+resources/subscribeon legacy revisions only) - Tools:
tools/call,tools/list - Completions:
completion/complete - Logging:
logging/setLevel(legacy revisions; deprecated by SEP-2577) - Subscriptions:
subscriptions/listen(modern-era change-notification channel, backed by aSubscriptionBusInterfaceon HTTP) - Discovery:
server/discover(modern era; answered automatically with the same capabilities as the legacyinitializeresult) - Extensions (SEP-2133
extensionscapability map): Tasks (tasks/get,tasks/update,tasks/cancelviaenableTasks()) and MCP Apps (McpServer::ui(), capability +_metaonly — no new RPC)
Capabilities are automatically detected based on registered handlers and included in both the legacy initialization response and the modern server/discover result.
Common Patterns
Creating a Server (Use convenience wrapper)
- Instantiate
McpServerwith a name - Register tools, prompts, and/or resources for desired capabilities
- Call
$server->run()to start
Creating a Client
- Instantiate
Client - Call
$client->connect()with command/URL and parameters - Returns initialized
ClientSession - Use session methods to interact with server
- Call
$client->close()when done