Skip to content

Commit ee8d82e

Browse files
committed
Introducing schema component
1 parent 0c87e0f commit ee8d82e

File tree

82 files changed

+6015
-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.

82 files changed

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

src/Schema/ClientCapabilities.php

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

src/Schema/Constants.php

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

0 commit comments

Comments
 (0)