Mirror of https://github.com/modelcontextprotocol/php-sdk — The official PHP SDK for Model Context Protocol servers and clients. Maintained in collaboration with The PHP Foundation.
  • PHP 99.8%
  • Makefile 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Christopher Hertel c5dbfb64e5
[Server][Capability] Do not announce an externally loaded registry as changed (#490)
* [Capability] Do not announce an externally loaded registry as changed

`Registry` suppresses its `*ListChangedEvent`s while it is loading — `dispatch()`
returns early on the `loading` guard, so the elements a loader registers are the
registry's initial contents rather than a change to them.
`testListChangedEventsAreSuppressedDuringTheDeferredLoad` pins that.

The guard only covers `Registry::load()`, which needs the loader the constructor
took. A registry the caller built cannot be given one that way, so
`Builder::resolve()` loads it from the outside instead — by calling
`$chainLoader->load($registry)` directly, which never sets the guard. Every
element then dispatches on the way in.

With a notification bus configured that is not quiet. `PublishingEventDispatcher`
turns each event into a published notification, so every `build()` puts one
`list_changed` per element on the bus for a registry that did not change. Under
PHP-FPM, where the server is built per request and `Psr16NotificationBus` is
shared and persistent, every request broadcasts its whole element list to every
open `subscriptions/listen` stream and consumes the 256-entry backlog — after
which a reader that fell behind silently skips real notifications.

Split the guarded body of `load()` into `loadFrom(LoaderInterface $loader)` and
route the custom-registry branch through it.

`loadFrom()` takes only the `loading` guard, not the `loaded` bookkeeping, which
stays with `load()`. The loader it runs belongs to the caller, so it cannot stand
in for the one the registry was constructed with: marking the registry loaded
would retire a constructor loader that never ran, and would make a second
`loadFrom()` — one registry handed to two builders — a silent no-op. Keeping
`loaded` out of it also keeps `load()`'s promise that a transient failure is
retried on the next read, including when the failing loader reads the registry
during its own run.

`RegistryInterface` declares no `load()`, so this stays on the concrete
`Registry`, and a third-party implementation keeps the path it has today.

* Apply batched suggestions from code review

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
2026-08-30 01:10:56 +02:00
.github chore(deps): bump astral-sh/setup-uv from 9.0.0 to 10.0.1 (#483) 2026-08-26 07:45:13 +02:00
.phpdoc/template [Docs] Render the guides with Zensical instead of phpDocumentor (#414) 2026-08-20 00:03:54 +02:00
adr [Docs] Render the guides with Zensical instead of phpDocumentor (#414) 2026-08-20 00:03:54 +02:00
docs [Docs] Deprecation policy, SDK tier target, and CONTRIBUTING.md (#472) 2026-08-24 01:23:52 +02:00
examples [Server] Serve ClientGateway::elicit() under the modern lifecycle (#466) 2026-08-20 01:25:59 +02:00
src [Server][Capability] Do not announce an externally loaded registry as changed (#490) 2026-08-30 01:10:56 +02:00
tests [Server][Capability] Do not announce an externally loaded registry as changed (#490) 2026-08-30 01:10:56 +02:00
.gitattributes [Docs] Render the guides with Zensical instead of phpDocumentor (#414) 2026-08-20 00:03:54 +02:00
.gitignore [Docs] Render the guides with Zensical instead of phpDocumentor (#414) 2026-08-20 00:03:54 +02:00
.php-cs-fixer.dist.php CS update after release of v3.95 (#282) 2026-04-26 12:39:27 +02:00
CHANGELOG.md [Changelog] Restructure 0.8.0 section (#478) 2026-08-24 01:16:10 +02:00
composer.json [Schema][Client][Server] Deprecate Roots, Sampling and Logging (SEP-2577) (#427) 2026-08-18 01:23:32 +02:00
CONTRIBUTING.md [Docs] Deprecation policy, SDK tier target, and CONTRIBUTING.md (#472) 2026-08-24 01:23:52 +02:00
LICENSE chore: update licensing to Apache 2.0 for new contributions (#222) 2026-01-18 21:29:44 +01:00
Makefile [Docs] Render the guides with Zensical instead of phpDocumentor (#414) 2026-08-20 00:03:54 +02:00
mkdocs.yml [Docs] Deprecation policy, SDK tier target, and CONTRIBUTING.md (#472) 2026-08-24 01:23:52 +02:00
phpdoc.dist.xml [Docs] Render the guides with Zensical instead of phpDocumentor (#414) 2026-08-20 00:03:54 +02:00
phpstan-baseline.neon [Server] Let a server say how long its answers stay fresh (#450) 2026-08-18 23:53:16 +02:00
phpstan.dist.neon style: apply php-cs-fixer and PHPStan fixes (#289) 2026-04-20 20:49:15 +02:00
phpunit.xml.dist [Tests] Introduce client-server integration tests (#419) 2026-08-15 02:24:48 +02:00
README.md [Docs] Deprecation policy, SDK tier target, and CONTRIBUTING.md (#472) 2026-08-24 01:23:52 +02:00
requirements-docs.txt [Docs] Render the guides with Zensical instead of phpDocumentor (#414) 2026-08-20 00:03:54 +02:00
ROADMAP.md Add badge linebreak and v1.0 roadmap items (#461) 2026-08-19 00:30:10 +02:00
SECURITY.md Add SECURITY.md with GitHub Security Advisories guidance (#250) 2026-02-22 19:20:00 +01:00

MCP PHP SDK

Latest Version CI PHP Version License

Server Conformance 2025-11-25 Client Conformance 2025-11-25 Server Conformance 2026-07-28 Client Conformance 2026-07-28

The official PHP SDK for the Model Context Protocol (MCP). It provides a framework-agnostic API for implementing MCP servers and clients in PHP — tools, resources, prompts, STDIO and HTTP transports, sessions, authorization, and both protocol eras (the initialize handshake and the stateless 2026-07-28 revision).

This project represents a collaboration between the PHP Foundation and the Symfony project. It adopts development practices and standards from the Symfony project, including Coding Standards and the Backward Compatibility Promise.

Until the first major release, this SDK is considered experimental, please see the roadmap for planned next steps and features.

Installation

composer require mcp/sdk

Build a server

A server is a plain PHP class plus three lines of wiring:

use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;

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

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

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

The walkthrough in First server explains each piece, and Try it with the Inspector shows it running.

Build a client

use Mcp\Client;
use Mcp\Client\Transport\StdioTransport;

$client = Client::builder()
    ->setClientInfo('My Application', '1.0.0')
    ->build();

$client->connect(new StdioTransport(command: 'php', args: ['/path/to/server.php']));

$tools = $client->listTools();
$result = $client->callTool('add', ['a' => 5, 'b' => 3]);

$client->disconnect();

See Connecting to a server for transports, timeouts, and the handlers that answer server-initiated requests.

Documentation

The full documentation is published at php.sdk.modelcontextprotocol.io.

  • Get started — Install the SDK and build your first server
  • Servers — Tools, resources, resource templates, prompts, and how to register them
  • Inside your handler — Talking back to the client, logging, and asking for input
  • Running your server — Server builder, STDIO and HTTP transports, framework integration, sessions, authorization
  • Clients — Client SDK for connecting to and communicating with MCP servers
  • Protocol versions — The two protocol eras, and what revision 2026-07-28 changed
  • Advanced — Events, protocol extensions (including MCP Apps), and custom message handlers
  • Examples — Runnable server and client examples
  • API Reference — Generated class reference

External Resources

PHP Libraries Using the MCP SDK

Building something on top of the SDK? Open a pull request to add it to this list.

Contributing

We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project. Start by reporting issues or sending pull requests. See CONTRIBUTING.md for development setup, coding standards, and what to run before opening a PR.

Credits

The starting point for this SDK was the PHP-MCP project, initiated by Kyrian Obikwelu, and the Symfony AI initiative. We are grateful for the work done by both projects and their contributors, which created a solid foundation for this SDK.

License

This project is licensed under the Apache License, Version 2.0 for new contributions, with existing code under the MIT License — see the LICENSE file for details.