Repository navigation
One placeholder-less resource template makes the whole server unserviceable #476
Description
Activity
- changed the title
[-]Resource template configuration errors are swallowed by the streamed HTTP response[/-][+]One placeholder-less resource template makes the whole server unserviceable[/+]on Aug 23, 2026 wachterjohannes commented
on Aug 23, 2026 ContributorAuthorMore actionsUpdated the description after reproducing it: the SDK does turn this into a JSON-RPC error rather than letting it escape, so the original "swallowed by the streamed response" framing was wrong. The real problem is the blast radius, one bad template makes
tools/listandtools/callfail too.- added 3 commits that reference this issue
on Aug 24, 2026 i'm a bit torn here if this really needs a code fix - mostly because it is not that easy.
in the end it feels fair: user land code is wrong, execution fails - error message give a hint even.i understand that it is inconvenient to be late, but elevating the logic into the builder (solution #1) or silently dropping all of those issues (solution #2) is too big of a trade off to me ... unfortunately i don't have a good counter idea at this point, sorry
- addedServerIssues & PRs related to the Server componentIssues & PRs related to the Server component
on Aug 24, 2026 wachterjohannes commented
on Aug 25, 2026 ContributorAuthorMore actionsyeah, fine to leave the blast radius open for now, i don't have a better idea either and i agree that both options cost more than they fix.
one piece i'd split off though, independent of that decision:
ConfigurationException extends InvalidArgumentException, soProtocolcatches it in the\InvalidArgumentExceptionarm and answers-32602with the full message. every other unexpected throwable is masked as "Internal server error." right below, so this is the one case where the client both gets blamed for something it cannot fix and gets handed the handler and uri template. catchingConfigurationExceptionbefore that arm and mapping it toforInternalErroris a few lines and doesn't touch the loading design at all.for the symfony side i'm relaxed anyway,
debug:mcpreads the registry so it triggers the load, and the developer gets the message on the cli before any client connects.
Reported by Mariano Damian Ferro Villanueva via email, opening it here on their behalf.
Problem
A
#[McpResourceTemplate]whoseuriTemplatecontains no placeholder takes the whole server down, not just that one template.#[McpResourceTemplate( uriTemplate: 'data://tags', name: 'all_tags', title: 'All Tags', description: 'All Tags', mimeType: 'application/json' )] public function tag_all(?int $paged = 1): array { // ... }The handshake still succeeds, and then every request fails, including ones that have nothing to do with resources:
Reproduced on
mainagainstServer::builder()with the Streamable HTTP transport, on both the handshake and the 2026-07-28 lifecycle.Cause
ResourceTemplate::__construct()requires at least one placeholder and throws (src/Schema/ResourceTemplate.php#L59-L61).ReflectedElementLoaderwraps that into aConfigurationException(ReflectedElementLoader.php#L214-L219), which abortsRegistry::load()before any element is registered.Builder::$lazyLoadingdefaults totrue, so that load runs on the first read during request handling.Registry::load()setsloadedonly on success, so every subsequent request retries and fails the same way.So one misconfigured element is enough to make a server serve nothing, and the client is told
-32602(Invalid params) for what is a server-side configuration error it cannot do anything about.The original report saw it as a 500 with
Cannot modify header information - headers already sent (output started at /vendor/symfony/http-foundation/Response.php:393), which is how it surfaces once the response is already being written. I could not reproduce that part onmain, so treat it as a symptom of the surrounding stack rather than part of this issue.Secondary issue: inconsistent handling
The same mistake behaves differently depending on the registration path:
ReflectedElementLoaderthrowsConfigurationException, a hard failure taking down the registry.Discoverer::processFile()catches\Throwable, logs it and continues, so an attribute-discovered template with the same mistake is silently dropped.Suggested fix
Validate the URI template in
Builder::addResourceTemplate(), where the developer wrote it, instead of at the first read of the registry. PR follows.Worth considering separately: a
ConfigurationExceptionreaching a client as-32602is misleading (it is caught bycatch (\InvalidArgumentException)inProtocol), and a single failing element aborting the whole registry load is a large blast radius for any other configuration mistake.Line references are against
main.