can1357/oh-my-pi · error · MCPTransportError

HTTP

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.

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.

Appendix: 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)