[Schema][Client][Server] Close MCP Apps gaps: extension negotiation checks (#429)

* [Schema][Client][Server] Close MCP Apps gaps: extension negotiation checks

Add ClientGateway::supportsExtension(), Client\Builder::enableExtension() and
ClientCapabilities::withExtensions(); introduce side-agnostic ExtensionInterface
and deprecate ServerExtensionInterface; document attribute-based UI linkage.

* Drop ServerExtensionInterface in favour of ExtensionInterface
This commit is contained in:
Christopher Hertel 2026-08-18 01:46:53 +02:00 • committed by GitHub
commit 6f58fe596a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -13,6 +13,9 @@ namespace Mcp\Tests\Unit\Client;
use Mcp\Client;
use Mcp\Client\Configuration;
use Mcp\Exception\LogicException;
use Mcp\Schema\ClientCapabilities;
use Mcp\Schema\Extension\Apps\McpApps;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase;
@ -43,6 +46,41 @@ final class BuilderTest extends TestCase
$this->assertNull($this->extractConfiguration($client)->clientInfo->title);
}
#[TestDox('enableExtension() announces the extension under capabilities.extensions')]
public function testEnableExtensionAdvertisesExtension(): void
{
$client = Client::builder()
->enableExtension(new McpApps())
->build();
$capabilities = $this->extractConfiguration($client)->capabilities;
$this->assertSame([McpApps::EXTENSION_ID => ['mimeTypes' => [McpApps::MIME_TYPE]]], $capabilities->extensions);
}
#[TestDox('enableExtension() extensions are merged into capabilities set via setCapabilities()')]
public function testEnableExtensionMergesIntoCustomCapabilities(): void
{
$client = Client::builder()
->setCapabilities(new ClientCapabilities(elicitation: true, extensions: ['io.example/other' => []]))
->enableExtension(new McpApps())
->build();
$capabilities = $this->extractConfiguration($client)->capabilities;
$this->assertTrue($capabilities->elicitation);
$this->assertSame(['io.example/other', McpApps::EXTENSION_ID], array_keys($capabilities->extensions ?? []));
}
#[TestDox('enableExtension() throws when the same extension is enabled twice')]
public function testEnableExtensionRejectsDuplicate(): void
{
$this->expectException(LogicException::class);
$this->expectExceptionMessage(McpApps::EXTENSION_ID);
Client::builder()->enableExtension(new McpApps(), new McpApps());
}
private function extractConfiguration(Client $client): Configuration
{
$config = (new \ReflectionClass($client))->getProperty('config')->getValue($client);