[Server] Untangle origin tracking from Registry (#302)

Drop the isManual flag from ElementReference and the discovery-state methods
from RegistryInterface. The Registry becomes a flat last-write-wins map;
DiscoveryLoader owns its own owned-set bookkeeping so re-running discovery
only removes elements it itself contributed. Adds ChainLoader for explicit
composition. Manual-over-discovered precedence is preserved via loader
ordering, not a flag on the reference.
This commit is contained in:
Christopher Hertel 2026-05-17 14:03:31 +02:00 • committed by GitHub
commit 967c5cda4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 1003 additions and 338 deletions

View file

@ -0,0 +1,27 @@
<?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\Capability\Registry\Loader\Stub;
use Mcp\Capability\Discovery\DiscovererInterface;
use Mcp\Capability\Discovery\DiscoveryState;
final class MutableDiscoverer implements DiscovererInterface
{
public function __construct(public DiscoveryState $state)
{
}
public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = self::DEFAULT_NAME_PATERNS): DiscoveryState
{
return $this->state;
}
}

View file

@ -0,0 +1,30 @@
<?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\Capability\Registry\Loader\Stub;
use Mcp\Capability\Registry\Loader\LoaderInterface;
use Mcp\Capability\RegistryInterface;
final class RecordingLoader implements LoaderInterface
{
/**
* @param \ArrayObject<int, string> $calls
*/
public function __construct(private string $name, private \ArrayObject $calls)
{
}
public function load(RegistryInterface $registry): void
{
$this->calls->append($this->name);
}
}

View file

@ -0,0 +1,37 @@
<?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\Capability\Registry\Loader\Stub;
use Mcp\Capability\Registry\Loader\LoaderInterface;
use Mcp\Capability\RegistryInterface;
use Mcp\Schema\Tool;
final class ToolWriterLoader implements LoaderInterface
{
public function __construct(private string $toolName, private \Closure $handler)
{
}
public function load(RegistryInterface $registry): void
{
$registry->registerTool(new Tool(
name: $this->toolName,
title: null,
inputSchema: ['type' => 'object', 'properties' => [], 'required' => null],
description: null,
annotations: null,
icons: null,
meta: null,
outputSchema: null,
), $this->handler);
}
}