From 08d332c7ac8f5234058758bb50eade6e0e4a0320 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Tue, 18 Aug 2026 14:59:12 +0200 Subject: [PATCH] [Schema] Serialize a settingless extension as an object (#438) * [Schema] Serialize a settingless extension as an object An extension declaring support with no settings was advertised as `[]`, an empty JSON array, which the wire schema rejects - `extensions` maps an id to a settings *object*. Both ServerCapabilities and ClientCapabilities now cast each entry, so it goes out as `{}`. * Apply suggestion from @chr-hertel --- src/Schema/ClientCapabilities.php | 5 ++++- src/Schema/ServerCapabilities.php | 7 ++++++- .../Extension/CapabilitiesExtensionsTest.php | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Schema/ClientCapabilities.php b/src/Schema/ClientCapabilities.php index bc71cfc..75d50ff 100644 --- a/src/Schema/ClientCapabilities.php +++ b/src/Schema/ClientCapabilities.php @@ -188,7 +188,10 @@ class ClientCapabilities implements \JsonSerializable } if ($this->extensions) { - $data['extensions'] = (object) $this->extensions; + $data['extensions'] = (object) array_map( + static fn (mixed $settings): mixed => \is_array($settings) ? (object) $settings : $settings, + $this->extensions, + ); } return $data ?: new \stdClass(); diff --git a/src/Schema/ServerCapabilities.php b/src/Schema/ServerCapabilities.php index 0e47c61..33cc1ee 100644 --- a/src/Schema/ServerCapabilities.php +++ b/src/Schema/ServerCapabilities.php @@ -189,7 +189,12 @@ class ServerCapabilities implements \JsonSerializable } if ($this->extensions) { - $data['extensions'] = (object) $this->extensions; + // Each entry is a settings *object*; an extension with no settings + // declares `{}`, and an empty PHP array would serialize as `[]`. + $data['extensions'] = (object) array_map( + static fn (mixed $settings): mixed => \is_array($settings) ? (object) $settings : $settings, + $this->extensions, + ); } return $data; diff --git a/tests/Unit/Schema/Extension/CapabilitiesExtensionsTest.php b/tests/Unit/Schema/Extension/CapabilitiesExtensionsTest.php index 7ef916e..dbb7e7e 100644 --- a/tests/Unit/Schema/Extension/CapabilitiesExtensionsTest.php +++ b/tests/Unit/Schema/Extension/CapabilitiesExtensionsTest.php @@ -57,6 +57,24 @@ class CapabilitiesExtensionsTest extends TestCase $this->assertObjectHasProperty(McpApps::EXTENSION_ID, $json['extensions']); } + public function testServerCapabilitiesSerializeSettinglessExtensionAsObject(): void + { + $caps = new ServerCapabilities(tools: true, extensions: ['acme/thing' => []]); + + $json = json_encode($caps->jsonSerialize(), \JSON_UNESCAPED_SLASHES); + + $this->assertStringContainsString('"acme/thing":{}', (string) $json); + } + + public function testClientCapabilitiesSerializeSettinglessExtensionAsObject(): void + { + $caps = new ClientCapabilities(elicitation: true, extensions: ['acme/thing' => []]); + + $json = json_encode($caps->jsonSerialize(), \JSON_UNESCAPED_SLASHES); + + $this->assertStringContainsString('"acme/thing":{}', (string) $json); + } + public function testServerCapabilitiesJsonSerializeWithoutExtensions(): void { $caps = new ServerCapabilities(tools: true);