php-sdk/tests/Unit/Schema/ClientCapabilitiesTest.php
Christopher Hertel 43815e38a8
[Schema][Server] Harden sampling tools against the spec (#420)
* harden sampling tools against the spec

Report tool-flow violations as -32602 instead of dropping the request,
accept resource_link in tool results, gate tools on sampling.tools, and
reject empty content.

* normalize keyed content arrays to lists

array_filter() and friends preserve keys, and a keyed array serializes as a
JSON object instead of the content-block array the schema requires.
2026-08-15 04:13:53 +02:00

114 lines
3.8 KiB
PHP

<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Tests\Unit\Schema;
use Mcp\Schema\ClientCapabilities;
use PHPUnit\Framework\TestCase;
class ClientCapabilitiesTest extends TestCase
{
public function testSerializesRootsWithoutListChanged(): void
{
$capabilities = new ClientCapabilities(roots: true);
$data = json_decode(json_encode($capabilities), true);
$this->assertArrayHasKey('roots', $data);
$this->assertSame([], $data['roots']);
}
public function testSerializesRootsWithListChanged(): void
{
$capabilities = new ClientCapabilities(roots: true, rootsListChanged: true);
$data = json_decode(json_encode($capabilities), true);
$this->assertSame(['listChanged' => true], $data['roots']);
}
public function testSerializesEmptyCapabilitiesAsObject(): void
{
$capabilities = new ClientCapabilities();
$this->assertSame('{}', json_encode($capabilities));
}
public function testFromArrayReadsRootsListChanged(): void
{
$capabilities = ClientCapabilities::fromArray(['roots' => ['listChanged' => true]]);
$this->assertTrue($capabilities->roots);
$this->assertTrue($capabilities->rootsListChanged);
}
public function testFromArrayRootsWithoutListChanged(): void
{
$capabilities = ClientCapabilities::fromArray(['roots' => []]);
$this->assertTrue($capabilities->roots);
$this->assertNull($capabilities->rootsListChanged);
}
public function testRoundTripPreservesRootsListChanged(): void
{
$capabilities = new ClientCapabilities(roots: true, rootsListChanged: true);
$data = json_decode(json_encode($capabilities), true);
$restored = ClientCapabilities::fromArray($data);
$this->assertTrue($restored->roots);
$this->assertTrue($restored->rootsListChanged);
}
public function testRoundTripPreservesSamplingSubCapabilities(): void
{
$capabilities = new ClientCapabilities(sampling: true, samplingContext: true, samplingTools: true);
$serialized = $capabilities->jsonSerialize();
$this->assertObjectHasProperty('context', $serialized['sampling']);
$this->assertObjectHasProperty('tools', $serialized['sampling']);
$restored = ClientCapabilities::fromArray(json_decode(json_encode($capabilities), true));
$this->assertTrue($restored->sampling);
$this->assertTrue($restored->samplingContext);
$this->assertTrue($restored->samplingTools);
}
public function testPlainSamplingLeavesSubCapabilitiesOff(): void
{
$capabilities = new ClientCapabilities(sampling: true);
$serialized = $capabilities->jsonSerialize();
$this->assertObjectNotHasProperty('context', $serialized['sampling']);
$this->assertObjectNotHasProperty('tools', $serialized['sampling']);
$restored = ClientCapabilities::fromArray(json_decode(json_encode($capabilities), true));
$this->assertTrue($restored->sampling);
$this->assertFalse($restored->samplingContext);
$this->assertFalse($restored->samplingTools);
}
public function testSamplingSubCapabilitiesAreHydratedFromObject(): void
{
$sampling = new \stdClass();
$sampling->context = new \stdClass();
$sampling->tools = new \stdClass();
$capabilities = ClientCapabilities::fromArray(['sampling' => $sampling]);
$this->assertTrue($capabilities->sampling);
$this->assertTrue($capabilities->samplingContext);
$this->assertTrue($capabilities->samplingTools);
}
}