Skip to content

[Server] Serve an extension's methods under the modern lifecycle - #454

Merged
chr-hertel merged 2 commits into
modelcontextprotocol:mainfrom
chr-hertel:pr/2026-extension-methods
Aug 18, 2026
Merged

[Server] Serve an extension's methods under the modern lifecycle#454
chr-hertel merged 2 commits into
modelcontextprotocol:mainfrom
chr-hertel:pr/2026-extension-methods

Conversation

@chr-hertel

Copy link
Copy Markdown
Member

Stacked on #453.

Builder already collects an extension's message classes and handlers (#443). This adds the method-to-extension map alongside them and hands it to the modern dispatcher, so a method belonging to an extension this server does not serve is answered -32601 naming the extension instead of a bare "no handler found".

It is still an unknown method — the server genuinely does not implement it — but the caller can act on the answer rather than guessing.

One test moved: StatelessProtocolTest had a case asserting a malformed extension identifier is refused at build time. Since #443 made ExtensionInterface::getId() return an ExtensionIdentifier, that validation now happens in the value object and is already covered by BuilderTest::testEnableExtensionRejectsUnprefixedIdentifier, so the duplicate is dropped.


Cross-fork PRs can only target main, so this diff also carries its ancestors until they merge. Only the last commit(s) belong to this PR — GitHub's "Commits" tab separates them.

The modern dispatcher takes the method-to-extension map the builder collects,
so a method belonging to an extension this server does not serve is answered
-32601 naming the extension instead of a bare "no handler found". It is still
an unknown method - the server genuinely does not implement it - but the
caller can now act on the answer.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds richer “unknown method” behavior for the modern/stateless server lifecycle by tracking which extension defines each RPC method, enabling more actionable -32601 errors when a known extension method is not served.

Changes:

  • Track RPC method → extension identifier mappings in Server\Builder and pass them into the stateless protocol.
  • Centralize stateless “unknown method” error formatting to optionally name the owning extension.
  • Add unit tests covering extension method serving, extension advertising, and unknown-method messaging behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
tests/Unit/Server/Stateless/StatelessProtocolTest.php Adds stateless lifecycle tests for extension methods, advertised capabilities, and unknown-method messaging.
src/Server/Stateless/StatelessProtocol.php Uses an injected method→extension map to produce more specific method-not-found errors.
src/Server/Builder.php Records method→extension ownership while enabling extensions and passes that into the stateless dispatcher.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Server/Builder.php
Comment on lines 410 to 415
foreach ($extension->getMessages() as $message) {
$this->extensionMessages[] = $message;
// Recorded even though the handler answers it, so a server with
// the extension *off* can say so instead of "no such method".
$this->extensionMethods[$message::getMethod()] = $id;
}
* server genuinely does not implement it — but naming the extension turns
* an opaque refusal into something the caller can act on.
*/
private function unknownMethod(string $method, string|int $id): Error
Comment on lines +825 to +826
#[TestDox('a method of an extension this server does not serve says so by name')]
public function testDisabledExtensionMethodNamesItsExtension(): void
The message factory resolves a contested method to whichever class
registered first, while extensionMethods kept the last one — so error
messages could name the wrong extension. Also fixes a test that
claimed to prove a method gets named by its extension while asserting
the opposite; the case it meant to cover (an enabled extension with no
handler for one of its methods) had no coverage at all.
@chr-hertel
chr-hertel merged commit b973893 into modelcontextprotocol:main Aug 18, 2026
25 checks passed
@chr-hertel
chr-hertel deleted the pr/2026-extension-methods branch August 18, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants