can1357/oh-my-pi · error · MCPTransportError

Transport not connected

Error message

Transport not connected

What it means

request() was called on the HTTP MCP transport before connect() completed or after the transport was disconnected. The transport guards every JSON-RPC execution with a #connected flag and throws this MCPTransportError (stage: connect, failure: closed, retryable: true).

Source

Thrown at packages/coding-agent/src/mcp/transports/http.ts:425

			if (!(error instanceof SSEResumeError) && this.onAuthError && (status === 401 || status === 403)) {
				const newHeaders = await this.onAuthError();
				if (newHeaders) {
					// Persist refreshed headers so subsequent requests use them directly
					this.config = { ...this.config, headers: newHeaders };
					return this.#executeRequest<T>(method, params, options);
				}
			}
			throw error;
		}
	}

	async #executeRequest<T>(
		method: string,
		params: Record<string, unknown> | undefined,
		options: MCPRequestOptions | undefined,
	): Promise<T> {
		if (!this.#connected) {
			throw new MCPTransportError({
				transport: "http",
				stage: "connect",
				failure: "closed",
				message: "Transport not connected",
				retryable: true,
			});
		}

		const id = this.#requestIds.next(this.config.requestIdFormat);
		const body = {
			jsonrpc: "2.0" as const,
			id,
			method,
			params: params ?? {},
		};

		const generated: Record<string, string> = {
			"Content-Type": "application/json",

View on GitHub (pinned to 9690622007)

Solutions

  1. Await transport.connect() before issuing any request() calls
  2. Check the connected state (or listen for onClose) before enqueueing requests; queue or re-create the transport when disconnected
  3. Retry with a fresh connect — the error is marked retryable
  4. Fix lifecycle races: ensure teardown (close) completes before dropping references, and don't share one transport across async contexts without a ready-gate

Example fix

// before
transport.request("tools/call", { name: "search" }); // may throw if not connected
// after
if (!transport.isConnected()) await transport.connect();
await transport.request("tools/call", { name: "search" });
Defensive patterns

Strategy: validation

Validate before calling

// Gate all requests on a ready promise established at setup:
const ready = transport.connect();
async function safeRequest<T>(m: string, p?: Record<string, unknown>) {
  await ready;
  return transport.request<T>(m, p);
}

Try / catch

try {
  await transport.request("tools/list");
} catch (err) {
  if (err instanceof MCPTransportError && err.failure === "closed" && err.stage === "connect") {
    await transport.connect();
    await transport.request("tools/list");
  } else throw err;
}

Prevention

When it happens

Trigger: Calling transport.request(...) without a prior successful connect(); after close(); after the SSE listener detected a fatal stream end and fired onClose while requests are still in flight.

Common situations: Application code racing a slow connect (calling tools before initialization resolves); reusing a transport singleton after teardown; a reconnect cycle dropping requests submitted in the gap.

Related errors


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