mcp-http-streaming-bug-repro

Abelin101/mcp-http-streaming-bug-repro

3.2

If you are the rightful owner of mcp-http-streaming-bug-repro and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.

This document provides a structured overview of a minimal HTTP MCP server designed to reproduce a ChatGPT UI streaming bug.

Tools
1
Resources
0
Prompts
0

ChatGPT HTTP MCP Bug: Tools execute successfully but UI shows “Error in message stream” (Minimal Repro Included)

Summary

I’ve isolated a reproducible bug in ChatGPT’s HTTP MCP message streaming pipeline.

When ChatGPT connects to an HTTP MCP server via a publicly accessible URL (ngrok), it:

  • Connects successfully
  • Discovers the tools successfully (ListToolsRequest)
  • Sends CallToolRequest successfully
  • My MCP server executes the tool successfully
  • My MCP server returns a valid response
  • BUT the ChatGPT UI fails to render the result and instead shows:

This issue persists even with a minimal 60-line MCP server, no external dependencies, and a single trivial echo tool.
Logs clearly show that the tool executes and returns normally, which confirms the issue is on the ChatGPT UI side.

This is not specific to my project — it reproduces 100% on a minimal FastAPI MCP server.


Environment

  • ChatGPT Model: ChatGPT 5.1 (Developer Mode)
  • MCP Type: HTTP MCP
  • Tunnel: ngrok http 9098
  • Client Browsers tested: Chrome, Edge, Opera — normal & incognito
  • Server: FastAPI + FastApiMCP (from fastapi-mcp)
  • OS: Windows 11 + WSL2 (Ubuntu 24.04)
  • Python: 3.10
  • FastAPI: Latest
  • MCP SDK: Latest fastapi-mcp release

Minimal MCP Server (single tool)

Here is the entire reproducible MCP server (slim_server.py).
This is the full server — nothing omitted.

Click to expand the minimal server code
# slim_server.py - minimal reproducible HTTP MCP server

import asyncio
import json
import logging
from typing import Any, Dict

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse, PlainTextResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

from fastapi_mcp import FastApiMCP   # <-- the same import used in the SDK examples

# -------------------------------------------------------------------
# Logging
# -------------------------------------------------------------------

log = logging.getLogger("minimal_mcp")
log.setLevel(logging.INFO)

if not log.handlers:
    sh = logging.StreamHandler()
    sh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
    log.addHandler(sh)

logging.getLogger("mcp.server.lowlevel.server").setLevel(logging.INFO)

# -------------------------------------------------------------------
# FastAPI app
# -------------------------------------------------------------------

app = FastAPI(title="Minimal MCP Server", version="0.0.1")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# -------------------------------------------------------------------
# Health check
# -------------------------------------------------------------------

@app.get("/healthz")
async def healthz():
    return PlainTextResponse("ok", status_code=200)

# -------------------------------------------------------------------
# Request logging middleware (verbose for MCP)
# -------------------------------------------------------------------

@app.middleware("http")
async def log_requests(request: Request, call_next):
    body_bytes = await request.body()
    path = request.url.path
    headers = {k: v for k, v in request.headers.items()}

    decoded = body_bytes.decode("utf-8", "ignore")

    if path.startswith("/mcp"):
        log.info(f"HTTP {request.method} {path} headers={headers} body={decoded!r}")

    response = await call_next(request)

    log.info(
        f"HTTP {request.method} {path} -> {response.status_code} "
        f"({response.headers.get('content-type')})"
    )
    return response

# -------------------------------------------------------------------
# Minimal tool: echo
# -------------------------------------------------------------------

class EchoRequest(BaseModel):
    message: str

class EchoResponse(BaseModel):
    message: str

@app.post(
    "/echo",
    operation_id="echo",
    response_model=EchoResponse,
    tags=["mcp", "test"],
)
async def echo_tool(body: EchoRequest) -> EchoResponse:
    log.info(f"echo_tool called with message={body.message!r}")
    return EchoResponse(message=f"echo: {body.message}")

# -------------------------------------------------------------------
# MCP Mount
# -------------------------------------------------------------------

mcp_http = FastApiMCP(
    app,
    name="Minimal MCP Repro",
    description="A minimal HTTP MCP server for reproducing ChatGPT UI streaming bug.",
    include_operations=["echo"],
)

mcp_http.mount()  # mounts /mcp and /mcp/messages
log.info("Mounted HTTP MCP endpoint at /mcp")

# -------------------------------------------------------------------
# Run app
# -------------------------------------------------------------------

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "ops.mcp.slim_server:app",  # adjust to your module path
        host="127.0.0.1",
        port=9098,
        reload=True,
        log_level="info",
    )

</details>

Reproduction Steps

Run the MCP server above.

Expose it via ngrok:

ngrok http 9098


Example URL:

https://adela-unlaudable-nondidactically.ngrok-free.dev


In ChatGPT → GPT Builder → Add Tool → MCP Server (HTTP):

https://<my-ngrok-url>.ngrok-free.dev/mcp


ChatGPT successfully connects and shows the echo tool.

Open a new chat with the custom GPT.

Ask:

Call the echo tool with message "hello"

✔ What happens in ngrok logs:
GET /mcp                  200 OK
POST /mcp/messages/       202 Accepted
POST /mcp/messages/       202 Accepted
POST /mcp                 405 Method Not Allowed   # expected

✔ What happens in the FastAPI logs:
Processing request of type ListToolsRequest
Processing request of type CallToolRequest
echo_tool called with message='hello'
HTTP POST /echo -> 200 OK

✔ What happens in local curl:
$ curl -X POST http://127.0.0.1:9098/echo -d '{"message":"hello"}'
{"message":"echo: hello"}

❌ What happens in ChatGPT UI:
Error in message stream


The UI never displays the tool response, even though the backend definitely received it.

Expected vs Actual
✔ Expected

After calling the echo MCP tool, ChatGPT should show:

{ "message": "echo: hello" }

❌ Actual

ChatGPT logs show MCP handshake + tool execution succeeded

My MCP server returns correctly

The UI displays a red banner:

Error in message stream


No assistant response appears.

Why this is an OpenAI bug

Because:

The minimal server is correct

The MCP connector successfully registers the server + tool

The connector successfully sends call_tool

My server executes the tool without error

My server returns a valid MCP response

The TCP stream over ngrok is healthy

The UI fails after the backend receives the response

This strongly suggests a client-side UI streaming bug in ChatGPT when rendering responses for HTTP MCP.

Attachments / Evidence
✔ Full minimal server source

(above)

✔ MCP server logs

(included via log_requests)

✔ ngrok logs

(showing correct 200/202 activity)

✔ ChatGPT UI screenshots

(a reproducible failure every time)

Notes

This does not occur locally — only through ChatGPT UI.

Happens across multiple browsers and different machines.

Reproduces 100% with this minimal server.

If there’s any way to enable additional debug logging on the ChatGPT side or test a beta build, I’d be happy to help.

Thanks!