[Schema][Server] Add the extensions framework SEP-2133 defines - #443
[Schema][Server] Add the extensions framework SEP-2133 defines#443chr-hertel wants to merge 2 commits into
Conversation
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.
There was a problem hiding this comment.
Pull request overview
Adds the server-side “extensions framework” described by SEP-2133: extension identifiers are validated, and extensions can now contribute method message classes (so requests can be decoded) and request handlers (so requests can be served).
Changes:
- Add
ExtensionIdentifiervalidation and apply it inServer\Builder::enableExtension(). - Introduce
MethodProvidingExtensionInterfaceso extensions can register message classes and handlers; wire these intoMessageFactory::make()via anadditionallist. - Update typing docs for covariant result generics and add unit tests/fixtures covering identifier validation and method-providing extensions.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Server/Builder.php | Validates extension identifiers; collects extension message classes and handlers; passes extension messages into MessageFactory::make(). |
| src/Schema/Extension/ExtensionIdentifier.php | Defines SEP-2133 identifier validation and reserved-label detection. |
| src/Schema/Extension/MethodProvidingExtensionInterface.php | New interface for extensions that add methods, exposing message classes and handlers. |
| src/JsonRpc/MessageFactory.php | Extends make() to accept additional request/notification message classes. |
| src/Server/Handler/Request/RequestHandlerInterface.php | Marks handler result template covariant (PHPDoc) and documents rationale. |
| src/Schema/JsonRpc/Response.php | Marks response result template covariant (PHPDoc) and documents rationale. |
| tests/Unit/Server/BuilderTest.php | Adds unit tests verifying identifier validation and extension-contributed messages/handlers. |
| tests/Unit/Schema/Extension/ExtensionIdentifierTest.php | Adds unit tests for identifier validation and reserved-label recognition. |
| tests/Unit/Server/Extension/ThingExtension.php | Minimal method-providing extension fixture for unit tests. |
| tests/Unit/Server/Extension/ThingListHandler.php | Fixture handler serving the extension method. |
| tests/Unit/Server/Extension/ThingListRequest.php | Fixture request class defining the extension method. |
| tests/Unit/Server/Extension/ThingListResult.php | Fixture result class for the extension method. |
| CHANGELOG.md | Documents the new extensions framework and related typing changes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| * The two halves are declared separately because they answer different | ||
| * questions: the handlers say how a claimed method is served, and | ||
| * {@see self::getMethods()} says which methods exist at all — which is what | ||
| * lets a server distinguish "this extension is not enabled here" from "no such | ||
| * method", instead of answering `-32601` to both. |
| public function testEnableExtensionRejectsUnprefixedIdentifier(): void | ||
| { | ||
| $this->expectException(LogicException::class); | ||
| $this->expectExceptionMessage('Invalid extension identifier'); |
There was a problem hiding this comment.
Not a bug — expectExceptionMessage() does substring matching (ExceptionMessageIsOrContains), not exact-match, so this partial-string assertion against the full "Invalid extension identifier: ..." message is correct as written.
enableExtension()took any string and advertised it, with no way for an extension to add a method:MessageFactorycould not decode one, so nothing downstream ever saw it.Identifiers are validated.
Builder::enableExtension()checks the identifier against the_metakey naming rules through the newMcp\Schema\Extension\ExtensionIdentifier— a prefix is mandatory, since an unprefixed name has no owner — which also recognises themodelcontextprotocol/mcpsecond labels the specification reserves.An extension can contribute a method. An extension implementing the new
MethodProvidingExtensionInterfacecontributes both the message classes its methods decode into (registered withMessageFactory, without which the methods cannot be decoded at all) and the handlers serving them.Supporting changes:
MessageFactory::make()takes an$additionallist of message classes, andRequestHandlerInterface's result template is now covariant, so a handler declaring a concrete result satisfies a collection typed by the interface.This is the framework SEP-2133 defines — what MCP Apps already sits on, and what the Tasks extension (#428) needs.