Перейти к содержимому
Выберите тему

PHP SDK

Официальный PHP SDK для MCP, пакет mcp/sdk 0.8.1 - сервер отвечает обеим эпохам с одного адреса, клиент по умолчанию говорит на 2025-11-25, SDK ещё не достиг версии 1.0

сверено с выпуском 0.8.1 4 октября 2026

По README выпуска это официальный PHP SDK для MCP, не привязанный к какой-либо программной платформе: серверы и клиенты, транспорты stdio и HTTP, обе эпохи протокола.

README называет и тех, кто ведёт проект: это совместная работа The PHP Foundation и проекта Symfony. Проект следует стандартам кода Symfony и его обещанию обратной совместимости.

В списке официальных SDK у PHP третий уровень (Tier 3). По правилам уровней третий уровень - это экспериментальные, частично реализованные или специализированные SDK.

Проверенный выпуск - 0.8.1 от 29 августа 2026: выпуск на GitHub, пакет mcp/sdk на Packagist. Помеченных предварительных выпусков нет.

По руководству по установке нужны PHP 8.1 или новее и расширение fileinfo.

Установка
composer require mcp/sdk

Этого хватает для сервера и клиента на stdio. Остальное ставится по задаче:

Что нужно Что доставить
искать #[McpTool] и другие атрибуты в каталоге symfony/finder
сервер по HTTP любую реализацию PSR-17, например nyholm/psr7
отдать ответ из отдельной точки входа HTTP laminas/laminas-httphandlerrunner
клиент по HTTP любой клиент PSR-18, например symfony/http-client
проверять токены доступа JWT firebase/php-jwt

Список записан в ProtocolVersion.php: 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25, 2026-07-28. Граница эпох задана константой FIRST_MODERN_VERSION, она равна 2026-07-28.

По журналу изменений редакция 2026-07-28 появилась в выпуске 0.8.0.

Для вас это значит следующее. Сервер на этом выпуске обслуживает и клиента Modern, и клиента Legacy. Клиент на этом выпуске умеет обе эпохи, но не выбирает между ними сам: эпоху задаёте вы.

По документации клиента клиент по умолчанию предлагает в рукопожатии V2025_11_25. Редакцию 2026-07-28 нужно выбрать явно.

Клиент в эпохе Modern
$client = Client::builder()
->setClientInfo('my-client', '1.0.0')
->setProtocolVersion(ProtocolVersion::V2026_07_28)
->setCapabilities(new ClientCapabilities(elicitation: true))
->addRequestHandler($myElicitationHandler)
->build();
$client->connect(new HttpTransport('https://example.com/mcp'));
$client->callTool('greet', []);

Пример взят из документации выпуска 0.8.1 и здесь не запускался.

Что при этом меняется, описано в документации о версиях:

  • connect() не отправляет initialize. Клиент запрашивает server/discover только ради сведений о сервере. Если сервер на этот запрос не ответил, соединение всё равно считается рабочим.
  • Если ответ server/discover содержит supportedVersions и выбранной редакции там нет, клиент переходит на редакцию Modern из списка сервера или отказывается от соединения.
  • Перехода на initialize при неудаче нет. В коде клиента отказ server/discover записывается в журнал, и работа продолжается в эпохе Modern.

В старой эпохе выбранная версия - это предложение. Клиент принимает встречное предложение сервера, если знает такую редакцию. Иначе рукопожатие завершается ошибкой ConnectionException.

По описанию работы с двумя эпохами Server::builder()->build() собирает сервер с обработчиком для каждой эпохи, а StreamableHttpTransport решает по каждому запросу, какой из них ответит. Один адрес отвечает и на initialize, и на server/discover.

Решение принимается по телу запроса:

Что в запросе Куда он попадает
params._meta называет редакцию Modern эпоха Modern
params._meta называет редакцию рукопожатия эпоха Legacy
такого поля нет эпоха Legacy, включая initialize
запрос GET или DELETE эпоха Legacy

Заголовок MCP-Protocol-Version решения не принимает. Он сверяется с телом. Запрос, в котором заголовок противоречит _meta, отклоняется с кодом -32020. Неподдерживаемая версия отклоняется с кодом -32022 и списком поддерживаемых.

Рукопожатие не договаривается о версии Modern. Клиенту, который просит в initialize версию 2026-07-28, сервер предложит последнюю версию рукопожатия.

Оставить одну эпоху можно так:

  • withoutModernEra() - сервер только старой эпохи;
  • buildStateless() вместе со StatelessHttpTransport - адрес только эпохи Modern;
  • setModernVersions() - сузить список редакций Modern.
server.php
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()));

Пример взят из документации выпуска 0.8.1 и здесь не запускался.

Метод setDiscovery() ищет атрибуты в каталоге, для этого нужен пакет symfony/finder.

client.php
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();

Пример взят из документации выпуска 0.8.1 и здесь не запускался.

Этот клиент не задаёт редакцию, поэтому начинает с initialize и предлагает 2025-11-25.

Tasks. В выпуске не реализовано. В плане развития пункт «Implement the Tasks extension» не отмечен как выполненный.

MCP Apps. Есть типы и средства для сервера. По документации расширений сервер включает расширение вызовом enableExtension(new McpApps()), описывает ресурс с адресом ui:// и связывает с ним инструмент. Клиент может объявить расширение через Client\Builder::enableExtension(). Показ интерфейса в изолированной рамке - задача программы-хоста, готового средства для этого документация выпуска не описывает. Поэтому в блоке совместимости стоит «частично».

Функция Как с ней обходится выпуск 0.8.1
Roots, Sampling, Logging Помечены устаревшими и выдают предупреждение, но работают. Журнал изменений называет самый ранний срок удаления - 28 июля 2027. На редакции 2026-07-28 вызовы sample() и listRoots() бросают LogicException
initialize и сеансы Работают для клиентов старой эпохи. Документация о сеансах: сеансы относятся к эпохе рукопожатия, редакция 2026-07-28 их убрала
Динамическая регистрация клиента (RFC 7591) На стороне сервера есть ClientRegistrationMiddleware
HTTP+SSE из 2024-11-05 В выпуске не реализован. Документ об уровне SDK относит его к пробелам, которые нужно либо реализовать, либо явно назвать намеренно пропущенными

Авторизация OAuth в SDK не завершена. В плане развития пункты «Implement OAuth2 authentication for server» и «Implement OAuth2 authentication for client» не отмечены как выполненные. Документ об уровне SDK сообщает о результатах проверки соответствия: сервер - 100%, клиент - 20%, почти все провалы клиента относятся к OAuth. Этот документ написан до выпуска 0.8.x: последней версией в нём названа 0.7.1.

На 4 октября 2026 ветка main опережает метку v0.8.1 на 13 изменений. Список редакций в ProtocolVersion.php там тот же. В журнале изменений на main есть раздел 0.9.0, который ещё не выпущен. В нём, например, удалён один из аргументов атрибута #[CompletionProvider] и добавлен метод HttpTransport::getSessionId().

  • Когда выйдет версия 1.0: план развития перечисляет цели, но сроков не называет.
  • Текущие результаты проверки соответствия для выпуска 0.8.1: опубликованные в документации цифры относятся к более раннему состоянию.
  • Как сервер на stdio выбирает эпоху: документация выпуска описывает разбор запросов только для StreamableHttpTransport.