modelcontextprotocol / modelcontextprotocol/php-sdk

One placeholder-less resource template makes the whole server unserviceable

Aperta
#476 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Server
Lingua principale
PHP
Stelle
1.6k
Fork
173
Merge medio
2g 49m
PR unite (30g)
23

Descrizione

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.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia da Builder::addResourceTemplate(), quindi leggi ResourceTemplate.php e ReflectedElementLoader.php per capire dove l’URI non valida viene attualmente rifiutata. Segui Registry::load() e il percorso di caricamento lazy per confermare il momento in cui si verifica il fallimento. Il lavoro è completo quando un template senza placeholder viene rifiutato durante la registrazione anziché alla prima richiesta, senza impedire la registrazione di elementi validi.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
php
Ambito
backend-api-design
Tipo di issue
Bug
Difficoltà
3/5
Tempo stimato
1-2 giorni
Stato di attività
Attiva
Chiarezza
Specificata chiaramente
Idoneità per principianti
72/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.