Skip to content

Commit 58a0725

Browse files
committed
Add support for icons and website url
1 parent 660f150 commit 58a0725

File tree

10 files changed

+279
-22
lines changed

10 files changed

+279
-22
lines changed

examples/schema-showcase/server.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,18 @@
1313
require_once dirname(__DIR__).'/bootstrap.php';
1414
chdir(__DIR__);
1515

16+
use Mcp\Schema\Icon;
1617
use Mcp\Server;
1718
use Mcp\Server\Session\FileSessionStore;
1819

1920
$server = Server::builder()
20-
->setServerInfo('Schema Showcase', '1.0.0')
21+
->setServerInfo(
22+
'Schema Showcase',
23+
'1.0.0',
24+
'A showcase server demonstrating MCP schema capabilities.',
25+
[new Icon('https://www.php.net/images/logos/php-logo-white.svg', 'image/svg+xml', ['any'])],
26+
'https://github.com/modelcontextprotocol/php-sdk',
27+
)
2128
->setContainer(container())
2229
->setLogger(logger())
2330
->setSession(new FileSessionStore(__DIR__.'/sessions'))

src/Schema/Enum/ProtocolVersion.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@
1313

1414
/**
1515
* Available protocol versions for MCP.
16+
*
17+
* @author Illia Vasylevskyi<[email protected]>
1618
*/
1719
enum ProtocolVersion: string
1820
{
1921
case V2024_11_05 = '2024-11-05';
20-
2122
case V2025_03_26 = '2025-03-26';
22-
2323
case V2025_06_18 = '2025-06-18';
24+
case V2025_11_25 = '2025-11-25';
2425
}

src/Schema/Icon.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the official PHP MCP SDK.
5+
*
6+
* A collaboration between Symfony and the PHP Foundation.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Mcp\Schema;
13+
14+
use Mcp\Exception\InvalidArgumentException;
15+
16+
/**
17+
* A url pointing to an icon URL or a base64-encoded data URI.
18+
*
19+
* @phpstan-type IconData array{
20+
* src: string,
21+
* mimeType?: string,
22+
* sizes?: string[],
23+
* }
24+
*
25+
* @author Christopher Hertel <[email protected]>
26+
*/
27+
class Icon implements \JsonSerializable
28+
{
29+
/**
30+
* @param string $src a standard URI pointing to an icon resource
31+
* @param ?string $mimeType optional override if the server's MIME type is missing or generic
32+
* @param ?string[] $sizes optional array of strings that specify sizes at which the icon can be used.
33+
* Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for
34+
* scalable formats like SVG.
35+
*/
36+
public function __construct(
37+
public readonly string $src,
38+
public readonly ?string $mimeType = null,
39+
public readonly ?array $sizes = null,
40+
) {
41+
if (empty($src)) {
42+
throw new InvalidArgumentException('Icon "src" must be a non-empty string.');
43+
}
44+
if (!preg_match('#^(https?://|data:)#', $src)) {
45+
throw new InvalidArgumentException('Icon "src" must be a valid URL or data URI.');
46+
}
47+
48+
if (null !== $sizes) {
49+
foreach ($sizes as $size) {
50+
if (!\is_string($size)) {
51+
throw new InvalidArgumentException('Each size in "sizes" must be a string.');
52+
}
53+
if (!preg_match('/^(any|\d+x\d+)$/', $size)) {
54+
throw new InvalidArgumentException(\sprintf('Invalid size format "%s" in "sizes". Expected "WxH" or "any".', $size));
55+
}
56+
}
57+
}
58+
}
59+
60+
/**
61+
* @param IconData $data
62+
*/
63+
public static function fromArray(array $data): self
64+
{
65+
if (empty($data['src']) || !\is_string($data['src'])) {
66+
throw new InvalidArgumentException('Invalid or missing "src" in Icon data.');
67+
}
68+
69+
return new self($data['src'], $data['mimeTypes'] ?? null, $data['sizes'] ?? null);
70+
}
71+
72+
/**
73+
* @return IconData
74+
*/
75+
public function jsonSerialize(): array
76+
{
77+
$data = [
78+
'src' => $this->src,
79+
];
80+
81+
if (null !== $this->mimeType) {
82+
$data['mimeType'] = $this->mimeType;
83+
}
84+
85+
if (null !== $this->sizes) {
86+
$data['sizes'] = $this->sizes;
87+
}
88+
89+
return $data;
90+
}
91+
}

src/Schema/Implementation.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,31 @@
1616
/**
1717
* Describes the name and version of an MCP implementation.
1818
*
19+
* @phpstan-import-type IconData from Icon
20+
*
1921
* @author Kyrian Obikwelu <[email protected]>
2022
*/
2123
class Implementation implements \JsonSerializable
2224
{
25+
/**
26+
* @param ?Icon[] $icons
27+
*/
2328
public function __construct(
2429
public readonly string $name = 'app',
2530
public readonly string $version = 'dev',
2631
public readonly ?string $description = null,
32+
public readonly ?array $icons = null,
33+
public readonly ?string $websiteUrl = null,
2734
) {
2835
}
2936

3037
/**
3138
* @param array{
3239
* name: string,
3340
* version: string,
41+
* description?: string,
42+
* icons?: IconData[],
43+
* websiteUrl?: string,
3444
* } $data
3545
*/
3646
public static function fromArray(array $data): self
@@ -42,13 +52,30 @@ public static function fromArray(array $data): self
4252
throw new InvalidArgumentException('Invalid or missing "version" in Implementation data.');
4353
}
4454

45-
return new self($data['name'], $data['version'], $data['description'] ?? null);
55+
if (isset($data['icons'])) {
56+
if (!\is_array($data['icons'])) {
57+
throw new InvalidArgumentException('Invalid "icons" in Implementation data; expected an array.');
58+
}
59+
60+
$data['icons'] = array_map(Icon::fromArray(...), $data['icons']);
61+
}
62+
63+
return new self(
64+
$data['name'],
65+
$data['version'],
66+
$data['description'] ?? null,
67+
$data['icons'] ?? null,
68+
$data['websiteUrl'] ?? null,
69+
);
4670
}
4771

4872
/**
4973
* @return array{
5074
* name: string,
5175
* version: string,
76+
* description?: string,
77+
* icons?: Icon[],
78+
* websiteUrl?: string,
5279
* }
5380
*/
5481
public function jsonSerialize(): array
@@ -62,6 +89,14 @@ public function jsonSerialize(): array
6289
$data['description'] = $this->description;
6390
}
6491

92+
if (null !== $this->icons) {
93+
$data['icons'] = $this->icons;
94+
}
95+
96+
if (null !== $this->websiteUrl) {
97+
$data['websiteUrl'] = $this->websiteUrl;
98+
}
99+
65100
return $data;
66101
}
67102
}

src/Schema/Prompt.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
* A prompt or prompt template that the server offers.
1818
*
1919
* @phpstan-import-type PromptArgumentData from PromptArgument
20+
* @phpstan-import-type IconData from Icon
2021
*
2122
* @phpstan-type PromptData array{
2223
* name: string,
2324
* description?: string,
2425
* arguments?: PromptArgumentData[],
26+
* icons?: IconData[],
2527
* _meta?: array<string, mixed>
2628
* }
2729
*
@@ -31,14 +33,16 @@ class Prompt implements \JsonSerializable
3133
{
3234
/**
3335
* @param string $name the name of the prompt or prompt template
34-
* @param string|null $description an optional description of what this prompt provides
35-
* @param PromptArgument[]|null $arguments A list of arguments for templating. Null if not a template.
36+
* @param ?string $description an optional description of what this prompt provides
37+
* @param ?PromptArgument[] $arguments A list of arguments for templating. Null if not a template.
38+
* @param ?Icon[] $icons optional icons representing the prompt
3639
* @param ?array<string, mixed> $meta Optional metadata
3740
*/
3841
public function __construct(
3942
public readonly string $name,
4043
public readonly ?string $description = null,
4144
public readonly ?array $arguments = null,
45+
public readonly ?array $icons = null,
4246
public readonly ?array $meta = null,
4347
) {
4448
if (null !== $this->arguments) {
@@ -71,6 +75,7 @@ public static function fromArray(array $data): self
7175
name: $data['name'],
7276
description: $data['description'] ?? null,
7377
arguments: $arguments,
78+
icons: isset($data['icons']) && \is_array($data['icons']) ? array_map(Icon::fromArray(...), $data['icons']) : null,
7479
meta: isset($data['_meta']) ? $data['_meta'] : null
7580
);
7681
}
@@ -80,6 +85,7 @@ public static function fromArray(array $data): self
8085
* name: string,
8186
* description?: string,
8287
* arguments?: array<PromptArgument>,
88+
* icons?: Icon[],
8389
* _meta?: array<string, mixed>
8490
* }
8591
*/
@@ -92,6 +98,9 @@ public function jsonSerialize(): array
9298
if (null !== $this->arguments) {
9399
$data['arguments'] = $this->arguments;
94100
}
101+
if (null !== $this->icons) {
102+
$data['icons'] = $this->icons;
103+
}
95104
if (null !== $this->meta) {
96105
$data['_meta'] = $this->meta;
97106
}

src/Schema/Resource.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@
1717
* A known resource that the server is capable of reading.
1818
*
1919
* @phpstan-import-type AnnotationsData from Annotations
20+
* @phpstan-import-type IconData from Icon
2021
*
2122
* @phpstan-type ResourceData array{
2223
* uri: string,
2324
* name: string,
24-
* description?: string|null,
25-
* mimeType?: string|null,
26-
* annotations?: AnnotationsData|null,
27-
* size?: int|null,
28-
* _meta?: array<string, mixed>
25+
* description?: string,
26+
* mimeType?: string,
27+
* annotations?: AnnotationsData,
28+
* size?: int,
29+
* icons?: IconData[],
30+
* _meta?: array<string, mixed>,
2931
* }
3032
*
3133
* @author Kyrian Obikwelu <[email protected]>
@@ -46,10 +48,11 @@ class Resource implements \JsonSerializable
4648
/**
4749
* @param string $uri the URI of this resource
4850
* @param string $name A human-readable name for this resource. This can be used by clients to populate UI elements.
49-
* @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.
50-
* @param string|null $mimeType the MIME type of this resource, if known
51-
* @param Annotations|null $annotations optional annotations for the client
52-
* @param int|null $size The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
51+
* @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.
52+
* @param ?string $mimeType the MIME type of this resource, if known
53+
* @param ?Annotations $annotations optional annotations for the client
54+
* @param ?int $size The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
55+
* @param ?Icon[] $icons optional icons representing the resource
5356
* @param ?array<string, mixed> $meta Optional metadata
5457
*
5558
* This can be used by Hosts to display file sizes and estimate context window usage
@@ -61,6 +64,7 @@ public function __construct(
6164
public readonly ?string $mimeType = null,
6265
public readonly ?Annotations $annotations = null,
6366
public readonly ?int $size = null,
67+
public readonly ?array $icons = null,
6468
public readonly ?array $meta = null,
6569
) {
6670
if (!preg_match(self::RESOURCE_NAME_PATTERN, $name)) {
@@ -94,6 +98,7 @@ public static function fromArray(array $data): self
9498
mimeType: $data['mimeType'] ?? null,
9599
annotations: isset($data['annotations']) ? Annotations::fromArray($data['annotations']) : null,
96100
size: isset($data['size']) ? (int) $data['size'] : null,
101+
icons: isset($data['icons']) && \is_array($data['icons']) ? array_map(Icon::fromArray(...), $data['icons']) : null,
97102
meta: isset($data['_meta']) ? $data['_meta'] : null
98103
);
99104
}
@@ -106,6 +111,7 @@ public static function fromArray(array $data): self
106111
* mimeType?: string,
107112
* annotations?: Annotations,
108113
* size?: int,
114+
* icons?: Icon[],
109115
* _meta?: array<string, mixed>
110116
* }
111117
*/
@@ -127,6 +133,9 @@ public function jsonSerialize(): array
127133
if (null !== $this->size) {
128134
$data['size'] = $this->size;
129135
}
136+
if (null !== $this->icons) {
137+
$data['icons'] = $this->icons;
138+
}
130139
if (null !== $this->meta) {
131140
$data['_meta'] = $this->meta;
132141
}

0 commit comments

Comments
 (0)