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

  1. Read the embedded body text for the server's error detail (often a JSON-RPC or auth error message).
  2. For 401/403: refresh credentials or reconfigure auth before reconnecting.
  3. For 404: the session was lost — reconnect (re-initialize) to get a fresh Mcp-Session-Id; the error is retryable for this reason.
  4. For 502/503: retry with backoff, the backend is temporarily unavailable.
  5. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/89e97adf1cb5c976. Report an issue: GitHub.