Repository navigation
Make server/discover a request handler so servers can customize it #621
Description
StatelessProtocol answers server/discover itself.
It checks the method name before dispatch and builds the DiscoverResult in a private method from the supported versions, Configuration::$capabilities and Configuration::$instructions.
Every other request, initialize included, goes through the request handlers instead.
That has two consequences:
- A server cannot change the answer.
initializeis answered byInitializeHandler, which a handler added withBuilder::addRequestHandler()can replace, because custom handlers run before the defaults.
There is no such handler forserver/discover, so the only way to change its result is to rewrite the HTTP response afterServer::run(), which depends on the wire format and has to skip streamed responses. server/discoverskips what every other method goes through:RequestEvent,ResponseEventandErrorEventare not dispatched for it, and a failure is not mapped to an error the way a handler's failure is.
Use case
A server that serves both eras from one endpoint may need a different server/discover answer than its initialize answer.
For example, a server whose tools enable more tools for the current session cannot offer those on 2026-07-28, which has no session.
Its instructions are shared by both eras today, so they tell stateless clients to call tools that are not listed and cannot work.
Proposal
Answer server/discover with a default DiscoverHandler, like initialize is answered with InitializeHandler:
DiscoverHandlerreturns the sameDiscoverResultas today.
The modern dispatcher registers it after the custom handlers, so a handler added withaddRequestHandler()that supportsDiscoverRequestanswers instead.server/discovergoes through the normal dispatch and its events, and keeps its wire behaviour: same status codes and header checks, never a stream (also when the client acceptstext/event-stream), and not reachable from the handshake era.
A server can then register its own discover handler, or adjust the default answer in a ResponseEvent listener.
Alternative considered
A second configuration field, for example Builder::setModernInstructions() used by server/discover and falling back to setInstructions().
It solves the instructions case only: every other member that may differ per era (capabilities, for example) would need its own field, and Configuration would copy each public attribute per era.
A handler covers all of them with the extension point the SDK already has for initialize.
Context
Shopware serves both eras from its MCP endpoints and hides its session-bound toolset tools on 2026-07-28.
It currently swaps the instructions in the HTTP response as a workaround (shopware/shopware#22011) and would drop that once server/discover can be customized.
Activity