assertInstanceOf(ExtensionInterface::class, $extension); $this->assertSame('io.modelcontextprotocol/ui', (string) $extension->getId()); $this->assertSame(['mimeTypes' => ['text/html;profile=mcp-app']], $extension->getCapabilities()); } public function testUiResourceCspSerialization(): void { $csp = new UiResourceCsp( connectDomains: ['https://api.example.com'], resourceDomains: ['https://cdn.example.com'], frameDomains: ['https://embed.example.com'], baseUriDomains: ['https://example.com'], ); $serialized = $csp->jsonSerialize(); $this->assertSame(['https://api.example.com'], $serialized['connectDomains']); $this->assertSame(['https://cdn.example.com'], $serialized['resourceDomains']); $this->assertSame(['https://embed.example.com'], $serialized['frameDomains']); $this->assertSame(['https://example.com'], $serialized['baseUriDomains']); } public function testUiResourceCspOmitsNullFields(): void { $csp = new UiResourceCsp(connectDomains: ['https://api.example.com']); $serialized = $csp->jsonSerialize(); $this->assertArrayHasKey('connectDomains', $serialized); $this->assertArrayNotHasKey('resourceDomains', $serialized); $this->assertArrayNotHasKey('frameDomains', $serialized); $this->assertArrayNotHasKey('baseUriDomains', $serialized); } public function testUiResourceCspFromArray(): void { $csp = UiResourceCsp::fromArray([ 'connectDomains' => ['https://api.example.com'], 'frameDomains' => ['https://embed.example.com'], ]); $this->assertSame(['https://api.example.com'], $csp->connectDomains); $this->assertNull($csp->resourceDomains); $this->assertSame(['https://embed.example.com'], $csp->frameDomains); $this->assertNull($csp->baseUriDomains); } public function testUiResourcePermissionsSerialization(): void { $perms = new UiResourcePermissions( camera: true, microphone: false, geolocation: true, clipboardWrite: false, ); $serialized = $perms->jsonSerialize(); // Per spec, each requested permission is an empty object marker. $this->assertEquals(new \stdClass(), $serialized['camera']); $this->assertArrayNotHasKey('microphone', $serialized); $this->assertEquals(new \stdClass(), $serialized['geolocation']); $this->assertArrayNotHasKey('clipboardWrite', $serialized); $this->assertSame('{"camera":{},"geolocation":{}}', json_encode($perms)); } public function testUiResourcePermissionsOmitsUnrequestedFields(): void { $perms = new UiResourcePermissions(clipboardWrite: true); $serialized = $perms->jsonSerialize(); $this->assertArrayNotHasKey('camera', $serialized); $this->assertArrayNotHasKey('microphone', $serialized); $this->assertArrayNotHasKey('geolocation', $serialized); $this->assertArrayHasKey('clipboardWrite', $serialized); } public function testUiResourcePermissionsFromArray(): void { // Spec wire shape: presence indicates a request; the value is an empty object. $perms = UiResourcePermissions::fromArray([ 'camera' => [], 'clipboardWrite' => [], ]); $this->assertTrue($perms->camera); $this->assertFalse($perms->microphone); $this->assertFalse($perms->geolocation); $this->assertTrue($perms->clipboardWrite); } public function testUiResourcePermissionsFromArrayTreatsNullAsNotRequested(): void { // isset() rejects null values, so 'camera' => null must read as "not requested", // not as "requested with a null marker". // @phpstan-ignore-next-line — intentionally off-spec payload $perms = UiResourcePermissions::fromArray(['camera' => null, 'geolocation' => []]); $this->assertFalse($perms->camera); $this->assertTrue($perms->geolocation); } public function testUiResourceContentMetaSerialization(): void { $meta = new UiResourceContentMeta( csp: new UiResourceCsp(connectDomains: ['https://api.example.com']), permissions: new UiResourcePermissions(clipboardWrite: true), domain: 'example.com', prefersBorder: true, ); $serialized = $meta->jsonSerialize(); $this->assertArrayHasKey('csp', $serialized); $this->assertArrayHasKey('permissions', $serialized); $this->assertSame('example.com', $serialized['domain']); $this->assertTrue($serialized['prefersBorder']); } public function testUiResourceContentMetaOmitsNullFields(): void { $meta = new UiResourceContentMeta(prefersBorder: true); $serialized = $meta->jsonSerialize(); $this->assertArrayNotHasKey('csp', $serialized); $this->assertArrayNotHasKey('permissions', $serialized); $this->assertArrayNotHasKey('domain', $serialized); $this->assertArrayHasKey('prefersBorder', $serialized); } public function testUiResourceContentMetaFromArray(): void { $meta = UiResourceContentMeta::fromArray([ 'csp' => ['connectDomains' => ['https://api.example.com']], 'permissions' => ['clipboardWrite' => []], 'domain' => 'example.com', 'prefersBorder' => false, ]); $this->assertInstanceOf(UiResourceCsp::class, $meta->csp); $this->assertSame(['https://api.example.com'], $meta->csp->connectDomains); $this->assertInstanceOf(UiResourcePermissions::class, $meta->permissions); $this->assertTrue($meta->permissions->clipboardWrite); $this->assertSame('example.com', $meta->domain); $this->assertFalse($meta->prefersBorder); } public function testUiToolMetaSerialization(): void { $meta = new UiToolMeta( resourceUri: 'ui://my-app', visibility: [ToolVisibility::Model, ToolVisibility::App], ); $serialized = $meta->jsonSerialize(); $this->assertSame('ui://my-app', $serialized['resourceUri']); $this->assertSame(['model', 'app'], $serialized['visibility']); } public function testUiToolMetaOmitsNullFields(): void { $meta = new UiToolMeta(resourceUri: 'ui://my-app'); $serialized = $meta->jsonSerialize(); $this->assertArrayHasKey('resourceUri', $serialized); $this->assertArrayNotHasKey('visibility', $serialized); } public function testUiToolMetaFromArray(): void { $meta = UiToolMeta::fromArray([ 'resourceUri' => 'ui://my-app', 'visibility' => ['app'], ]); $this->assertSame('ui://my-app', $meta->resourceUri); $this->assertSame([ToolVisibility::App], $meta->visibility); } public function testToolVisibilityEnum(): void { $this->assertSame('model', ToolVisibility::Model->value); $this->assertSame('app', ToolVisibility::App->value); } public function testResourceMarkerSerializesToEmptyObject(): void { $marker = McpApps::resourceMarker(); $this->assertEquals(new \stdClass(), $marker); $this->assertSame('{}', json_encode($marker)); } }