can1357/oh-my-pi · error · MCPTransportError
HTTP ${response.status}: ${text}
Error message
HTTP ${response.status}: ${text} What it means
The server answered a JSON-RPC POST (a notification here) with a non-2xx HTTP status other than the acceptable 202 Accepted. The transport surfaces the status code plus the response body text as an http_status MCPTransportError. Retryability is limited to 404, 502, and 503; other statuses (401/404-session-gone/500) are terminal. Because this path is the notification sender, the stage is 'receive' after the request was fully sent.
Source
Thrown at packages/coding-agent/src/mcp/transports/http.ts:788
}
const timeout = resolveMCPTimeoutMs(this.config.timeout);
const operation = createMCPTimeout(timeout, this.#operationSignal());
let stage: MCPFailureStage = "send";
let traceId: string | undefined;
try {
const response = await this.#fetch(
{ method: "POST", body: JSON.stringify(body), signal: operation.signal },
generated,
);
stage = "receive";
traceId = mcpTraceIdFromHeaders(response.headers);
// 202 Accepted is success for notifications
if (!response.ok && response.status !== 202) {
const text = await response.text();
throw new MCPTransportError({
transport: "http",
stage,
failure: "http_status",
message: `HTTP ${response.status}: ${text}`,
retryable: response.status === 404 || response.status === 502 || response.status === 503,
code: response.status,
traceId,
});
}
// The server may piggyback server-to-client requests or notifications
// on the notification response (MCP Streamable HTTP spec). Read them.
const contentType = response.headers.get("Content-Type") ?? "";
if (contentType.includes("text/event-stream") && response.body) {
// Use the SSE connection's signal if available; otherwise keep the existing finite read timeout.
if (this.#sseConnection) {
this.#trackBackgroundDrain(
this.#readSSEStream(response.body, this.#operationSignal(this.#sseConnection.signal)),View on GitHub (pinned to 9690622007)
Solutions
- Read the embedded body text for the server's error detail (often a JSON-RPC or auth error message).
- For 401/403: refresh credentials or reconfigure auth before reconnecting.
- For 404: the session was lost — reconnect (re-initialize) to get a fresh Mcp-Session-Id; the error is retryable for this reason.
- For 502/503: retry with backoff, the backend is temporarily unavailable.
- For 500: inspect the server logs; the response body text will point at the failing handler.
Example fix
// client (before): keeps notifying on a dead session
await transport.notify('notifications/cancelled', { requestId });
// after
try {
await transport.notify('notifications/cancelled', { requestId });
} catch (e) {
if (e instanceof MCPTransportError && e.code === 404) {
await reconnect(); // fresh session, then resend
}
} Defensive patterns
Strategy: retry
Validate before calling
// pre-flight: confirm endpoint reachable and credentials valid
const probe = await fetch(url, { method: 'HEAD' });
if (probe.status === 401 || probe.status === 403) throw new Error('MCP credentials invalid/missing');
if (probe.status === 404) throw new Error('Wrong MCP endpoint URL'); Type guard
function isHttpStatusError(e: unknown, codes: number[]): e is MCPTransportError & { code: number } {
return e instanceof MCPTransportError && e.failure === 'http_status' &&
typeof (e as { code?: number }).code === 'number' && codes.includes((e as { code: number }).code);
} Try / catch
try {
await transport.notify('notifications/cancelled', { requestId });
} catch (e) {
if (e instanceof MCPTransportError && e.failure === 'http_status') {
if ([404, 502, 503].includes(e.code ?? 0)) await backoffRetry(() => reconnectAndNotify());
else if (e.code === 401) await refreshAuthAndReconnect();
else throw e; // 5xx server bug: surface to user
} else throw e;
} Prevention
- Refresh auth tokens proactively before expiry.
- Reconnect (re-initialize) after any server redeploy — session ids do not survive restarts.
- Point notifications at the message endpoint, not the SSE/stream endpoint.
- Log e.code and e.traceId with server correlation in mind.
When it happens
Trigger: 401/403 from expired or missing auth credentials; 404 when the server restarted and the Mcp-Session-Id no longer exists; 500 from a server-side handler failure; 502/503 from an unavailable reverse proxy or overloaded backend.
Common situations: Auth tokens expiring mid-session; MCP server redeploy while the client holds an old session id; wrong endpoint URL (posting to the legacy SSE endpoint rather than the message endpoint); gateway rate limiting.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Notify timeout after ${timeout}ms
- MCP request failed: ${response.status} ${response.statusText
- MCP OAuth refresh failed: ${response.status} ${text}
- HTTP ${response.status}: server redirected a ${init.method}
- Too many redirects (> ${MAX_REDIRECT_HOPS}) fetching ${url}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/89e97adf1cb5c976.
Report an issue: GitHub.