Skip to content

[Server] Keep one unregistrable element from taking the whole registry down - #477

Open
wachterjohannes wants to merge 1 commit into
modelcontextprotocol:mainfrom
wachterjohannes:fix/resource-template-validation-at-registration
Open

[Server] Keep one unregistrable element from taking the whole registry down#477
wachterjohannes wants to merge 1 commit into
modelcontextprotocol:mainfrom
wachterjohannes:fix/resource-template-validation-at-registration

Conversation

@wachterjohannes

@wachterjohannes wachterjohannes commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #476.

A placeholder-less resource template made the server serve nothing. ReflectedElementLoader wrapped the failure in a ConfigurationException and rethrew it, which aborted Registry::load() before any element was registered. Loading is lazy by default, so that ran while a request was being served: tools/list and tools/call answered -32602 with the template's message, and since load() marks itself loaded only on success, every following request repeated it.

The loader now logs the failure and skips that element, the way Discoverer already treats an attribute it cannot process (Discoverer.php:300-310). The bad template is the only thing missing; the rest of the registry stays servable, and a server-side configuration error stops being reported to the client as invalid params on unrelated calls. All four element types carried the same rethrow, so all four change.

The ResourceTemplate message now also says what to do about it: a URI without a placeholder addresses a single resource, so it belongs in addResource().

Changed from the first revision

The first take validated the URI template in Builder::addResourceTemplate(). @chr-hertel pushed back on that, rightly: every schema invariant would end up mirrored in the builder alongside the value object that already owns it, and the ResourceTemplate::isValidUriTemplate() accessor existed only to make that mirroring possible. Both are gone. Nothing in Builder changed, and this revision fixes the reported path — the #[McpResourceTemplate] blast radius — rather than one doorway into it.

@chr-hertel chr-hertel added the Server Issues & PRs related to the Server component label Aug 23, 2026
Comment thread src/Server/Builder.php Outdated
): self {
if (!ResourceTemplate::isValidUriTemplate($uriTemplate)) {
throw new InvalidArgumentException(\sprintf('Invalid URI template "%s" for resource template handler %s: a template needs a scheme and at least one placeholder, e.g. "user://{userId}/profile". Use addResource() for a URI that addresses a single resource.', $uriTemplate, $this->describeHandler($handler)));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

bringing the validation logic into Builder is not a real option to me. even tho you tried to mitigate with that new isValidUriTemplate method, this still is a bit leaky and wouldn't scale nice - think of bringing all those validation+exception paths to the Builder ... 😬

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, and dropped — Builder is untouched now, and ResourceTemplate::isValidUriTemplate() is gone with it.

Chasing your point further: the validation never belonged at registration at all, because the reported case is #[McpResourceTemplate], which never goes through addResourceTemplate(). The real defect is that ReflectedElementLoader rethrows a failed element as a ConfigurationException and takes Registry::load() down with it, before anything is registered — under lazy loading that happens mid-request, so one bad template answers tools/list and tools/call with the template's message on every request.

Discoverer already has the answer (Discoverer.php:300-310): catch per element, log, move on. The loader now does the same for all four element types. One bad element costs you that element.

@wachterjohannes
wachterjohannes force-pushed the fix/resource-template-validation-at-registration branch from 69190d8 to 737ec3b Compare August 24, 2026 21:37
@wachterjohannes wachterjohannes changed the title [Server] Reject a placeholder-less resource template URI at registration [Server] Keep one unregistrable element from taking the whole registry down Aug 24, 2026
@wachterjohannes
wachterjohannes force-pushed the fix/resource-template-validation-at-registration branch from 737ec3b to 76fb3ec Compare August 24, 2026 21:40
…y down

A placeholder-less resource template made the server serve nothing. `ReflectedElementLoader`
wrapped the failure in a `ConfigurationException` and rethrew it, which aborted `Registry::load()`
before any element was registered. Loading is lazy by default, so that ran while a request was
being served: `tools/list` and `tools/call` answered `-32602` with the template's message, and
since `load()` marks itself loaded only on success, every following request repeated it.

The loader now logs the failure and skips that element, the way `Discoverer` already treats an
attribute it cannot process. The bad template is the only thing missing; the rest of the registry
stays servable, and a server-side configuration error stops being reported to the client as
invalid params on unrelated calls.

Applies to all four element types, which carried the same rethrow.

The `ResourceTemplate` message now also says what to do about it: a URI without a placeholder
addresses a single resource, so it belongs in `addResource()`.

Fixes modelcontextprotocol#476.
@wachterjohannes
wachterjohannes force-pushed the fix/resource-template-validation-at-registration branch from 76fb3ec to 12856e0 Compare August 24, 2026 21:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Server Issues & PRs related to the Server component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

One placeholder-less resource template makes the whole server unserviceable

2 participants