StreamableHTTP: a client disconnect while reading the POST body is logged as an exception and answered 500

Aberta Para iniciantes
#3,469 2 comentários 0 reações 0 responsáveis Ver no GitHub

Ninguém assumiu esta issue ainda.

Avaliação

Dificuldade
2/5
Tempo estimado
1-3 horas
Facilidade para iniciantes
78/100
Tipo de issue
Bug
Clareza
Claramente especificada
Status de atividade
Ativa
Stack de tecnologia
python
Domínio
api, backend

Direção de pesquisa

Comece em mcp/server/streamable_http.py, em StreamableHTTPServerTransport._handle_post_request, especialmente na linha body = await request.body() e no manipulador genérico de exceções que vem em seguida. Compare o tratamento existente de desconexões em _handle_message, referenciado por #2064 e #2257. Considera-se concluído quando ClientDisconnect é tratado sem um stack trace de erro ou uma resposta 500, e a cobertura de regressão verifica esse comportamento.

Escrita pelo modelo de indexação a partir do texto da issue.

Descrição

v1 v2
Initial Checks
Release line

2.x (current stable), and the same on 1.28.1

Description

StreamableHTTPServerTransport._handle_post_request reads the request body inside its blanket except Exception. When the client goes away before the body arrives, await request.body() raises starlette.requests.ClientDisconnect, the transport logs it with logger.exception("Error handling POST request") and answers 500. That is a normal network event on a public MCP endpoint (mobile clients, proxies with short timeouts, health probes), and it lands as an unhandled server error with a stack trace in every log aggregator. #2064 / #2257 handled the disconnect in _handle_message; this is the earlier point, in streamable_http.py (the body = await request.body() line and the except Exception as err below it). The SSE transport has no handling for it either.

Example Code
import asyncio
import importlib.metadata as md
import logging

from mcp.server.streamable_http import StreamableHTTPServerTransport

records = []


class Capture(logging.Handler):
    def emit(self, record):
        records.append(record)


logging.getLogger("mcp").addHandler(Capture())
logging.getLogger("mcp").setLevel(logging.DEBUG)


async def main():
    transport = StreamableHTTPServerTransport(mcp_session_id=None)
    scope = {
        "type": "http", "method": "POST", "path": "/mcp", "query_string": b"", "headers": [
            (b"content-type", b"application/json"),
            (b"accept", b"application/json, text/event-stream"),
            (b"content-length", b"58"),
        ],
    }

    async def receive():  # the client went away before sending its body
        return {"type": "http.disconnect"}

    sent = []

    async def send(message):
        sent.append(message)

    async with transport.connect():
        try:
            await asyncio.wait_for(transport.handle_request(scope, receive, send), timeout=5)
        except asyncio.TimeoutError:
            print("handle_request did not return within 5s")
    status = next((m["status"] for m in sent if m["type"] == "http.response.start"), None)
    print(f"mcp {md.version('mcp')}: response status = {status}")
    for r in records:
        if r.levelno >= logging.ERROR:
            print(f"  logged {r.levelname} by {r.name}: {r.getMessage()} | exc: {type(r.exc_info[1]).__name__ if r.exc_info else None}")


try:
    asyncio.run(asyncio.wait_for(main(), timeout=15))
except asyncio.TimeoutError:
    print("main did not finish within 15s")

Output on 1.28.1 and 2.1.1:

mcp 2.1.1: response status = 500
  logged ERROR by mcp.server.streamable_http: Error handling POST request | exc: ClientDisconnect

(The script caps handle_request at 5 s because the stub send / receive is not a real ASGI server; the 500 and the log line are sent before that.)

Expected: ClientDisconnect caught before the generic handler, logged at debug or info, and no response attempted, since there is nobody to answer.

Python & MCP Python SDK

Python 3.12.13, mcp 1.28.1 and 2.1.1, starlette 1.3.1, macOS

Linguagem predominante
Python
Estrelas
24.3k
Forks
4k
Merge médio
1d 19min
PRs com merge (30d)
29

Guia de contribuição

Abrir o guia de contribuição

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Mais de modelcontextprotocol/python-sdk

Todas as issues de modelcontextprotocol/python-sdk

Issues semelhantes

Mais issues de Python

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.