Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion examples/schema-showcase/server.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@
require_once dirname(__DIR__).'/bootstrap.php';
chdir(__DIR__);

use Mcp\Schema\Icon;
use Mcp\Server;
use Mcp\Server\Session\FileSessionStore;

$server = Server::builder()
->setServerInfo('Schema Showcase', '1.0.0')
->setServerInfo(
'Schema Showcase',
'1.0.0',
'A showcase server demonstrating MCP schema capabilities.',
[new Icon('https://www.php.net/images/logos/php-logo-white.svg', 'image/svg+xml', ['any'])],
'https://github.com/modelcontextprotocol/php-sdk',
)
->setContainer(container())
->setLogger(logger())
->setSession(new FileSessionStore(__DIR__.'/sessions'))
Expand Down
5 changes: 3 additions & 2 deletions src/Schema/Enum/ProtocolVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@

/**
* Available protocol versions for MCP.
*
* @author Illia Vasylevskyi<[email protected]>
*/
enum ProtocolVersion: string
{
case V2024_11_05 = '2024-11-05';

case V2025_03_26 = '2025-03-26';

case V2025_06_18 = '2025-06-18';
case V2025_11_25 = '2025-11-25';
}
91 changes: 91 additions & 0 deletions src/Schema/Icon.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?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;

use Mcp\Exception\InvalidArgumentException;

/**
* A url pointing to an icon URL or a base64-encoded data URI.
*
* @phpstan-type IconData array{
* src: string,
* mimeType?: string,
* sizes?: string[],
* }
*
* @author Christopher Hertel <[email protected]>
*/
class Icon implements \JsonSerializable
{
/**
* @param string $src a standard URI pointing to an icon resource
* @param ?string $mimeType optional override if the server's MIME type is missing or generic
* @param ?string[] $sizes optional array of strings that specify sizes at which the icon can be used.
* Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for
* scalable formats like SVG.
*/
public function __construct(
public readonly string $src,
public readonly ?string $mimeType = null,
public readonly ?array $sizes = null,
) {
if (empty($src)) {
throw new InvalidArgumentException('Icon "src" must be a non-empty string.');
}
if (!preg_match('#^(https?://|data:)#', $src)) {
throw new InvalidArgumentException('Icon "src" must be a valid URL or data URI.');
}

if (null !== $sizes) {
foreach ($sizes as $size) {
if (!\is_string($size)) {
throw new InvalidArgumentException('Each size in "sizes" must be a string.');
}
if (!preg_match('/^(any|\d+x\d+)$/', $size)) {
throw new InvalidArgumentException(\sprintf('Invalid size format "%s" in "sizes". Expected "WxH" or "any".', $size));
}
}
}
}

/**
* @param IconData $data
*/
public static function fromArray(array $data): self
{
if (empty($data['src']) || !\is_string($data['src'])) {
throw new InvalidArgumentException('Invalid or missing "src" in Icon data.');
}

return new self($data['src'], $data['mimeTypes'] ?? null, $data['sizes'] ?? null);
}

/**
* @return IconData
*/
public function jsonSerialize(): array
{
$data = [
'src' => $this->src,
];

if (null !== $this->mimeType) {
$data['mimeType'] = $this->mimeType;
}

if (null !== $this->sizes) {
$data['sizes'] = $this->sizes;
}

return $data;
}
}
37 changes: 36 additions & 1 deletion src/Schema/Implementation.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,31 @@
/**
* Describes the name and version of an MCP implementation.
*
* @phpstan-import-type IconData from Icon
*
* @author Kyrian Obikwelu <[email protected]>
*/
class Implementation implements \JsonSerializable
{
/**
* @param ?Icon[] $icons
*/
public function __construct(
public readonly string $name = 'app',
public readonly string $version = 'dev',
public readonly ?string $description = null,
public readonly ?array $icons = null,
public readonly ?string $websiteUrl = null,
) {
}

/**
* @param array{
* name: string,
* version: string,
* description?: string,
* icons?: IconData[],
* websiteUrl?: string,
* } $data
*/
public static function fromArray(array $data): self
Expand All @@ -42,13 +52,30 @@ public static function fromArray(array $data): self
throw new InvalidArgumentException('Invalid or missing "version" in Implementation data.');
}

return new self($data['name'], $data['version'], $data['description'] ?? null);
if (isset($data['icons'])) {
if (!\is_array($data['icons'])) {
throw new InvalidArgumentException('Invalid "icons" in Implementation data; expected an array.');
}

$data['icons'] = array_map(Icon::fromArray(...), $data['icons']);
}

return new self(
$data['name'],
$data['version'],
$data['description'] ?? null,
$data['icons'] ?? null,
$data['websiteUrl'] ?? null,
);
}

/**
* @return array{
* name: string,
* version: string,
* description?: string,
* icons?: Icon[],
* websiteUrl?: string,
* }
*/
public function jsonSerialize(): array
Expand All @@ -62,6 +89,14 @@ public function jsonSerialize(): array
$data['description'] = $this->description;
}

if (null !== $this->icons) {
$data['icons'] = $this->icons;
}

if (null !== $this->websiteUrl) {
$data['websiteUrl'] = $this->websiteUrl;
}

return $data;
}
}
19 changes: 14 additions & 5 deletions src/Schema/Prompt.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,30 @@
* A prompt or prompt template that the server offers.
*
* @phpstan-import-type PromptArgumentData from PromptArgument
* @phpstan-import-type IconData from Icon
*
* @phpstan-type PromptData array{
* name: string,
* description?: string,
* arguments?: PromptArgumentData[],
* icons?: IconData[],
* }
*
* @author Kyrian Obikwelu <[email protected]>
*/
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 string $name the name of the prompt or prompt template
* @param ?string $description an optional description of what this prompt provides
* @param ?PromptArgument[] $arguments A list of arguments for templating. Null if not a template.
* @param ?Icon[] $icons optional icons representing the prompt
*/
public function __construct(
public readonly string $name,
public readonly ?string $description = null,
public readonly ?array $arguments = null,
public readonly ?array $icons = null,
) {
if (null !== $this->arguments) {
foreach ($this->arguments as $arg) {
Expand All @@ -63,15 +67,17 @@ public static function fromArray(array $data): self
return new self(
name: $data['name'],
description: $data['description'] ?? null,
arguments: $arguments
arguments: $arguments,
icons: isset($data['icons']) && \is_array($data['icons']) ? array_map(Icon::fromArray(...), $data['icons']) : null,
);
}

/**
* @return array{
* name: string,
* description?: string,
* arguments?: array<PromptArgument>
* arguments?: array<PromptArgument>,
* icons?: Icon[],
* }
*/
public function jsonSerialize(): array
Expand All @@ -83,6 +89,9 @@ public function jsonSerialize(): array
if (null !== $this->arguments) {
$data['arguments'] = $this->arguments;
}
if (null !== $this->icons) {
$data['icons'] = $this->icons;
}

return $data;
}
Expand Down
32 changes: 20 additions & 12 deletions src/Schema/Resource.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
* A known resource that the server is capable of reading.
*
* @phpstan-import-type AnnotationsData from Annotations
* @phpstan-import-type IconData from Icon
*
* @phpstan-type ResourceData array{
* uri: string,
* name: string,
* description?: string|null,
* mimeType?: string|null,
* annotations?: AnnotationsData|null,
* size?: int|null,
* description?: string,
* mimeType?: string,
* annotations?: AnnotationsData,
* size?: int,
* }
*
* @author Kyrian Obikwelu <[email protected]>
Expand All @@ -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 $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 $mimeType the MIME type of this resource, if known
* @param ?Annotations $annotations optional annotations for the client
* @param ?int $size The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
* @param ?Icon[] $icons optional icons representing the resource
*
* 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,
Expand All @@ -59,6 +61,7 @@ public function __construct(
public readonly ?string $mimeType = null,
public readonly ?Annotations $annotations = null,
public readonly ?int $size = null,
public readonly ?array $icons = null,
) {
if (!preg_match(self::RESOURCE_NAME_PATTERN, $name)) {
throw new InvalidArgumentException('Invalid resource name: must contain only alphanumeric characters, underscores, and hyphens.');
Expand Down Expand Up @@ -86,7 +89,8 @@ public static function fromArray(array $data): self
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,
icons: isset($data['icons']) && \is_array($data['icons']) ? array_map(Icon::fromArray(...), $data['icons']) : null,
);
}

Expand All @@ -98,6 +102,7 @@ public static function fromArray(array $data): self
* mimeType?: string,
* annotations?: Annotations,
* size?: int,
* icons?: Icon[],
* }
*/
public function jsonSerialize(): array
Expand All @@ -118,6 +123,9 @@ public function jsonSerialize(): array
if (null !== $this->size) {
$data['size'] = $this->size;
}
if (null !== $this->icons) {
$data['icons'] = $this->icons;
}

return $data;
}
Expand Down
Loading