Skip to content

Commit 477cdb1

Browse files
committed
Introducing schema component
1 parent 63dc146 commit 477cdb1

File tree

76 files changed

+5684
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+5684
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
}
1919
],
2020
"require": {
21+
"ext-fileinfo": "*",
2122
"php": "^8.2",
2223
"psr/log": "^1.0 || ^2.0 || ^3.0",
2324
"symfony/uid": "^6.4 || ^7.0"

src/Exception/RuntimeException.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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\Exception;
13+
14+
/**
15+
* @author Christopher Hertel <[email protected]>
16+
*/
17+
class RuntimeException extends \RuntimeException implements ExceptionInterface
18+
{
19+
}

src/Schema/Annotations.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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\Schema\Enum\Role;
15+
16+
/**
17+
* Optional annotations for the client. The client can use annotations
18+
* to inform how objects are used or displayed.
19+
*
20+
* @author Kyrian Obikwelu <[email protected]>
21+
*/
22+
class Annotations implements \JsonSerializable
23+
{
24+
/**
25+
* @param Role[]|null $audience Describes who the intended customer of this object or data is.
26+
*
27+
* It can include multiple entries to indicate content useful for multiple audiences (e.g., `[Role::User, Role::Assistant]`).
28+
* @param float|null $priority Describes how important this data is for operating the server.
29+
*
30+
* A value of 1 means "most important," and indicates that the data is
31+
* effectively required, while 0 means "least important," and indicates that
32+
* the data is entirely optional.
33+
*/
34+
public function __construct(
35+
public readonly ?array $audience = null,
36+
public readonly ?float $priority = null,
37+
) {
38+
if (null !== $this->priority && ($this->priority < 0 || $this->priority > 1)) {
39+
throw new \InvalidArgumentException('Annotation priority must be between 0 and 1.');
40+
}
41+
if (null !== $this->audience) {
42+
foreach ($this->audience as $role) {
43+
if (!($role instanceof Role)) {
44+
throw new \InvalidArgumentException('All audience members must be instances of Role enum.');
45+
}
46+
}
47+
}
48+
}
49+
50+
/**
51+
* @param Role[]|null $audience Describes who the intended customer of this object or data is.
52+
*
53+
* It can include multiple entries to indicate content useful for multiple audiences (e.g., `[Role::User, Role::Assistant]`).
54+
* @param float|null $priority Describes how important this data is for operating the server.
55+
*
56+
* A value of 1 means "most important," and indicates that the data is
57+
* effectively required, while 0 means "least important," and indicates that
58+
* the data is entirely optional.
59+
*/
60+
public static function make(?array $audience = null, ?float $priority = null): static
61+
{
62+
return new static($audience, $priority);
63+
}
64+
65+
public function toArray(): array
66+
{
67+
$data = [];
68+
if (null !== $this->audience) {
69+
$data['audience'] = array_map(fn (Role $r) => $r->value, $this->audience);
70+
}
71+
if (null !== $this->priority) {
72+
$data['priority'] = $this->priority;
73+
}
74+
75+
return $data;
76+
}
77+
78+
public static function fromArray(array $data): static
79+
{
80+
$audience = null;
81+
if (isset($data['audience']) && \is_array($data['audience'])) {
82+
$audience = array_map(fn (string $r) => Role::from($r), $data['audience']);
83+
}
84+
85+
return new static(
86+
$audience,
87+
isset($data['priority']) ? (float) $data['priority'] : null
88+
);
89+
}
90+
91+
public function jsonSerialize(): array
92+
{
93+
return $this->toArray();
94+
}
95+
}

src/Schema/ClientCapabilities.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
/**
15+
* Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.
16+
*
17+
* @author Kyrian Obikwelu <[email protected]>
18+
*/
19+
class ClientCapabilities implements \JsonSerializable
20+
{
21+
public function __construct(
22+
public readonly ?bool $roots = false,
23+
public readonly ?bool $rootsListChanged = null,
24+
public readonly ?bool $sampling = null,
25+
public readonly ?array $experimental = null,
26+
) {
27+
}
28+
29+
public static function make(?bool $roots = false, ?bool $rootsListChanged = null, ?bool $sampling = null, ?array $experimental = null): static
30+
{
31+
return new static($roots, $rootsListChanged, $sampling, $experimental);
32+
}
33+
34+
public function toArray(): array
35+
{
36+
$data = [];
37+
if ($this->roots || $this->rootsListChanged) {
38+
$data['roots'] = new \stdClass();
39+
if ($this->rootsListChanged) {
40+
$data['roots']->listChanged = $this->rootsListChanged;
41+
}
42+
}
43+
44+
if ($this->sampling) {
45+
$data['sampling'] = new \stdClass();
46+
}
47+
48+
if ($this->experimental) {
49+
$data['experimental'] = (object) $this->experimental;
50+
}
51+
52+
return $data;
53+
}
54+
55+
public static function fromArray(array $data): static
56+
{
57+
$rootsEnabled = isset($data['roots']);
58+
$rootsListChanged = null;
59+
if ($rootsEnabled) {
60+
if (\is_array($data['roots']) && \array_key_exists('listChanged', $data['roots'])) {
61+
$rootsListChanged = (bool) $data['roots']['listChanged'];
62+
} elseif (\is_object($data['roots']) && property_exists($data['roots'], 'listChanged')) {
63+
$rootsListChanged = (bool) $data['roots']->listChanged;
64+
}
65+
}
66+
67+
$sampling = null;
68+
if (isset($data['sampling'])) {
69+
$sampling = true;
70+
}
71+
72+
return new static(
73+
$rootsListChanged,
74+
$sampling,
75+
$data['experimental'] ?? null
76+
);
77+
}
78+
79+
public function jsonSerialize(): array
80+
{
81+
return $this->toArray();
82+
}
83+
}

src/Schema/Constants.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
/**
15+
* @author Kyrian Obikwelu <[email protected]>
16+
*/
17+
final class Constants
18+
{
19+
public const LATEST_PROTOCOL_VERSION = '2025-03-26';
20+
public const JSONRPC_VERSION = '2.0';
21+
22+
public const PARSE_ERROR = -32700;
23+
public const INVALID_REQUEST = -32600;
24+
public const METHOD_NOT_FOUND = -32601;
25+
public const INVALID_PARAMS = -32602;
26+
public const INTERNAL_ERROR = -32603;
27+
28+
public const SERVER_ERROR = -32000;
29+
30+
private function __construct()
31+
{
32+
}
33+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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\Content;
13+
14+
use Mcp\Schema\Annotations;
15+
16+
/**
17+
* Represents audio content in MCP.
18+
*
19+
* @author Kyrian Obikwelu <[email protected]>
20+
*/
21+
class AudioContent extends Content
22+
{
23+
public function __construct(
24+
public readonly string $data,
25+
public readonly string $mimeType,
26+
public readonly ?Annotations $annotations = null,
27+
) {
28+
parent::__construct('audio');
29+
}
30+
31+
public static function make(string $data, string $mimeType, ?Annotations $annotations = null): static
32+
{
33+
return new static($data, $mimeType, $annotations);
34+
}
35+
36+
/**
37+
* Convert the content to an array.
38+
*
39+
* @return array{type: string, data: string, mimeType: string, annotations?: array}
40+
*/
41+
public function toArray(): array
42+
{
43+
$result = [
44+
'type' => 'audio',
45+
'data' => $this->data,
46+
'mimeType' => $this->mimeType,
47+
];
48+
49+
if (null !== $this->annotations) {
50+
$result['annotations'] = $this->annotations->toArray();
51+
}
52+
53+
return $result;
54+
}
55+
56+
public static function fromArray(array $data): static
57+
{
58+
if (!isset($data['data']) || !isset($data['mimeType'])) {
59+
throw new \InvalidArgumentException("Invalid or missing 'data' or 'mimeType' in AudioContent data.");
60+
}
61+
62+
return new static($data['data'], $data['mimeType'], $data['annotations'] ?? null);
63+
}
64+
65+
/**
66+
* Create a new AudioContent from a file path.
67+
*
68+
* @param string $path Path to the audio file
69+
* @param string|null $mimeType Optional MIME type override
70+
* @param ?Annotations $annotations Optional annotations describing the content
71+
*
72+
* @throws \InvalidArgumentException If the file doesn't exist
73+
*/
74+
public static function fromFile(string $path, ?string $mimeType = null, ?Annotations $annotations = null): static
75+
{
76+
if (!file_exists($path)) {
77+
throw new \InvalidArgumentException("Audio file not found: {$path}");
78+
}
79+
80+
$content = file_get_contents($path);
81+
if (false === $content) {
82+
throw new \RuntimeException("Could not read audio file: {$path}");
83+
}
84+
$data = base64_encode($content);
85+
$detectedMime = $mimeType ?? mime_content_type($path) ?: 'application/octet-stream';
86+
87+
return new static($data, $detectedMime, $annotations);
88+
}
89+
90+
/**
91+
* Create a new AudioContent from a string.
92+
*
93+
* @param string $data The audio data
94+
* @param string $mimeType MIME type of the audio
95+
* @param ?Annotations $annotations Optional annotations describing the content
96+
*/
97+
public static function fromString(string $data, string $mimeType, ?Annotations $annotations = null): static
98+
{
99+
return new static(base64_encode($data), $mimeType, $annotations);
100+
}
101+
102+
public function jsonSerialize(): array
103+
{
104+
return $this->toArray();
105+
}
106+
}

0 commit comments

Comments
 (0)