[Schema][Server] Add the extensions framework SEP-2133 defines (#443)
* [Schema][Server] Add the extensions framework SEP-2133 defines enableExtension() took any string and advertised it, with no way for an extension to add a method: MessageFactory could not decode one, so nothing downstream ever saw it. Identifiers are now checked against the _meta key naming rules through ExtensionIdentifier - a prefix is mandatory, since an unprefixed name has no owner, and the modelcontextprotocol/mcp second labels the specification reserves are recognised. An extension implementing MethodProvidingExtensionInterface contributes both its message classes and the handlers serving them. MessageFactory::make() takes an $additional list of message classes, and RequestHandlerInterface's result template is covariant so a handler declaring a concrete result satisfies a collection typed by the interface. * Fix stale @see reference in MethodProvidingExtensionInterface docblock * [Schema][Server][Client] Make ExtensionIdentifier a value object ExtensionInterface::getId() now returns ExtensionIdentifier, which validates the SEP-2133 naming rules at construction instead of the callers checking it. getMessages()/getRequestHandlers() move into ExtensionInterface itself; AbstractExtension gives them empty defaults for extensions that only announce a capability.
This commit is contained in:
parent
5e3e69acc4
commit
60711951d4
1 changed files with 525 additions and 17 deletions
|
|
@ -32,6 +32,7 @@ use Mcp\Exception\LogicException;
|
|||
use Mcp\JsonRpc\MessageFactory;
|
||||
use Mcp\Schema\Annotations;
|
||||
use Mcp\Schema\Enum\ProtocolVersion;
|
||||
use Mcp\Schema\Extension\AbstractExtension;
|
||||
use Mcp\Schema\Extension\ExtensionInterface;
|
||||
use Mcp\Schema\Icon;
|
||||
use Mcp\Schema\Implementation;
|
||||
|
|
@ -214,6 +215,9 @@ final class Builder
|
|||
*/
|
||||
private array $extensions = [];
|
||||
|
||||
/** @var list<class-string<\Mcp\Schema\JsonRpc\Request>|class-string<\Mcp\Schema\JsonRpc\Notification>> */
|
||||
private array $extensionMessages = [];
|
||||
|
||||
/**
|
||||
* @var LoaderInterface[]
|
||||
*/
|
||||
|
|
@ -280,18 +284,33 @@ final class Builder
|
|||
* Enable one or more MCP protocol extensions, announced to clients under
|
||||
* `capabilities.extensions` during the initialize handshake.
|
||||
*
|
||||
* @throws LogicException if the same extension is enabled more than once
|
||||
* An extension also contributes the message classes its methods decode
|
||||
* into and the handlers serving them, if any — see {@see AbstractExtension}
|
||||
* for extensions that only announce a capability.
|
||||
*
|
||||
* @throws InvalidArgumentException if the identifier is not a valid `_meta` prefix
|
||||
* @throws LogicException if the same extension is enabled more than once
|
||||
*/
|
||||
public function enableExtension(ExtensionInterface ...$extensions): self
|
||||
{
|
||||
foreach ($extensions as $extension) {
|
||||
$id = $extension->getId();
|
||||
$id = (string) $extension->getId();
|
||||
|
||||
if (isset($this->extensions[$id])) {
|
||||
throw new LogicException(\sprintf('Extension "%s" is already enabled.', $id));
|
||||
}
|
||||
|
||||
$this->extensions[$id] = $extension->getCapabilities();
|
||||
|
||||
// Without this the method cannot be decoded at all, so nothing
|
||||
// downstream ever sees it.
|
||||
foreach ($extension->getMessages() as $message) {
|
||||
$this->extensionMessages[] = $message;
|
||||
}
|
||||
|
||||
foreach ($extension->getRequestHandlers() as $handler) {
|
||||
$this->requestHandlers[] = $handler;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -709,7 +728,7 @@ final class Builder
|
|||
$eagerlyLoaded = !$this->lazyLoading;
|
||||
}
|
||||
|
||||
$messageFactory = MessageFactory::make();
|
||||
$messageFactory = MessageFactory::make(additional: $this->extensionMessages);
|
||||
|
||||
$capabilities = $this->serverCapabilities ?? $this->detectCapabilities($registry, $eagerlyLoaded);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue