Skip to content

One placeholder-less resource template makes the whole server unserviceable #476

Description

@wachterjohannes

Reported by Mariano Damian Ferro Villanueva via email, opening it here on their behalf.

Problem

A #[McpResourceTemplate] whose uriTemplate contains 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:

tools/list -> {"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"Error registering manual resource template 'data://tags': Invalid URI template : \"data://tags\" must be a valid URI template with at least one placeholder."}}
tools/call -> {"jsonrpc":"2.0","id":3,"error":{"code":-32602,"message":"Error registering manual resource template 'data://tags': Invalid URI template : \"data://tags\" must be a valid URI template with at least one placeholder."}}

Reproduced on main against Server::builder() with the Streamable HTTP transport, on both the handshake and the 2026-07-28 lifecycle.

Cause

  1. ResourceTemplate::__construct() requires at least one placeholder and throws (src/Schema/ResourceTemplate.php#L59-L61).
  2. ReflectedElementLoader wraps that into a ConfigurationException (ReflectedElementLoader.php#L214-L219), which aborts Registry::load() before any element is registered.
  3. Builder::$lazyLoading defaults to true, so that load runs on the first read during request handling. Registry::load() sets loaded only 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 on main, 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:

  • ReflectedElementLoader throws ConfigurationException, 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 ConfigurationException reaching a client as -32602 is misleading (it is caught by catch (\InvalidArgumentException) in Protocol), 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions