Roll out meta properties in attributes and more schema classes (#129)

This commit is contained in:
Kaipi Yann 2025-11-08 13:58:26 +01:00 • committed by GitHub
commit d3a07912b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 178 additions and 88 deletions

View file

@ -19,7 +19,8 @@ use Mcp\Exception\InvalidArgumentException;
* @phpstan-type BlobResourceContentsData array{
* uri: string,
* mimeType?: string|null,
* blob: string
* blob: string,
* _meta?: array<string, mixed>
* }
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
@ -27,16 +28,18 @@ use Mcp\Exception\InvalidArgumentException;
class BlobResourceContents extends ResourceContents
{
/**
* @param string $uri the URI of the resource or sub-resource
* @param string|null $mimeType the MIME type of the resource or sub-resource
* @param string $blob a base64-encoded string representing the binary data of the item
* @param string $uri the URI of the resource or sub-resource
* @param string|null $mimeType the MIME type of the resource or sub-resource
* @param string $blob a base64-encoded string representing the binary data of the item
* @param ?array<string, mixed> $meta Optional metadata
*/
public function __construct(
string $uri,
?string $mimeType,
public readonly string $blob,
?array $meta = null,
) {
parent::__construct($uri, $mimeType);
parent::__construct($uri, $mimeType, $meta);
}
/**
@ -51,25 +54,29 @@ class BlobResourceContents extends ResourceContents
throw new InvalidArgumentException('Missing or invalid "blob" for BlobResourceContents.');
}
return new self($data['uri'], $data['mimeType'] ?? null, $data['blob']);
return new self($data['uri'], $data['mimeType'] ?? null, $data['blob'], $data['_meta'] ?? null);
}
/**
* @param resource $stream
*/
public static function fromStream(string $uri, $stream, string $mimeType): self
* @param resource $stream
* @param ?array<string, mixed> $meta Optional metadata
* */
public static function fromStream(string $uri, $stream, string $mimeType, ?array $meta = null): self
{
$blob = stream_get_contents($stream);
return new self($uri, $mimeType, base64_encode($blob));
return new self($uri, $mimeType, base64_encode($blob), $meta);
}
public static function fromSplFileInfo(string $uri, \SplFileInfo $file, ?string $explicitMimeType = null): self
/**
* @param ?array<string, mixed> $meta Optional metadata
* */
public static function fromSplFileInfo(string $uri, \SplFileInfo $file, ?string $explicitMimeType = null, ?array $meta = null): self
{
$mimeType = $explicitMimeType ?? mime_content_type($file->getPathname());
$blob = file_get_contents($file->getPathname());
return new self($uri, $mimeType, base64_encode($blob));
return new self($uri, $mimeType, base64_encode($blob), $meta);
}
/**
@ -79,7 +86,7 @@ class BlobResourceContents extends ResourceContents
{
return [
'blob' => $this->blob,
...$this->jsonSerialize(),
...parent::jsonSerialize(),
];
}
}

View file

@ -16,7 +16,8 @@ namespace Mcp\Schema\Content;
*
* @phpstan-type ResourceContentsData = array{
* uri: string,
* mimeType?: string|null
* mimeType?: string|null,
* _meta?: array<string, mixed>
* }
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
@ -24,12 +25,14 @@ namespace Mcp\Schema\Content;
abstract class ResourceContents implements \JsonSerializable
{
/**
* @param string $uri the URI of the resource or sub-resource
* @param string|null $mimeType the MIME type of the resource or sub-resource
* @param string $uri the URI of the resource or sub-resource
* @param string|null $mimeType the MIME type of the resource or sub-resource
* @param ?array<string, mixed> $meta Optional metadata
*/
public function __construct(
public readonly string $uri,
public readonly ?string $mimeType = null,
public readonly ?array $meta = null,
) {
}
@ -43,6 +46,10 @@ abstract class ResourceContents implements \JsonSerializable
$data['mimeType'] = $this->mimeType;
}
if (null !== $this->meta) {
$data['_meta'] = $this->meta;
}
return $data;
}
}

View file

@ -19,7 +19,8 @@ use Mcp\Exception\InvalidArgumentException;
* @phpstan-type TextResourceContentsData array{
* uri: string,
* mimeType?: string|null,
* text: string
* text: string,
* _meta?: array<string, mixed>
* }
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
@ -27,16 +28,18 @@ use Mcp\Exception\InvalidArgumentException;
class TextResourceContents extends ResourceContents
{
/**
* @param string $uri the URI of the resource or sub-resource
* @param string|null $mimeType the MIME type of the resource or sub-resource
* @param string $text The text of the item. This must only be set if the item can actually be represented as text (not binary data).
* @param string $uri the URI of the resource or sub-resource
* @param string|null $mimeType the MIME type of the resource or sub-resource
* @param string $text The text of the item. This must only be set if the item can actually be represented as text (not binary data).
* @param ?array<string, mixed> $meta Optional metadata
*/
public function __construct(
string $uri,
?string $mimeType,
public readonly string $text,
?array $meta = null,
) {
parent::__construct($uri, $mimeType);
parent::__construct($uri, $mimeType, $meta);
}
/**
@ -51,7 +54,7 @@ class TextResourceContents extends ResourceContents
throw new InvalidArgumentException('Missing or invalid "text" for TextResourceContents.');
}
return new self($data['uri'], $data['mimeType'] ?? null, $data['text']);
return new self($data['uri'], $data['mimeType'] ?? null, $data['text'], $data['_meta'] ?? null);
}
/**

View file

@ -22,6 +22,7 @@ use Mcp\Exception\InvalidArgumentException;
* name: string,
* description?: string,
* arguments?: PromptArgumentData[],
* _meta?: array<string, mixed>
* }
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
@ -32,11 +33,13 @@ class Prompt implements \JsonSerializable
* @param string $name the name of the prompt or prompt template
* @param string|null $description an optional description of what this prompt provides
* @param PromptArgument[]|null $arguments A list of arguments for templating. Null if not a template.
* @param ?array<string, mixed> $meta Optional metadata
*/
public function __construct(
public readonly string $name,
public readonly ?string $description = null,
public readonly ?array $arguments = null,
public readonly ?array $meta = null,
) {
if (null !== $this->arguments) {
foreach ($this->arguments as $arg) {
@ -60,10 +63,15 @@ class Prompt implements \JsonSerializable
$arguments = array_map(fn (array $argData) => PromptArgument::fromArray($argData), $data['arguments']);
}
if (!empty($data['_meta']) && !\is_array($data['_meta'])) {
throw new InvalidArgumentException('Invalid "_meta" in Prompt data.');
}
return new self(
name: $data['name'],
description: $data['description'] ?? null,
arguments: $arguments
arguments: $arguments,
meta: isset($data['_meta']) ? $data['_meta'] : null
);
}
@ -71,7 +79,8 @@ class Prompt implements \JsonSerializable
* @return array{
* name: string,
* description?: string,
* arguments?: array<PromptArgument>
* arguments?: array<PromptArgument>,
* _meta?: array<string, mixed>
* }
*/
public function jsonSerialize(): array
@ -83,6 +92,9 @@ class Prompt implements \JsonSerializable
if (null !== $this->arguments) {
$data['arguments'] = $this->arguments;
}
if (null !== $this->meta) {
$data['_meta'] = $this->meta;
}
return $data;
}

View file

@ -25,6 +25,7 @@ use Mcp\Exception\InvalidArgumentException;
* mimeType?: string|null,
* annotations?: AnnotationsData|null,
* size?: int|null,
* _meta?: array<string, mixed>
* }
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
@ -43,14 +44,15 @@ class Resource implements \JsonSerializable
private const URI_PATTERN = '/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s]*$/';
/**
* @param string $uri the URI of this resource
* @param string $name A human-readable name for this resource. This can be used by clients to populate UI elements.
* @param string|null $description A description of what this resource represents. This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
* @param string|null $mimeType the MIME type of this resource, if known
* @param Annotations|null $annotations optional annotations for the client
* @param int|null $size The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
* @param string $uri the URI of this resource
* @param string $name A human-readable name for this resource. This can be used by clients to populate UI elements.
* @param string|null $description A description of what this resource represents. This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
* @param string|null $mimeType the MIME type of this resource, if known
* @param Annotations|null $annotations optional annotations for the client
* @param int|null $size The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
* @param ?array<string, mixed> $meta Optional metadata
*
* This can be used by Hosts to display file sizes and estimate context window usage.
* This can be used by Hosts to display file sizes and estimate context window usage
*/
public function __construct(
public readonly string $uri,
@ -59,6 +61,7 @@ class Resource implements \JsonSerializable
public readonly ?string $mimeType = null,
public readonly ?Annotations $annotations = null,
public readonly ?int $size = null,
public readonly ?array $meta = null,
) {
if (!preg_match(self::RESOURCE_NAME_PATTERN, $name)) {
throw new InvalidArgumentException('Invalid resource name: must contain only alphanumeric characters, underscores, and hyphens.');
@ -80,13 +83,18 @@ class Resource implements \JsonSerializable
throw new InvalidArgumentException('Invalid or missing "name" in Resource data.');
}
if (!empty($data['_meta']) && !\is_array($data['_meta'])) {
throw new InvalidArgumentException('Invalid "_meta" in Resource data.');
}
return new self(
uri: $data['uri'],
name: $data['name'],
description: $data['description'] ?? null,
mimeType: $data['mimeType'] ?? null,
annotations: isset($data['annotations']) ? Annotations::fromArray($data['annotations']) : null,
size: isset($data['size']) ? (int) $data['size'] : null
size: isset($data['size']) ? (int) $data['size'] : null,
meta: isset($data['_meta']) ? $data['_meta'] : null
);
}
@ -98,6 +106,7 @@ class Resource implements \JsonSerializable
* mimeType?: string,
* annotations?: Annotations,
* size?: int,
* _meta?: array<string, mixed>
* }
*/
public function jsonSerialize(): array
@ -118,6 +127,9 @@ class Resource implements \JsonSerializable
if (null !== $this->size) {
$data['size'] = $this->size;
}
if (null !== $this->meta) {
$data['_meta'] = $this->meta;
}
return $data;
}

View file

@ -24,6 +24,7 @@ use Mcp\Exception\InvalidArgumentException;
* description?: string|null,
* mimeType?: string|null,
* annotations?: AnnotationsData|null,
* _meta?: array<string, mixed>
* }
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
@ -42,11 +43,12 @@ class ResourceTemplate implements \JsonSerializable
private const URI_TEMPLATE_PATTERN = '/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/.*{[^{}]+}.*/';
/**
* @param string $uriTemplate a URI template (according to RFC 6570) that can be used to construct resource URIs
* @param string $name A human-readable name for the type of resource this template refers to. This can be used by clients to populate UI elements.
* @param string|null $description This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
* @param string|null $mimeType The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.
* @param Annotations|null $annotations optional annotations for the client
* @param string $uriTemplate a URI template (according to RFC 6570) that can be used to construct resource URIs
* @param string $name A human-readable name for the type of resource this template refers to. This can be used by clients to populate UI elements.
* @param string|null $description This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
* @param string|null $mimeType The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.
* @param Annotations|null $annotations optional annotations for the client
* @param ?array<string, mixed> $meta Optional metadata
*/
public function __construct(
public readonly string $uriTemplate,
@ -54,6 +56,7 @@ class ResourceTemplate implements \JsonSerializable
public readonly ?string $description = null,
public readonly ?string $mimeType = null,
public readonly ?Annotations $annotations = null,
public readonly ?array $meta = null,
) {
if (!preg_match(self::RESOURCE_NAME_PATTERN, $name)) {
throw new InvalidArgumentException('Invalid resource name: must contain only alphanumeric characters, underscores, and hyphens.');
@ -75,12 +78,17 @@ class ResourceTemplate implements \JsonSerializable
throw new InvalidArgumentException('Invalid or missing "name" in ResourceTemplate data.');
}
if (!empty($data['_meta']) && !\is_array($data['_meta'])) {
throw new InvalidArgumentException('Invalid "_meta" in ResourceTemplate data.');
}
return new self(
uriTemplate: $data['uriTemplate'],
name: $data['name'],
description: $data['description'] ?? null,
mimeType: $data['mimeType'] ?? null,
annotations: isset($data['annotations']) ? Annotations::fromArray($data['annotations']) : null
annotations: isset($data['annotations']) ? Annotations::fromArray($data['annotations']) : null,
meta: isset($data['_meta']) ? $data['_meta'] : null
);
}
@ -91,6 +99,7 @@ class ResourceTemplate implements \JsonSerializable
* description?: string,
* mimeType?: string,
* annotations?: Annotations,
* _meta?: array<string, mixed>
* }
*/
public function jsonSerialize(): array
@ -108,6 +117,9 @@ class ResourceTemplate implements \JsonSerializable
if (null !== $this->annotations) {
$data['annotations'] = $this->annotations;
}
if (null !== $this->meta) {
$data['_meta'] = $this->meta;
}
return $data;
}

View file

@ -28,6 +28,7 @@ use Mcp\Exception\InvalidArgumentException;
* inputSchema: ToolInputSchema,
* description?: string|null,
* annotations?: ToolAnnotationsData,
* _meta?: array<string, mixed>
* }
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
@ -35,18 +36,20 @@ use Mcp\Exception\InvalidArgumentException;
class Tool implements \JsonSerializable
{
/**
* @param string $name the name of the tool
* @param string|null $description A human-readable description of the tool.
* This can be used by clients to improve the LLM's understanding of
* available tools. It can be thought of like a "hint" to the model.
* @param ToolInputSchema $inputSchema a JSON Schema object (as a PHP array) defining the expected 'arguments' for the tool
* @param ToolAnnotations|null $annotations optional additional tool information
* @param string $name the name of the tool
* @param string|null $description A human-readable description of the tool.
* This can be used by clients to improve the LLM's understanding of
* available tools. It can be thought of like a "hint" to the model.
* @param ToolInputSchema $inputSchema a JSON Schema object (as a PHP array) defining the expected 'arguments' for the tool
* @param ToolAnnotations|null $annotations optional additional tool information
* @param ?array<string, mixed> $meta Optional metadata
*/
public function __construct(
public readonly string $name,
public readonly array $inputSchema,
public readonly ?string $description,
public readonly ?ToolAnnotations $annotations,
public readonly ?array $meta = null,
) {
if (!isset($inputSchema['type']) || 'object' !== $inputSchema['type']) {
throw new InvalidArgumentException('Tool inputSchema must be a JSON Schema of type "object".');
@ -75,7 +78,8 @@ class Tool implements \JsonSerializable
$data['name'],
$data['inputSchema'],
isset($data['description']) && \is_string($data['description']) ? $data['description'] : null,
isset($data['annotations']) && \is_array($data['annotations']) ? ToolAnnotations::fromArray($data['annotations']) : null
isset($data['annotations']) && \is_array($data['annotations']) ? ToolAnnotations::fromArray($data['annotations']) : null,
isset($data['_meta']) && \is_array($data['_meta']) ? $data['_meta'] : null
);
}
@ -85,6 +89,7 @@ class Tool implements \JsonSerializable
* inputSchema: ToolInputSchema,
* description?: string,
* annotations?: ToolAnnotations,
* _meta?: array<string, mixed>
* }
*/
public function jsonSerialize(): array
@ -99,6 +104,9 @@ class Tool implements \JsonSerializable
if (null !== $this->annotations) {
$data['annotations'] = $this->annotations;
}
if (null !== $this->meta) {
$data['_meta'] = $this->meta;
}
return $data;
}