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
@chr-hertel chr-hertel added Server Issues & PRs related to the Server component 2026-07-28 All issues and PRs related to the spec release 2026-07-28 labels Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2026-07-28 All issues and PRs related to the spec release 2026-07-28 Server Issues & PRs related to the Server component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants