[Server] Serve an extension's methods under the modern lifecycle - #454
Merged
chr-hertel merged 2 commits intoAug 18, 2026
Merged
Conversation
chr-hertel
requested review from
CodeWithKyrian,
Nyholm and
soyuka
as code owners
August 18, 2026 19:10
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.
chr-hertel
force-pushed
the
pr/2026-extension-methods
branch
from
August 18, 2026 22:48
03ad564 to
2bd9bc4
Compare
Contributor
There was a problem hiding this comment.
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\Builderand 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 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #453.
Builderalready 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-32601naming 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:
StatelessProtocolTesthad a case asserting a malformed extension identifier is refused at build time. Since #443 madeExtensionInterface::getId()return anExtensionIdentifier, that validation now happens in the value object and is already covered byBuilderTest::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.