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/2026-07-28/schema#implementation
31: */
32: final readonly class Implementation extends BaseMetadata implements Arrayable, Icons
33: {
34: /**
35: * @param non-empty-string $version
36: * @param null|non-empty-string $description
37: * @param null|non-empty-string $websiteUrl
38: * @param null|list<Icon> $icons
39: */
40: public function __construct(
41: string $name,
42: public string $version,
43: ?string $title = null,
44: public ?string $description = null,
45: public ?string $websiteUrl = null,
46: public ?array $icons = null,
47: ) {
48: parent::__construct(name: $name, title: $title);
49:
50: Assert::that($version)->isNonEmptyString('"version" must be a non-empty string.');
51: Assert::that($description)->nullOr()->isNonEmptyString('"description" must be a non-empty string or null.');
52: Assert::that($websiteUrl)
53: ->nullOr()
54: ->isNonEmptyString('"websiteUrl" must be a non-empty string or null.')
55: ->matchesRegularExpression('/\Ahttps?:\/\/\S+\z/', '"websiteUrl" must be an HTTP or HTTPS URL.')
56: ;
57:
58: if (null !== $icons) {
59: Assert::that($icons)->values()->isInstanceOf(Icon::class);
60: }
61: }
62:
63: #[\Override]
64: public static function fromArray(array $data): static
65: {
66: Assert::that($data)->hasOffset('name', 'missing the required "name" key.');
67: $name = $data['name'];
68: Assert::that($name)->isNonEmptyString('"name" must be a non-empty string, {type} given.');
69:
70: Assert::that($data)->hasOffset('version', 'missing the required "version" key.');
71: $version = $data['version'];
72: Assert::that($version)->isNonEmptyString('"version" must be a non-empty string, {type} given.');
73:
74: $title = $data['title'] ?? null;
75: Assert::that($title)->nullOr()->isNonEmptyString('"title" must be a non-empty string or null, {type} given.');
76:
77: $description = $data['description'] ?? null;
78: Assert::that($description)->nullOr()->isNonEmptyString('"description" must be a non-empty string or null, {type} given.');
79:
80: $websiteUrl = $data['websiteUrl'] ?? null;
81: Assert::that($websiteUrl)->nullOr()->isNonEmptyString('"websiteUrl" must be a non-empty string or null, {type} given.');
82:
83: $icons = null;
84:
85: if (isset($data['icons'])) {
86: Assert::that($data['icons'])
87: ->isList('"icons" must be a list, {type} given.')
88: ->values()
89: ->isArray('each implementation "icons" must be an object, {type} given.')
90: ->isMap('each implementation "icons" must be a string-keyed object.')
91: ;
92: $icons = array_map(Icon::fromArray(...), $data['icons']);
93: }
94:
95: return new self(
96: name: $name,
97: version: $version,
98: title: $title,
99: description: $description,
100: websiteUrl: $websiteUrl,
101: icons: $icons,
102: );
103: }
104:
105: #[\Override]
106: public function toArray(): array
107: {
108: $data = [
109: 'name' => $this->name,
110: 'version' => $this->version,
111: ];
112:
113: if (null !== $this->title) {
114: $data['title'] = $this->title;
115: }
116:
117: if (null !== $this->description) {
118: $data['description'] = $this->description;
119: }
120:
121: if (null !== $this->websiteUrl) {
122: $data['websiteUrl'] = $this->websiteUrl;
123: }
124:
125: if (null !== $this->icons) {
126: $data['icons'] = array_map(static fn(Icon $icon): array => $icon->toArray(), $this->icons);
127: }
128:
129: return $data;
130: }
131:
132: #[\Override]
133: public function jsonSerialize(): array
134: {
135: return $this->toArray();
136: }
137: }
138: