Kick off the SDK based on Symfony's MCP SDK

This commit is contained in:
Christopher Hertel 2025-07-25 11:40:39 +02:00
commit ee0dffe369
No known key found for this signature in database
GPG key ID: 111F6BFF7B6B5AA3
19 changed files with 3924 additions and 0 deletions

View file

@ -0,0 +1,163 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server;
use Mcp\Exception\ExceptionInterface;
use Mcp\Exception\HandlerNotFoundException;
use Mcp\Exception\InvalidInputMessageException;
use Mcp\Exception\NotFoundExceptionInterface;
use Mcp\Message\Error;
use Mcp\Message\Factory;
use Mcp\Message\Notification;
use Mcp\Message\Request;
use Mcp\Message\Response;
use Psr\Log\LoggerInterface;
/**
* @final
*
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
class JsonRpcHandler
{
/**
* @var array<int, RequestHandlerInterface>
*/
private readonly array $requestHandlers;
/**
* @var array<int, NotificationHandlerInterface>
*/
private readonly array $notificationHandlers;
/**
* @param iterable<RequestHandlerInterface> $requestHandlers
* @param iterable<NotificationHandlerInterface> $notificationHandlers
*/
public function __construct(
private readonly Factory $messageFactory,
iterable $requestHandlers,
iterable $notificationHandlers,
private readonly LoggerInterface $logger,
) {
$this->requestHandlers = $requestHandlers instanceof \Traversable ? iterator_to_array($requestHandlers) : $requestHandlers;
$this->notificationHandlers = $notificationHandlers instanceof \Traversable ? iterator_to_array($notificationHandlers) : $notificationHandlers;
}
/**
* @return iterable<string|null>
*
* @throws ExceptionInterface When a handler throws an exception during message processing
* @throws \JsonException When JSON encoding of the response fails
*/
public function process(string $input): iterable
{
$this->logger->info('Received message to process', ['message' => $input]);
try {
$messages = $this->messageFactory->create($input);
} catch (\JsonException $e) {
$this->logger->warning('Failed to decode json message', ['exception' => $e]);
yield $this->encodeResponse(Error::parseError($e->getMessage()));
return;
}
foreach ($messages as $message) {
if ($message instanceof InvalidInputMessageException) {
$this->logger->warning('Failed to create message', ['exception' => $message]);
yield $this->encodeResponse(Error::invalidRequest(0, $message->getMessage()));
continue;
}
$this->logger->info('Decoded incoming message', ['message' => $message]);
try {
yield $message instanceof Notification
? $this->handleNotification($message)
: $this->encodeResponse($this->handleRequest($message));
} catch (\DomainException) {
yield null;
} catch (NotFoundExceptionInterface $e) {
$this->logger->warning(\sprintf('Failed to create response: %s', $e->getMessage()), ['exception' => $e]);
yield $this->encodeResponse(Error::methodNotFound($message->id, $e->getMessage()));
} catch (\InvalidArgumentException $e) {
$this->logger->warning(\sprintf('Invalid argument: %s', $e->getMessage()), ['exception' => $e]);
yield $this->encodeResponse(Error::invalidParams($message->id, $e->getMessage()));
} catch (\Throwable $e) {
$this->logger->critical(\sprintf('Uncaught exception: %s', $e->getMessage()), ['exception' => $e]);
yield $this->encodeResponse(Error::internalError($message->id, $e->getMessage()));
}
}
}
/**
* @throws \JsonException When JSON encoding fails
*/
private function encodeResponse(Response|Error|null $response): ?string
{
if (null === $response) {
$this->logger->warning('Response is null');
return null;
}
$this->logger->info('Encoding response', ['response' => $response]);
if ($response instanceof Response && [] === $response->result) {
return json_encode($response, \JSON_THROW_ON_ERROR | \JSON_FORCE_OBJECT);
}
return json_encode($response, \JSON_THROW_ON_ERROR);
}
/**
* @return null
*
* @throws ExceptionInterface When a notification handler throws an exception
*/
private function handleNotification(Notification $notification)
{
$handled = false;
foreach ($this->notificationHandlers as $handler) {
if ($handler->supports($notification)) {
$handler->handle($notification);
$handled = true;
}
}
if (!$handled) {
$this->logger->warning(\sprintf('No handler found for "%s".', $notification->method), ['notification' => $notification]);
}
return null;
}
/**
* @throws NotFoundExceptionInterface When no handler is found for the request method
* @throws ExceptionInterface When a request handler throws an exception
*/
private function handleRequest(Request $request): Response|Error
{
foreach ($this->requestHandlers as $handler) {
if ($handler->supports($request)) {
return $handler->createResponse($request);
}
}
throw new HandlerNotFoundException(\sprintf('No handler found for method "%s".', $request->method));
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\NotificationHandler;
use Mcp\Message\Notification;
use Mcp\Server\NotificationHandlerInterface;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
abstract class BaseNotificationHandler implements NotificationHandlerInterface
{
public function supports(Notification $message): bool
{
return $message->method === \sprintf('notifications/%s', $this->supportedNotification());
}
abstract protected function supportedNotification(): string;
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\NotificationHandler;
use Mcp\Message\Notification;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class InitializedHandler extends BaseNotificationHandler
{
protected function supportedNotification(): string
{
return 'initialized';
}
public function handle(Notification $notification): void
{
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server;
use Mcp\Exception\ExceptionInterface;
use Mcp\Message\Notification;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
interface NotificationHandlerInterface
{
public function supports(Notification $message): bool;
/**
* @throws ExceptionInterface When the handler encounters an error processing the notification
*/
public function handle(Notification $notification): void;
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\RequestHandler;
use Mcp\Message\Request;
use Mcp\Server\RequestHandlerInterface;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
abstract class BaseRequestHandler implements RequestHandlerInterface
{
public function supports(Request $message): bool
{
return $message->method === $this->supportedMethod();
}
abstract protected function supportedMethod(): string;
}

View file

@ -0,0 +1,45 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\RequestHandler;
use Mcp\Message\Request;
use Mcp\Message\Response;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class InitializeHandler extends BaseRequestHandler
{
public function __construct(
private readonly string $name = 'app',
private readonly string $version = 'dev',
) {
}
public function createResponse(Request $message): Response
{
return new Response($message->id, [
'protocolVersion' => '2025-03-26',
'capabilities' => [
'prompts' => ['listChanged' => false],
'tools' => ['listChanged' => false],
'resources' => ['listChanged' => false, 'subscribe' => false],
],
'serverInfo' => ['name' => $this->name, 'version' => $this->version],
]);
}
protected function supportedMethod(): string
{
return 'initialize';
}
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\RequestHandler;
use Mcp\Message\Request;
use Mcp\Message\Response;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class PingHandler extends BaseRequestHandler
{
public function createResponse(Request $message): Response
{
return new Response($message->id, []);
}
protected function supportedMethod(): string
{
return 'ping';
}
}

View file

@ -0,0 +1,83 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\RequestHandler;
use Mcp\Capability\Prompt\PromptGet;
use Mcp\Capability\Prompt\PromptGetterInterface;
use Mcp\Exception\ExceptionInterface;
use Mcp\Exception\InvalidArgumentException;
use Mcp\Message\Error;
use Mcp\Message\Request;
use Mcp\Message\Response;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class PromptGetHandler extends BaseRequestHandler
{
public function __construct(
private readonly PromptGetterInterface $getter,
) {
}
public function createResponse(Request $message): Response|Error
{
$name = $message->params['name'];
$arguments = $message->params['arguments'] ?? [];
try {
$result = $this->getter->get(new PromptGet(uniqid('', true), $name, $arguments));
} catch (ExceptionInterface) {
return Error::internalError($message->id, 'Error while handling prompt');
}
$messages = [];
foreach ($result->messages as $resultMessage) {
$content = match ($resultMessage->type) {
'text' => [
'type' => 'text',
'text' => $resultMessage->result,
],
'image', 'audio' => [
'type' => $resultMessage->type,
'data' => $resultMessage->result,
'mimeType' => $resultMessage->mimeType,
],
'resource' => [
'type' => 'resource',
'resource' => [
'uri' => $resultMessage->uri,
'mimeType' => $resultMessage->mimeType,
'text' => $resultMessage->result,
],
],
// TODO better exception
default => throw new InvalidArgumentException('Unsupported PromptGet result type: '.$resultMessage->type),
};
$messages[] = [
'role' => $resultMessage->role,
'content' => $content,
];
}
return new Response($message->id, [
'description' => $result->description,
'messages' => $messages,
]);
}
protected function supportedMethod(): string
{
return 'prompts/get';
}
}

View file

@ -0,0 +1,85 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\RequestHandler;
use Mcp\Capability\Prompt\CollectionInterface;
use Mcp\Message\Request;
use Mcp\Message\Response;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class PromptListHandler extends BaseRequestHandler
{
public function __construct(
private readonly CollectionInterface $collection,
private readonly int $pageSize = 20,
) {
}
public function createResponse(Request $message): Response
{
$nextCursor = null;
$prompts = [];
$metadataList = $this->collection->getMetadata(
$this->pageSize,
$message->params['cursor'] ?? null
);
foreach ($metadataList as $metadata) {
$nextCursor = $metadata->getName();
$result = [
'name' => $metadata->getName(),
];
$description = $metadata->getDescription();
if (null !== $description) {
$result['description'] = $description;
}
$arguments = [];
foreach ($metadata->getArguments() as $data) {
$argument = [
'name' => $data['name'],
'required' => $data['required'] ?? false,
];
if (isset($data['description'])) {
$argument['description'] = $data['description'];
}
$arguments[] = $argument;
}
if ([] !== $arguments) {
$result['arguments'] = $arguments;
}
$prompts[] = $result;
}
$result = [
'prompts' => $prompts,
];
if (null !== $nextCursor && \count($prompts) === $this->pageSize) {
$result['nextCursor'] = $nextCursor;
}
return new Response($message->id, $result);
}
protected function supportedMethod(): string
{
return 'prompts/list';
}
}

View file

@ -0,0 +1,79 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\RequestHandler;
use Mcp\Capability\Resource\CollectionInterface;
use Mcp\Message\Request;
use Mcp\Message\Response;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class ResourceListHandler extends BaseRequestHandler
{
public function __construct(
private readonly CollectionInterface $collection,
private readonly int $pageSize = 20,
) {
}
public function createResponse(Request $message): Response
{
$nextCursor = null;
$resources = [];
$metadataList = $this->collection->getMetadata(
$this->pageSize,
$message->params['cursor'] ?? null
);
foreach ($metadataList as $metadata) {
$nextCursor = $metadata->getUri();
$result = [
'uri' => $metadata->getUri(),
'name' => $metadata->getName(),
];
$description = $metadata->getDescription();
if (null !== $description) {
$result['description'] = $description;
}
$mimeType = $metadata->getMimeType();
if (null !== $mimeType) {
$result['mimeType'] = $mimeType;
}
$size = $metadata->getSize();
if (null !== $size) {
$result['size'] = $size;
}
$resources[] = $result;
}
$result = [
'resources' => $resources,
];
if (null !== $nextCursor && \count($resources) === $this->pageSize) {
$result['nextCursor'] = $nextCursor;
}
return new Response($message->id, $result);
}
protected function supportedMethod(): string
{
return 'resources/list';
}
}

View file

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\RequestHandler;
use Mcp\Capability\Resource\ResourceRead;
use Mcp\Capability\Resource\ResourceReaderInterface;
use Mcp\Exception\ExceptionInterface;
use Mcp\Exception\ResourceNotFoundException;
use Mcp\Message\Error;
use Mcp\Message\Request;
use Mcp\Message\Response;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class ResourceReadHandler extends BaseRequestHandler
{
public function __construct(
private readonly ResourceReaderInterface $reader,
) {
}
public function createResponse(Request $message): Response|Error
{
$uri = $message->params['uri'];
try {
$result = $this->reader->read(new ResourceRead(uniqid('', true), $uri));
} catch (ResourceNotFoundException $e) {
return new Error($message->id, Error::RESOURCE_NOT_FOUND, $e->getMessage());
} catch (ExceptionInterface) {
return Error::internalError($message->id, 'Error while reading resource');
}
return new Response($message->id, [
'contents' => [
[
'uri' => $result->uri,
'mimeType' => $result->mimeType,
$result->type => $result->result,
],
],
]);
}
protected function supportedMethod(): string
{
return 'resources/read';
}
}

View file

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\RequestHandler;
use Mcp\Capability\Tool\ToolCall;
use Mcp\Capability\Tool\ToolExecutorInterface;
use Mcp\Exception\ExceptionInterface;
use Mcp\Exception\InvalidArgumentException;
use Mcp\Message\Error;
use Mcp\Message\Request;
use Mcp\Message\Response;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class ToolCallHandler extends BaseRequestHandler
{
public function __construct(
private readonly ToolExecutorInterface $toolExecutor,
) {
}
public function createResponse(Request $message): Response|Error
{
$name = $message->params['name'];
$arguments = $message->params['arguments'] ?? [];
try {
$result = $this->toolExecutor->call(new ToolCall(uniqid('', true), $name, $arguments));
} catch (ExceptionInterface) {
return Error::internalError($message->id, 'Error while executing tool');
}
$content = match ($result->type) {
'text' => [
'type' => 'text',
'text' => $result->result,
],
'image', 'audio' => [
'type' => $result->type,
'data' => $result->result,
'mimeType' => $result->mimeType,
],
'resource' => [
'type' => 'resource',
'resource' => [
'uri' => $result->uri,
'mimeType' => $result->mimeType,
'text' => $result->result,
],
],
// TODO better exception
default => throw new InvalidArgumentException('Unsupported tool result type: '.$result->type),
};
return new Response($message->id, [
'content' => [$content], // TODO: allow multiple `ToolCallResult`s in the future
'isError' => $result->isError,
]);
}
protected function supportedMethod(): string
{
return 'tools/call';
}
}

View file

@ -0,0 +1,68 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\RequestHandler;
use Mcp\Capability\Tool\CollectionInterface;
use Mcp\Message\Request;
use Mcp\Message\Response;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class ToolListHandler extends BaseRequestHandler
{
public function __construct(
private readonly CollectionInterface $collection,
private readonly int $pageSize = 20,
) {
}
public function createResponse(Request $message): Response
{
$nextCursor = null;
$tools = [];
$metadataList = $this->collection->getMetadata(
$this->pageSize,
$message->params['cursor'] ?? null
);
foreach ($metadataList as $tool) {
$nextCursor = $tool->getName();
$inputSchema = $tool->getInputSchema();
$tools[] = [
'name' => $tool->getName(),
'description' => $tool->getDescription(),
'inputSchema' => [] === $inputSchema ? [
'type' => 'object',
'$schema' => 'http://json-schema.org/draft-07/schema#',
] : $inputSchema,
];
}
$result = [
'tools' => $tools,
];
if (null !== $nextCursor && \count($tools) === $this->pageSize) {
$result['nextCursor'] = $nextCursor;
}
return new Response($message->id, $result);
}
protected function supportedMethod(): string
{
return 'tools/list';
}
}

View file

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server;
use Mcp\Exception\ExceptionInterface;
use Mcp\Message\Error;
use Mcp\Message\Request;
use Mcp\Message\Response;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
interface RequestHandlerInterface
{
public function supports(Request $message): bool;
/**
* @throws ExceptionInterface When the handler encounters an error processing the request
*/
public function createResponse(Request $message): Response|Error;
}

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\Transport\Sse\Store;
use Mcp\Server\Transport\Sse\StoreInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Uid\Uuid;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class CachePoolStore implements StoreInterface
{
public function __construct(
private readonly CacheItemPoolInterface $cachePool,
) {
}
public function push(Uuid $id, string $message): void
{
$item = $this->cachePool->getItem($this->getCacheKey($id));
$messages = $item->isHit() ? $item->get() : [];
$messages[] = $message;
$item->set($messages);
$this->cachePool->save($item);
}
public function pop(Uuid $id): ?string
{
$item = $this->cachePool->getItem($this->getCacheKey($id));
if (!$item->isHit()) {
return null;
}
$messages = $item->get();
$message = array_shift($messages);
$item->set($messages);
$this->cachePool->save($item);
return $message;
}
public function remove(Uuid $id): void
{
$this->cachePool->deleteItem($this->getCacheKey($id));
}
private function getCacheKey(Uuid $id): string
{
return 'message_'.$id->toRfc4122();
}
}

View file

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\Transport\Sse;
use Symfony\Component\Uid\Uuid;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
interface StoreInterface
{
public function push(Uuid $id, string $message): void;
public function pop(Uuid $id): ?string;
public function remove(Uuid $id): void;
}

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\Transport\Sse;
use Mcp\Server\TransportInterface;
use Symfony\Component\Uid\Uuid;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class StreamTransport implements TransportInterface
{
public function __construct(
private readonly string $messageEndpoint,
private readonly StoreInterface $store,
private readonly Uuid $id,
) {
}
public function initialize(): void
{
ignore_user_abort(true);
$this->flushEvent('endpoint', $this->messageEndpoint);
}
public function isConnected(): bool
{
return 0 === connection_aborted();
}
public function receive(): \Generator
{
yield $this->store->pop($this->id);
}
public function send(string $data): void
{
$this->flushEvent('message', $data);
}
public function close(): void
{
$this->store->remove($this->id);
}
private function flushEvent(string $event, string $data): void
{
echo \sprintf('event: %s', $event).\PHP_EOL;
echo \sprintf('data: %s', $data).\PHP_EOL;
echo \PHP_EOL;
if (false !== ob_get_length()) {
ob_flush();
}
flush();
}
}

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\Transport\Stdio;
use Mcp\Server\TransportInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Heavily inspired by https://jolicode.com/blog/mcp-the-open-protocol-that-turns-llm-chatbots-into-intelligent-agents.
*/
final class SymfonyConsoleTransport implements TransportInterface
{
private string $buffer = '';
public function __construct(
private readonly InputInterface $input,
private readonly OutputInterface $output,
) {
}
public function initialize(): void
{
}
public function isConnected(): bool
{
return true;
}
public function receive(): \Generator
{
$stream = $this->input instanceof StreamableInputInterface ? $this->input->getStream() ?? \STDIN : \STDIN;
$line = fgets($stream);
if (false === $line) {
return;
}
$this->buffer .= \STDIN === $stream ? rtrim($line).\PHP_EOL : $line;
if (str_contains($this->buffer, \PHP_EOL)) {
$lines = explode(\PHP_EOL, $this->buffer);
$this->buffer = array_pop($lines);
yield from $lines;
}
}
public function send(string $data): void
{
$this->output->writeln($data);
}
public function close(): void
{
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
interface TransportInterface
{
public function initialize(): void;
public function isConnected(): bool;
public function receive(): \Generator;
public function send(string $data): void;
public function close(): void;
}