modelcontextprotocol / modelcontextprotocol/python-sdk

Duplicate `initialize` with changed parameters can overwrite `ServerSession.client_params`

Aperta
#2,605 4 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

bug fix proposed P2 ready for work
Lingua principale
Python
Stelle
24.3k
Fork
4k
Merge medio
1g 1h
PR unite (30g)
31

Descrizione

Initial Checks
Description

After a normal MCP initialization flow, a Python SDK server accepts another initialize request on the same live connection/session and returns a normal initialize result.

I agree that the spec does not explicitly say every repeated initialize must be rejected. My concern is narrower: after the session has already moved into normal operation, a later same-session initialize with different parameters can update the server's stored client initialization parameters.

The MCP lifecycle documentation describes initialization as the first client/server interaction and says the operation phase uses the negotiated protocol version and capabilities. In that model, silently changing the stored client identity/capabilities/protocol version after operation has begun seems surprising unless the SDK intentionally supports same-session renegotiation.

  • src/mcp/server/session.py stores the client initialization request in ServerSession._client_params.
  • ServerSession.client_params exposes that value.
  • In both the stable release and the main snapshot I tested, the InitializeRequest handler assigns self._client_params = params.
  • ServerSession.check_client_capability() later reads self._client_params.capabilities, so a duplicate initialize with different capabilities can affect capability-gated server logic.
What I observed

Reproduced on stable v1.27.1 (77431ebe7dda9ed0c61451b22d3e7f8d981bc092):

  • stdio: a second initialize was accepted after notifications/initialized; a later ping still succeeded.
  • stateful Streamable HTTP: a second initialize was accepted after notifications/initialized; a later ping still succeeded.
  • stateless Streamable HTTP: a second initialize was accepted after notifications/initialized; a later ping still succeeded.

For stateful Streamable HTTP on v1.27.1, I added a local diagnostic tool that reads ctx.session.client_params before and after the duplicate initialize on the same session:

Duplicate initialize parameters Observed result
Same parameters No state change
Changed clientInfo and capabilities client_params.clientInfo and client_params.capabilities changed
Older protocolVersion (2024-11-05) client_params.protocolVersion and client_params.clientInfo changed

On the frozen main snapshot (161834d4aee2633c42d3976c8f8751b6c4d947d5), the same duplicate-initialize acceptance still reproduced. My diagnostic snapshot directly observed client_params.capabilities changing after a duplicate initialize with changed capabilities on both stdio and stateful Streamable HTTP.

Expected behavior

If same-session duplicate initialization is not intended, I would expect the server to reject the later initialize after normal operation has begun, for example with a JSON-RPC -32600 Invalid Request error.

If this tolerant behavior is intentional, I think it should be either:

  • a no-op once the session has already initialized, especially for changed parameters, or
  • documented as same-session renegotiation, including whether ServerSession.client_params, client capabilities, client identity, and protocol version are allowed to change.
Example Code
import json

try:
    # Current main branch naming.
    from mcp.server.mcpserver import Context, MCPServer
except ImportError:
    # Latest stable release naming.
    from mcp.server.fastmcp import Context, FastMCP as MCPServer


mcp = MCPServer("dup-init-repro")


def field(obj, *names):
    if obj is None:
        return None
    for name in names:
        if hasattr(obj, name):
            return getattr(obj, name)
    return None


def dump(value):
    if value is None:
        return None
    if hasattr(value, "model_dump"):
        return value.model_dump(mode="json", by_alias=True)
    return value


@mcp.tool()
async def session_snapshot(ctx: Context) -> str:
    params = ctx.session.client_params
    return json.dumps(
        {
            "protocolVersion": field(params, "protocolVersion", "protocol_version"),
            "clientInfo": dump(field(params, "clientInfo", "client_info")),
            "clientCapabilities": dump(field(params, "capabilities")),
        },
        separators=(",", ":"),
        default=str,
    )


if __name__ == "__main__":
    mcp.run(transport="streamable-http")

'''
Request sequence:

1. Start the server.
2. Send a normal `initialize`.
3. Send `notifications/initialized`.
4. Call the `session_snapshot` tool and record the returned JSON.
5. Send another `initialize` on the same live session, but change `clientInfo` and/or `capabilities`.
6. Call `session_snapshot` again on the same session.

On `v1.27.1`, the second snapshot reflects the changed `clientInfo` / `capabilities`. On the `main` snapshot I tested, the second snapshot still reflects changed `capabilities`.
'''
Python & MCP Python SDK
- Python: `3.12.3`
- MCP Python SDK: `mcp==1.27.1` (`v1.27.1`, `77431ebe7dda9ed0c61451b22d3e7f8d981bc092`)
- Also reproduced on `main` snapshot `161834d4aee2633c42d3976c8f8751b6c4d947d5` (`v1.25.0-159-g161834d4`, pinned 2026-05-11)

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 src/mcp/server/session.py, in particolare dall'handler di InitializeRequest e da ServerSession.client_params. Riproduci la sequenza di inizializzazione duplicata descritta nell'issue sia su stdio sia su Streamable HTTP stateful o stateless, quindi esamina come vengono letti successivamente i parametri del client. Il lavoro è completato quando il comportamento scelto per l'inizializzazione duplicata è coerente e non vengono sovrascritti parametri del client in modo non intenzionale.

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

Valutazione

Stack tecnologico
python
Ambito
api, backend
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
48/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.