Adopt Schema component in server handlers
This commit is contained in:
parent
6025f890f9
commit
09707b4798
45 changed files with 1516 additions and 2424 deletions
|
|
@ -1,29 +0,0 @@
|
|||
<?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\Schema;
|
||||
|
||||
/**
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
interface Constants
|
||||
{
|
||||
public const LATEST_PROTOCOL_VERSION = '2025-03-26';
|
||||
public const JSONRPC_VERSION = '2.0';
|
||||
|
||||
public const PARSE_ERROR = -32700;
|
||||
public const INVALID_REQUEST = -32600;
|
||||
public const METHOD_NOT_FOUND = -32601;
|
||||
public const INVALID_PARAMS = -32602;
|
||||
public const INTERNAL_ERROR = -32603;
|
||||
|
||||
public const SERVER_ERROR = -32000;
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ class TextResourceContents extends ResourceContents
|
|||
{
|
||||
return [
|
||||
'text' => $this->text,
|
||||
...$this->jsonSerialize(),
|
||||
...parent::jsonSerialize(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,164 +0,0 @@
|
|||
<?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\Schema\JsonRpc;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A JSON-RPC batch request, as described in https://www.jsonrpc.org/specification#batch.
|
||||
*
|
||||
* @phpstan-import-type RequestData from Request
|
||||
* @phpstan-import-type NotificationData from Notification
|
||||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class BatchRequest implements MessageInterface, \Countable
|
||||
{
|
||||
/**
|
||||
* Create a new JSON-RPC 2.0 batch of requests/notifications.
|
||||
*
|
||||
* @param array<Request|Notification> $items
|
||||
*/
|
||||
public function __construct(public readonly array $items)
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
if (!($item instanceof Request || $item instanceof Notification)) {
|
||||
throw new InvalidArgumentException('All items in BatchRequest must be instances of Request or Notification.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<RequestData|NotificationData> $data
|
||||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
if (empty($data)) {
|
||||
throw new InvalidArgumentException('BatchRequest data array must not be empty.');
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($data as $itemData) {
|
||||
if (!\is_array($itemData)) {
|
||||
throw new InvalidArgumentException('BatchRequest item data must be an array.');
|
||||
}
|
||||
if (isset($itemData['id'])) {
|
||||
$items[] = Request::fromArray($itemData);
|
||||
} elseif (isset($itemData['method'])) {
|
||||
$items[] = Notification::fromArray($itemData);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid item in BatchRequest data: missing "method" or "id".');
|
||||
}
|
||||
}
|
||||
|
||||
return new self($items);
|
||||
}
|
||||
|
||||
public function getId(): string|int
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
if ($item instanceof Request) {
|
||||
return $item->getId();
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this batch has any requests.
|
||||
*/
|
||||
public function hasRequests(): bool
|
||||
{
|
||||
$hasRequests = false;
|
||||
foreach ($this->items as $item) {
|
||||
if ($item instanceof Request) {
|
||||
$hasRequests = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $hasRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this batch has any notifications.
|
||||
*/
|
||||
public function hasNotifications(): bool
|
||||
{
|
||||
$hasNotifications = false;
|
||||
foreach ($this->items as $item) {
|
||||
if ($item instanceof Notification) {
|
||||
$hasNotifications = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $hasNotifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all requests in this batch.
|
||||
*
|
||||
* @return array<Request>
|
||||
*/
|
||||
public function getRequests(): array
|
||||
{
|
||||
return array_filter($this->items, fn ($item) => $item instanceof Request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all notifications in this batch.
|
||||
*
|
||||
* @return array<Notification>
|
||||
*/
|
||||
public function getNotifications(): array
|
||||
{
|
||||
return array_filter($this->items, fn ($item) => $item instanceof Notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all elements in this batch.
|
||||
*
|
||||
* @return array<Request|Notification>
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the total number of elements in this batch.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->items);
|
||||
}
|
||||
|
||||
public function nRequests(): int
|
||||
{
|
||||
return \count($this->getRequests());
|
||||
}
|
||||
|
||||
public function nNotifications(): int
|
||||
{
|
||||
return \count($this->getNotifications());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Request|Notification>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
<?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\Schema\JsonRpc;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A JSON-RPC batch response, as described in https://www.jsonrpc.org/specification#batch.
|
||||
*
|
||||
* @phpstan-import-type ResponseData from Response
|
||||
* @phpstan-import-type ErrorData from Error
|
||||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class BatchResponse implements MessageInterface, \Countable
|
||||
{
|
||||
/**
|
||||
* @param array<Response|Error> $items the individual responses/errors in this batch
|
||||
*/
|
||||
public function __construct(
|
||||
public array $items,
|
||||
) {
|
||||
foreach ($items as $item) {
|
||||
if (!($item instanceof Response || $item instanceof Error)) {
|
||||
throw new InvalidArgumentException('All items in BatchResponse must be instances of Response or Error.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<ErrorData|ResponseData> $data
|
||||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
if (empty($data)) {
|
||||
throw new InvalidArgumentException('BatchResponse data array must not be empty.');
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($data as $itemData) {
|
||||
if (!\is_array($itemData)) {
|
||||
throw new InvalidArgumentException('BatchResponse item data must be an array.');
|
||||
}
|
||||
if (isset($itemData['id'])) {
|
||||
$items[] = Response::fromArray($itemData);
|
||||
} elseif (isset($itemData['error'])) {
|
||||
$items[] = Error::fromArray($itemData);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid item in BatchResponse data: missing "id" or "error".');
|
||||
}
|
||||
}
|
||||
|
||||
return new self($items);
|
||||
}
|
||||
|
||||
public function getId(): string|int
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
if ($item instanceof Response) {
|
||||
return $item->getId();
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('BatchResponse does not contain any Response items with an "id".');
|
||||
}
|
||||
|
||||
public function hasResponses(): bool
|
||||
{
|
||||
$hasResponses = false;
|
||||
foreach ($this->items as $item) {
|
||||
if ($item instanceof Response) {
|
||||
$hasResponses = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $hasResponses;
|
||||
}
|
||||
|
||||
public function hasErrors(): bool
|
||||
{
|
||||
$hasErrors = false;
|
||||
foreach ($this->items as $item) {
|
||||
if ($item instanceof Error) {
|
||||
$hasErrors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $hasErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response[]
|
||||
*/
|
||||
public function getResponses(): array
|
||||
{
|
||||
return array_filter($this->items, fn ($item) => $item instanceof Response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Error[]
|
||||
*/
|
||||
public function getErrors(): array
|
||||
{
|
||||
return array_filter($this->items, fn ($item) => $item instanceof Error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Error[]|Response[]
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Error[]|Response[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@
|
|||
namespace Mcp\Schema\JsonRpc;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\Constants;
|
||||
|
||||
/**
|
||||
* A response to a request that indicates an error occurred.
|
||||
|
|
@ -29,6 +28,14 @@ use Mcp\Schema\Constants;
|
|||
*/
|
||||
class Error implements MessageInterface
|
||||
{
|
||||
public const PARSE_ERROR = -32700;
|
||||
public const INVALID_REQUEST = -32600;
|
||||
public const METHOD_NOT_FOUND = -32601;
|
||||
public const INVALID_PARAMS = -32602;
|
||||
public const INTERNAL_ERROR = -32603;
|
||||
public const SERVER_ERROR = -32000;
|
||||
public const RESOURCE_NOT_FOUND = -32002;
|
||||
|
||||
/**
|
||||
* @param int $code the error type that occurred
|
||||
* @param string $message a short description of the error
|
||||
|
|
@ -47,7 +54,7 @@ class Error implements MessageInterface
|
|||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
if (!isset($data['jsonrpc']) || Constants::JSONRPC_VERSION !== $data['jsonrpc']) {
|
||||
if (!isset($data['jsonrpc']) || MessageInterface::JSONRPC_VERSION !== $data['jsonrpc']) {
|
||||
throw new InvalidArgumentException('Invalid or missing "jsonrpc" in Error data.');
|
||||
}
|
||||
if (!isset($data['id']) || !\is_string($data['id'])) {
|
||||
|
|
@ -65,32 +72,32 @@ class Error implements MessageInterface
|
|||
|
||||
public static function forParseError(string $message, string|int $id = ''): self
|
||||
{
|
||||
return new self($id, Constants::PARSE_ERROR, $message);
|
||||
return new self($id, self::PARSE_ERROR, $message);
|
||||
}
|
||||
|
||||
public static function forInvalidRequest(string $message, string|int $id = ''): self
|
||||
{
|
||||
return new self($id, Constants::INVALID_REQUEST, $message);
|
||||
return new self($id, self::INVALID_REQUEST, $message);
|
||||
}
|
||||
|
||||
public static function forMethodNotFound(string $message, string|int $id = ''): self
|
||||
{
|
||||
return new self($id, Constants::METHOD_NOT_FOUND, $message);
|
||||
return new self($id, self::METHOD_NOT_FOUND, $message);
|
||||
}
|
||||
|
||||
public static function forInvalidParams(string $message, string|int $id = ''): self
|
||||
{
|
||||
return new self($id, Constants::INVALID_PARAMS, $message);
|
||||
return new self($id, self::INVALID_PARAMS, $message);
|
||||
}
|
||||
|
||||
public static function forInternalError(string $message, string|int $id = ''): self
|
||||
{
|
||||
return new self($id, Constants::INTERNAL_ERROR, $message);
|
||||
return new self($id, self::INTERNAL_ERROR, $message);
|
||||
}
|
||||
|
||||
public static function forServerError(string $message, string|int $id = ''): self
|
||||
{
|
||||
return new self($id, Constants::SERVER_ERROR, $message);
|
||||
return new self($id, self::SERVER_ERROR, $message);
|
||||
}
|
||||
|
||||
public function getId(): string|int
|
||||
|
|
@ -121,7 +128,7 @@ class Error implements MessageInterface
|
|||
}
|
||||
|
||||
return [
|
||||
'jsonrpc' => Constants::JSONRPC_VERSION,
|
||||
'jsonrpc' => MessageInterface::JSONRPC_VERSION,
|
||||
'id' => $this->id,
|
||||
'error' => $error,
|
||||
];
|
||||
|
|
|
|||
29
src/Schema/JsonRpc/HasMethodInterface.php
Normal file
29
src/Schema/JsonRpc/HasMethodInterface.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Schema\JsonRpc;
|
||||
|
||||
/**
|
||||
* Interface for all incoming JSON-RPC messages that should be processed by a handler, and are expected to have a method.
|
||||
*
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
interface HasMethodInterface
|
||||
{
|
||||
public static function getMethod(): string;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function fromArray(array $data): self;
|
||||
}
|
||||
|
|
@ -18,4 +18,5 @@ namespace Mcp\Schema\JsonRpc;
|
|||
*/
|
||||
interface MessageInterface extends \JsonSerializable
|
||||
{
|
||||
public const JSONRPC_VERSION = '2.0';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
namespace Mcp\Schema\JsonRpc;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\Constants;
|
||||
|
||||
/**
|
||||
* @phpstan-type NotificationData array{
|
||||
|
|
@ -23,17 +22,14 @@ use Mcp\Schema\Constants;
|
|||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class Notification implements MessageInterface
|
||||
abstract class Notification implements HasMethodInterface, MessageInterface
|
||||
{
|
||||
/**
|
||||
* @param string $method the name of the method to be invoked
|
||||
* @param ?array<string, mixed> $params parameters for the method
|
||||
* @var array<string, mixed>|null
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $method,
|
||||
public readonly ?array $params = null,
|
||||
) {
|
||||
}
|
||||
protected ?array $meta;
|
||||
|
||||
abstract public static function getMethod(): string;
|
||||
|
||||
/**
|
||||
* @param NotificationData $data
|
||||
|
|
@ -51,16 +47,19 @@ class Notification implements MessageInterface
|
|||
throw new InvalidArgumentException('"params" for Notification must be an array/object or null.');
|
||||
}
|
||||
|
||||
return new self($data['method'], $params);
|
||||
$notification = static::fromParams($params);
|
||||
|
||||
if (isset($data['params']['_meta'])) {
|
||||
$notification->meta = $data['params']['_meta'];
|
||||
}
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null
|
||||
* @param array<string, mixed>|null $params
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
abstract protected static function fromParams(?array $params): self;
|
||||
|
||||
/**
|
||||
* @return NotificationData
|
||||
|
|
@ -68,13 +67,22 @@ class Notification implements MessageInterface
|
|||
public function jsonSerialize(): array
|
||||
{
|
||||
$array = [
|
||||
'jsonrpc' => Constants::JSONRPC_VERSION,
|
||||
'method' => $this->method,
|
||||
'jsonrpc' => MessageInterface::JSONRPC_VERSION,
|
||||
'method' => static::getMethod(),
|
||||
];
|
||||
if (null !== $this->params) {
|
||||
$array['params'] = $this->params;
|
||||
if (null !== $params = $this->getParams()) {
|
||||
$array['params'] = $params;
|
||||
}
|
||||
|
||||
if (null !== $this->meta && !isset($params['meta'])) {
|
||||
$array['params']['_meta'] = $this->meta;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
abstract protected function getParams(): ?array;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
<?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\Schema\JsonRpc;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\Constants;
|
||||
|
||||
/**
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class Parser
|
||||
{
|
||||
/**
|
||||
* Parses a raw JSON string into a JSON-RPC Message object (Request, Notification, Response, Error, or Batch variants).
|
||||
*
|
||||
* This method determines if the incoming message is a request-like message (Request, Notification, BatchRequest)
|
||||
* or a response-like message (Response, Error, BatchResponse) based on the presence of 'method' vs 'result'/'error'.
|
||||
*
|
||||
* @param string $json the raw JSON string to parse
|
||||
*
|
||||
* @return MessageInterface a specific instance of Request, Notification, Response, Error, BatchRequest, or BatchResponse
|
||||
*
|
||||
* @throws \JsonException if the string is not valid JSON
|
||||
* @throws InvalidArgumentException if the JSON structure does not conform to a recognizable JSON-RPC message type
|
||||
*/
|
||||
public static function parse(string $json): MessageInterface
|
||||
{
|
||||
$data = json_decode($json, true, 512, \JSON_THROW_ON_ERROR);
|
||||
|
||||
if (!\is_array($data)) {
|
||||
throw new InvalidArgumentException('Invalid JSON-RPC message: Root must be an object or array.');
|
||||
}
|
||||
|
||||
if (array_is_list($data) && !empty($data)) {
|
||||
$firstItem = $data[0];
|
||||
if (!\is_array($firstItem)) {
|
||||
throw new InvalidArgumentException('Invalid JSON-RPC batch: Items must be objects.');
|
||||
}
|
||||
|
||||
if (isset($firstItem['method'])) {
|
||||
return BatchRequest::fromArray($data);
|
||||
} elseif (isset($firstItem['id']) && (isset($firstItem['result']) || isset($firstItem['error']))) {
|
||||
return BatchResponse::fromArray($data);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid JSON-RPC batch: Items are not recognizable requests or responses.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($data['jsonrpc']) || Constants::JSONRPC_VERSION !== $data['jsonrpc']) {
|
||||
throw new InvalidArgumentException('Invalid or missing "jsonrpc" version. Must be "'.Constants::JSONRPC_VERSION.'".');
|
||||
}
|
||||
|
||||
if (isset($data['method'])) {
|
||||
if (isset($data['id'])) {
|
||||
return Request::fromArray($data);
|
||||
} else {
|
||||
return Notification::fromArray($data);
|
||||
}
|
||||
} elseif (isset($data['id'])) {
|
||||
if (\array_key_exists('result', $data)) {
|
||||
return Response::fromArray($data);
|
||||
} elseif (isset($data['error'])) {
|
||||
return Error::fromArray($data);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid JSON-RPC response/error: Missing "result" or "error" field for message with "id".');
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unrecognized JSON-RPC message structure.');
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@
|
|||
namespace Mcp\Schema\JsonRpc;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\Constants;
|
||||
|
||||
/**
|
||||
* @phpstan-type RequestData array{
|
||||
|
|
@ -24,31 +23,22 @@ use Mcp\Schema\Constants;
|
|||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class Request implements MessageInterface
|
||||
abstract class Request implements HasMethodInterface, MessageInterface
|
||||
{
|
||||
protected string|int $id;
|
||||
/**
|
||||
* @param string|int $id a unique identifier for the request
|
||||
* @param string $method the name of the method to be invoked
|
||||
* @param array<string, mixed>|null $params parameters for the method
|
||||
* @var array<string, mixed>|null
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string|int $id,
|
||||
public readonly string $method,
|
||||
public readonly ?array $params = null,
|
||||
) {
|
||||
}
|
||||
protected ?array $meta;
|
||||
|
||||
public function getId(): string|int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
abstract public static function getMethod(): string;
|
||||
|
||||
/**
|
||||
* @param RequestData $data
|
||||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
if (($data['jsonrpc'] ?? null) !== Constants::JSONRPC_VERSION) {
|
||||
if (($data['jsonrpc'] ?? null) !== MessageInterface::JSONRPC_VERSION) {
|
||||
throw new InvalidArgumentException('Invalid or missing "jsonrpc" version for Request.');
|
||||
}
|
||||
if (!isset($data['id']) || !\is_string($data['id']) && !\is_int($data['id'])) {
|
||||
|
|
@ -65,7 +55,24 @@ class Request implements MessageInterface
|
|||
throw new InvalidArgumentException('"params" for Request must be an array/object or null.');
|
||||
}
|
||||
|
||||
return new self($data['id'], $data['method'], $params);
|
||||
$request = static::fromParams($params);
|
||||
$request->id = $data['id'];
|
||||
|
||||
if (isset($data['params']['_meta'])) {
|
||||
$request->meta = $data['params']['_meta'];
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $params
|
||||
*/
|
||||
abstract protected static function fromParams(?array $params): self;
|
||||
|
||||
public function getId(): string|int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -74,14 +81,23 @@ class Request implements MessageInterface
|
|||
public function jsonSerialize(): array
|
||||
{
|
||||
$array = [
|
||||
'jsonrpc' => Constants::JSONRPC_VERSION,
|
||||
'jsonrpc' => MessageInterface::JSONRPC_VERSION,
|
||||
'id' => $this->id,
|
||||
'method' => $this->method,
|
||||
'method' => static::getMethod(),
|
||||
];
|
||||
if (null !== $this->params) {
|
||||
$array['params'] = $this->params;
|
||||
if (null !== $params = $this->getParams()) {
|
||||
$array['params'] = $params;
|
||||
}
|
||||
|
||||
if (null !== $this->meta && !isset($params['meta'])) {
|
||||
$array['params']['_meta'] = $this->meta;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
abstract protected function getParams(): ?array;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
namespace Mcp\Schema\JsonRpc;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\Constants;
|
||||
|
||||
/**
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
|
|
@ -45,7 +44,7 @@ class Response implements MessageInterface
|
|||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
if (($data['jsonrpc'] ?? null) !== Constants::JSONRPC_VERSION) {
|
||||
if (($data['jsonrpc'] ?? null) !== MessageInterface::JSONRPC_VERSION) {
|
||||
throw new InvalidArgumentException('Invalid or missing "jsonrpc" version for Response.');
|
||||
}
|
||||
if (!isset($data['id'])) {
|
||||
|
|
@ -71,7 +70,7 @@ class Response implements MessageInterface
|
|||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'jsonrpc' => Constants::JSONRPC_VERSION,
|
||||
'jsonrpc' => MessageInterface::JSONRPC_VERSION,
|
||||
'id' => $this->id,
|
||||
'result' => $this->result,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ namespace Mcp\Schema;
|
|||
* up to the client to decide how to interpret these preferences and how to
|
||||
* balance them against other considerations.
|
||||
*
|
||||
* @phpstan-type ModelPreferencesData array{
|
||||
* hints?: ModelHint[],
|
||||
* costPriority?: float,
|
||||
* speedPriority?: float,
|
||||
* intelligencePriority?: float,
|
||||
* }
|
||||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class ModelPreferences implements \JsonSerializable
|
||||
|
|
@ -50,12 +57,20 @@ class ModelPreferences implements \JsonSerializable
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* hints?: ModelHint[],
|
||||
* costPriority?: float,
|
||||
* speedPriority?: float,
|
||||
* intelligencePriority?: float,
|
||||
* }
|
||||
* @param ModelPreferencesData $preferences
|
||||
*/
|
||||
public static function fromArray(array $preferences): self
|
||||
{
|
||||
return new self(
|
||||
$preferences['hints'] ?? null,
|
||||
$preferences['costPriority'] ?? null,
|
||||
$preferences['speedPriority'] ?? null,
|
||||
$preferences['intelligencePriority'] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ModelPreferencesData
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,40 +28,37 @@ use Mcp\Schema\JsonRpc\Notification;
|
|||
class CancelledNotification extends Notification
|
||||
{
|
||||
/**
|
||||
* @param string $requestId The ID of the request that is being cancelled. This MUST correspond to the ID of a request previously issued in the same direction.
|
||||
* @param ?string $reason An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.
|
||||
* @param ?array<string, mixed> $_meta additional metadata about the notification
|
||||
* @param string|int $requestId The ID of the request that is being cancelled. This MUST correspond to the ID of a request previously issued in the same direction.
|
||||
* @param ?string $reason An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $requestId,
|
||||
public readonly string|int $requestId,
|
||||
public readonly ?string $reason = null,
|
||||
public readonly ?array $_meta = null,
|
||||
) {
|
||||
$params = [
|
||||
'requestId' => $this->requestId,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getMethod(): string
|
||||
{
|
||||
return 'notifications/cancelled';
|
||||
}
|
||||
|
||||
protected static function fromParams(?array $params): Notification
|
||||
{
|
||||
if (null === $params || !isset($params['requestId']) || (!\is_string($params['requestId']) && !\is_int($params['requestId']))) {
|
||||
throw new InvalidArgumentException('Invalid or missing "requestId" parameter for "notifications/cancelled" notification.');
|
||||
}
|
||||
|
||||
return new self($params['requestId'], $params['reason'] ?? null);
|
||||
}
|
||||
|
||||
protected function getParams(): ?array
|
||||
{
|
||||
$params = ['requestId' => $this->requestId];
|
||||
|
||||
if (null !== $this->reason) {
|
||||
$params['reason'] = $this->reason;
|
||||
}
|
||||
if (null !== $_meta) {
|
||||
$params['_meta'] = $_meta;
|
||||
}
|
||||
|
||||
parent::__construct('notifications/cancelled', $params);
|
||||
}
|
||||
|
||||
public static function fromNotification(Notification $notification): self
|
||||
{
|
||||
if ('notifications/cancelled' !== $notification->method) {
|
||||
throw new InvalidArgumentException('Notification is not a notifications/cancelled notification');
|
||||
}
|
||||
|
||||
$params = $notification->params;
|
||||
|
||||
if (!isset($params['requestId']) || !\is_string($params['requestId'])) {
|
||||
throw new InvalidArgumentException('Missing or invalid requestId parameter for notifications/cancelled notification');
|
||||
}
|
||||
|
||||
return new self($params['requestId'], $params['reason'] ?? null, $params['_meta'] ?? null);
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
namespace Mcp\Schema\Notification;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\JsonRpc\Notification;
|
||||
|
||||
/**
|
||||
|
|
@ -21,27 +20,18 @@ use Mcp\Schema\JsonRpc\Notification;
|
|||
*/
|
||||
class InitializedNotification extends Notification
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed>|null $_meta
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly ?array $_meta = null,
|
||||
) {
|
||||
$params = [];
|
||||
|
||||
if (null !== $_meta) {
|
||||
$params['_meta'] = $_meta;
|
||||
}
|
||||
|
||||
parent::__construct('notifications/initialized', $params);
|
||||
public static function getMethod(): string
|
||||
{
|
||||
return 'notifications/initialized';
|
||||
}
|
||||
|
||||
public static function fromNotification(Notification $notification): self
|
||||
public static function fromParams(?array $params): self
|
||||
{
|
||||
if ('notifications/initialized' !== $notification->method) {
|
||||
throw new InvalidArgumentException('Notification is not a notifications/initialized notification');
|
||||
}
|
||||
return new self();
|
||||
}
|
||||
|
||||
return new self($notification->params['_meta'] ?? null);
|
||||
protected function getParams(): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,51 +21,49 @@ use Mcp\Schema\JsonRpc\Notification;
|
|||
class LoggingMessageNotification extends Notification
|
||||
{
|
||||
/**
|
||||
* @param LoggingLevel $level the severity of this log message
|
||||
* @param mixed $data The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here.
|
||||
* @param string $logger an optional name of the logger issuing this message
|
||||
* @param ?array<string, mixed> $_meta optional metadata to include in the notification
|
||||
* @param LoggingLevel $level the severity of this log message
|
||||
* @param mixed $data The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here.
|
||||
* @param ?string $logger an optional name of the logger issuing this message
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly LoggingLevel $level,
|
||||
public readonly mixed $data,
|
||||
public readonly ?string $logger = null,
|
||||
public readonly ?array $_meta = null,
|
||||
) {
|
||||
$params = [
|
||||
'level' => $level->value,
|
||||
'data' => \is_string($data) ? $data : json_encode($data),
|
||||
];
|
||||
|
||||
if (null !== $logger) {
|
||||
$params['logger'] = $logger;
|
||||
}
|
||||
|
||||
if (null !== $_meta) {
|
||||
$params['_meta'] = $_meta;
|
||||
}
|
||||
|
||||
parent::__construct('notifications/message', $params);
|
||||
}
|
||||
|
||||
public static function fromNotification(Notification $notification): self
|
||||
public static function getMethod(): string
|
||||
{
|
||||
if ('notifications/message' !== $notification->method) {
|
||||
throw new InvalidArgumentException('Notification is not a notifications/message notification');
|
||||
}
|
||||
|
||||
$params = $notification->params;
|
||||
return 'notifications/message';
|
||||
}
|
||||
|
||||
protected static function fromParams(?array $params): Notification
|
||||
{
|
||||
if (!isset($params['level']) || !\is_string($params['level'])) {
|
||||
throw new InvalidArgumentException('Missing or invalid level parameter for notifications/message notification');
|
||||
throw new InvalidArgumentException('Missing or invalid "level" parameter for "notifications/message" notification.');
|
||||
}
|
||||
|
||||
if (!isset($params['data'])) {
|
||||
throw new InvalidArgumentException('Missing data parameter for notifications/message notification');
|
||||
throw new InvalidArgumentException('Missing "data" parameter for "notifications/message" notification.');
|
||||
}
|
||||
|
||||
$level = LoggingLevel::from($params['level']);
|
||||
$data = \is_string($params['data']) ? $params['data'] : json_encode($params['data']);
|
||||
|
||||
return new self($level, $params['data'], $params['logger'] ?? null, $params['_meta'] ?? null);
|
||||
return new self($level, $data, $params['logger'] ?? null);
|
||||
}
|
||||
|
||||
protected function getParams(): ?array
|
||||
{
|
||||
$params = [
|
||||
'level' => $this->level->value,
|
||||
'data' => $this->data,
|
||||
];
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$params['logger'] = $this->logger;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,45 +22,59 @@ use Mcp\Schema\JsonRpc\Notification;
|
|||
class ProgressNotification extends Notification
|
||||
{
|
||||
/**
|
||||
* @param string|int $progressToken the progress token which was given in the initial request, used to
|
||||
* associate this notification with the request that is proceeding
|
||||
* @param float $progress The progress thus far. This should increase every time progress is
|
||||
* made, even if the total is unknown.
|
||||
* @param ?float $total total number of items to process (or total progress required), if known
|
||||
* @param ?string $message an optional message describing the current progress
|
||||
* @param ?array<string, mixed> $_meta optional metadata to include in the notification
|
||||
* @param string|int $progressToken the progress token which was given in the initial request, used to
|
||||
* associate this notification with the request that is proceeding
|
||||
* @param float $progress The progress thus far. This should increase every time progress is
|
||||
* made, even if the total is unknown.
|
||||
* @param ?float $total total number of items to process (or total progress required), if known
|
||||
* @param ?string $message an optional message describing the current progress
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string|int $progressToken,
|
||||
public readonly float $progress,
|
||||
public readonly ?float $total = null,
|
||||
public readonly ?string $message = null,
|
||||
public readonly ?array $_meta = null,
|
||||
) {
|
||||
$params = [];
|
||||
if (null !== $_meta) {
|
||||
$params['_meta'] = $_meta;
|
||||
}
|
||||
|
||||
parent::__construct('notifications/progress', $params);
|
||||
}
|
||||
|
||||
public static function fromNotification(Notification $notification): self
|
||||
public static function getMethod(): string
|
||||
{
|
||||
if ('notifications/progress' !== $notification->method) {
|
||||
throw new InvalidArgumentException('Notification is not a notifications/progress notification');
|
||||
}
|
||||
|
||||
$params = $notification->params;
|
||||
return 'notifications/progress';
|
||||
}
|
||||
|
||||
protected static function fromParams(?array $params): Notification
|
||||
{
|
||||
if (!isset($params['progressToken']) || !\is_string($params['progressToken'])) {
|
||||
throw new InvalidArgumentException('Missing or invalid progressToken parameter for notifications/progress notification');
|
||||
throw new InvalidArgumentException('Missing or invalid "progressToken" parameter for "notifications/progress" notification.');
|
||||
}
|
||||
|
||||
if (!isset($params['progress']) || !\is_float($params['progress'])) {
|
||||
throw new InvalidArgumentException('Missing or invalid progress parameter for notifications/progress notification');
|
||||
throw new InvalidArgumentException('Missing or invalid "progress" parameter for "notifications/progress" notification.');
|
||||
}
|
||||
|
||||
return new self($params['progressToken'], $params['progress'], $params['total'] ?? null, $params['message'] ?? null, $params['_meta'] ?? null);
|
||||
return new self(
|
||||
$params['progressToken'],
|
||||
$params['progress'],
|
||||
$params['total'] ?? null,
|
||||
$params['message'] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
protected function getParams(): ?array
|
||||
{
|
||||
$params = [
|
||||
'progressToken' => $this->progressToken,
|
||||
'progress' => $this->progress,
|
||||
];
|
||||
|
||||
if (null !== $this->total) {
|
||||
$params['total'] = $this->total;
|
||||
}
|
||||
|
||||
if (null !== $this->message) {
|
||||
$params['message'] = $this->message;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
namespace Mcp\Schema\Notification;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\JsonRpc\Notification;
|
||||
|
||||
/**
|
||||
|
|
@ -21,26 +20,18 @@ use Mcp\Schema\JsonRpc\Notification;
|
|||
*/
|
||||
class PromptListChangedNotification extends Notification
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed>|null $_meta
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly ?array $_meta = null,
|
||||
) {
|
||||
$params = [];
|
||||
if (null !== $_meta) {
|
||||
$params['_meta'] = $_meta;
|
||||
}
|
||||
|
||||
parent::__construct('notifications/prompts/list_changed', $params);
|
||||
public static function getMethod(): string
|
||||
{
|
||||
return 'notifications/prompts/list_changed';
|
||||
}
|
||||
|
||||
public static function fromNotification(Notification $notification): self
|
||||
protected static function fromParams(?array $params): Notification
|
||||
{
|
||||
if ('notifications/prompts/list_changed' !== $notification->method) {
|
||||
throw new InvalidArgumentException('Notification is not a notifications/prompts/list_changed notification');
|
||||
}
|
||||
return new self();
|
||||
}
|
||||
|
||||
return new self($notification->params['_meta'] ?? null);
|
||||
protected function getParams(): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
namespace Mcp\Schema\Notification;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\JsonRpc\Notification;
|
||||
|
||||
/**
|
||||
|
|
@ -21,26 +20,18 @@ use Mcp\Schema\JsonRpc\Notification;
|
|||
*/
|
||||
class ResourceListChangedNotification extends Notification
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed>|null $_meta
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly ?array $_meta = null,
|
||||
) {
|
||||
$params = [];
|
||||
if (null !== $_meta) {
|
||||
$params['_meta'] = $_meta;
|
||||
}
|
||||
|
||||
parent::__construct('notifications/resources/list_changed', $params);
|
||||
public static function getMethod(): string
|
||||
{
|
||||
return 'notifications/resources/list_changed';
|
||||
}
|
||||
|
||||
public static function fromNotification(Notification $notification): self
|
||||
protected static function fromParams(?array $params): Notification
|
||||
{
|
||||
if ('notifications/resources/list_changed' !== $notification->method) {
|
||||
throw new InvalidArgumentException('Notification is not a notifications/resources/list_changed notification');
|
||||
}
|
||||
return new self();
|
||||
}
|
||||
|
||||
return new self($notification->params['_meta'] ?? null);
|
||||
protected function getParams(): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,33 +19,29 @@ use Mcp\Schema\JsonRpc\Notification;
|
|||
*/
|
||||
class ResourceUpdatedNotification extends Notification
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed>|null $_meta
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $uri,
|
||||
public readonly ?array $_meta = null,
|
||||
) {
|
||||
$params = ['uri' => $uri];
|
||||
if (null !== $_meta) {
|
||||
$params['_meta'] = $_meta;
|
||||
}
|
||||
|
||||
parent::__construct('notifications/resources/updated', $params);
|
||||
}
|
||||
|
||||
public static function fromNotification(Notification $notification): self
|
||||
public static function getMethod(): string
|
||||
{
|
||||
if ('notifications/resources/updated' !== $notification->method) {
|
||||
throw new InvalidArgumentException('Notification is not a notifications/resources/updated notification');
|
||||
return 'notifications/resources/updated';
|
||||
}
|
||||
|
||||
protected static function fromParams(?array $params): Notification
|
||||
{
|
||||
if (null === $params || !isset($params['uri']) || !\is_string($params['uri'])) {
|
||||
throw new InvalidArgumentException('Invalid or missing "uri" parameter for notifications/resources/updated notification.');
|
||||
}
|
||||
|
||||
$params = $notification->params;
|
||||
return new self($params['uri']);
|
||||
}
|
||||
|
||||
if (!isset($params['uri']) || !\is_string($params['uri'])) {
|
||||
throw new InvalidArgumentException('Missing or invalid uri parameter for notifications/resources/updated notification');
|
||||
}
|
||||
|
||||
return new self($params['uri'], $params['_meta'] ?? null);
|
||||
protected function getParams(): ?array
|
||||
{
|
||||
return [
|
||||
'uri' => $this->uri,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
namespace Mcp\Schema\Notification;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\JsonRpc\Notification;
|
||||
|
||||
/**
|
||||
|
|
@ -23,26 +22,18 @@ use Mcp\Schema\JsonRpc\Notification;
|
|||
*/
|
||||
class RootsListChangedNotification extends Notification
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed>|null $_meta
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly ?array $_meta = null,
|
||||
) {
|
||||
$params = [];
|
||||
if (null !== $_meta) {
|
||||
$params['_meta'] = $_meta;
|
||||
}
|
||||
|
||||
parent::__construct('notifications/roots/list_changed', $params);
|
||||
public static function getMethod(): string
|
||||
{
|
||||
return 'notifications/roots/list_changed';
|
||||
}
|
||||
|
||||
public static function fromNotification(Notification $notification): self
|
||||
protected static function fromParams(?array $params): Notification
|
||||
{
|
||||
if ('notifications/roots/list_changed' !== $notification->method) {
|
||||
throw new InvalidArgumentException('Notification is not a notifications/roots/list_changed notification');
|
||||
}
|
||||
return new self();
|
||||
}
|
||||
|
||||
return new self($notification->params['_meta'] ?? null);
|
||||
protected function getParams(): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue