1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus MCP SDK package.
7: *
8: * (c) 2026 John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9: *
10: * For the full copyright and license information, please view
11: * the LICENSE file that was distributed with this source code.
12: */
13:
14: namespace Nexus\Mcp\Core\Schema;
15:
16: use Nexus\Assert\Assert;
17:
18: /**
19: * Describes the MCP implementation.
20: *
21: * @implements Arrayable<array{
22: * name: non-empty-string,
23: * version: non-empty-string,
24: * title?: non-empty-string,
25: * description?: non-empty-string,
26: * websiteUrl?: non-empty-string,
27: * icons?: list<template-type<Icon, Arrayable, 'T'>>,
28: * }>
29: *
30: * @see https://modelcontextprotocol.io/specification/2025-11-25/schema#implementation
31: */
32: final readonly class Implementation extends BaseMetadata implements Arrayable, Icons
33: {
34: /**
35: * @var non-empty-string
36: */
37: public string $version;
38:
39: /**
40: * @var null|non-empty-string
41: */
42: public ?string $description;
43:
44: /**
45: * @var null|non-empty-string
46: */
47: public ?string $websiteUrl;
48:
49: /**
50: * @var null|list<Icon>
51: */
52: public ?array $icons;
53:
54: /**
55: * @param null|list<Icon> $icons
56: */
57: public function __construct(
58: string $name,
59: string $version,
60: ?string $title = null,
61: ?string $description = null,
62: ?string $websiteUrl = null,
63: ?array $icons = null,
64: ) {
65: parent::__construct($name, $title);
66:
67: Assert::that($version)->isNonEmptyString('"version" must be a non-empty string.');
68: Assert::that($description)->nullOr()->isNonEmptyString('"description" must be a non-empty string or null.');
69: Assert::that($websiteUrl)
70: ->nullOr()
71: ->isNonEmptyString('"websiteUrl" must be a non-empty string or null.')
72: ->matchesRegularExpression('/\Ahttps?:\/\/\S+\z/', '"websiteUrl" must be an HTTP or HTTPS URL.')
73: ;
74:
75: if (null !== $icons) {
76: Assert::that($icons)->values()->isInstanceOf(Icon::class);
77: }
78:
79: $this->version = $version;
80: $this->description = $description;
81: $this->websiteUrl = $websiteUrl;
82: $this->icons = $icons;
83: }
84:
85: /**
86: * @param array<string, mixed> $data
87: */
88: #[\Override]
89: public static function fromArray(array $data): static
90: {
91: Assert::that($data)->hasOffset('name', 'missing the required "name" key.');
92: $name = $data['name'];
93: Assert::that($name)->isString('"name" must be a string, {type} given.');
94:
95: Assert::that($data)->hasOffset('version', 'missing the required "version" key.');
96: $version = $data['version'];
97: Assert::that($version)->isString('"version" must be a string, {type} given.');
98:
99: $title = $data['title'] ?? null;
100: Assert::that($title)->nullOr()->isString('"title" must be a string or null, {type} given.');
101:
102: $description = $data['description'] ?? null;
103: Assert::that($description)->nullOr()->isString('"description" must be a string or null, {type} given.');
104:
105: $websiteUrl = $data['websiteUrl'] ?? null;
106: Assert::that($websiteUrl)->nullOr()->isString('"websiteUrl" must be a string or null, {type} given.');
107:
108: $icons = null;
109:
110: if (isset($data['icons'])) {
111: Assert::that($data['icons'])
112: ->isList('"icons" must be a list, {type} given.')
113: ->values()
114: ->isArray('each implementation "icons" must be an object, {type} given.')
115: ->isMap('each implementation "icons" must be a string-keyed object.')
116: ;
117: $icons = array_map(Icon::fromArray(...), $data['icons']);
118: }
119:
120: return new self($name, $version, $title, $description, $websiteUrl, $icons);
121: }
122:
123: #[\Override]
124: public function toArray(): array
125: {
126: $data = [
127: 'name' => $this->name,
128: 'version' => $this->version,
129: ];
130:
131: if (null !== $this->title) {
132: $data['title'] = $this->title;
133: }
134:
135: if (null !== $this->description) {
136: $data['description'] = $this->description;
137: }
138:
139: if (null !== $this->websiteUrl) {
140: $data['websiteUrl'] = $this->websiteUrl;
141: }
142:
143: if (null !== $this->icons) {
144: $data['icons'] = array_map(static fn(Icon $icon): array => $icon->toArray(), $this->icons);
145: }
146:
147: return $data;
148: }
149:
150: #[\Override]
151: public function jsonSerialize(): array
152: {
153: return $this->toArray();
154: }
155: }
156: