vercel/ai · error · MCPClientError
StdioClientTransport not connected
Error message
StdioClientTransport not connected
What it means
StdioMCPTransport.send() throws MCPClientError with this message when the transport has no live child process with a writable stdin. The stdio transport wraps an MCP server spawned as a child process; until start() succeeds there is no process, and after the process exits or fails to spawn, this.process or this.process.stdin is undefined. send() is invoked internally when tools are called, so a missing process makes all MCP communication impossible.
Source
Thrown at packages/mcp/src/tool/mcp-stdio/mcp-stdio-transport.ts:110
try {
const message = await deserializeMessage(line);
this.onmessage?.(message);
} catch (error) {
this.onerror?.(error as Error);
}
}
}
async close(): Promise<void> {
this.abortController.abort();
this.process = undefined;
this.readBuffer.clear();
}
send(message: JSONRPCMessage): Promise<void> {
return new Promise(resolve => {
if (!this.process?.stdin) {
throw new MCPClientError({
message: 'StdioClientTransport not connected',
});
}
const json = serializeMessage(message);
if (this.process.stdin.write(json)) {
resolve();
} else {
this.process.stdin.once('drain', resolve);
}
});
}
}
class ReadBuffer {
private buffer?: Buffer;
append(chunk: Buffer): void {View on GitHub (pinned to 69428b1f8b)
Solutions
- Verify the MCP server command actually starts: run the command (e.g. `npx -y @modelcontextprotocol/server-... args`) in a shell and check it stays alive and prints nothing to stderr.
- Ensure the client/transport lifecycle is respected: create the DefaultMCPClient with the stdio transport config and call its methods only after connection/init completes (don't call send() on a raw transport directly).
- Check the command, args, and env in your StdioMCPTransport config for typos or missing binaries; use absolute paths (e.g. `command: 'node'` with full script path) when PATH may differ.
- If the process crashed mid-session, recreate the MCP client/transport instead of reusing the dead instance.
- Check server logs/stderr captured by the transport for the underlying spawn or runtime failure.
Example fix
// before: reusing transport after process died, or sending before start
const transport = new StdioMCPTransport({ command: 'npx', args: ['-y', 'server'] });
await transport.send(message); // throws: not connected
// after: start first and recreate after close/crash
await transport.start();
await transport.send(message);
// if previously closed/crashed:
const fresh = new StdioMCPTransport({ command: 'npx', args: ['-y', 'server'] });
await fresh.start(); Defensive patterns
Strategy: try-catch
Validate before calling
const isConnected = (t: StdioMCPTransport) =>
Boolean((t as any).process?.stdin); // check before send, or prefer transport state
export function canSend(t: unknown): boolean {
return t instanceof StdioMCPTransport && Boolean((t as any).process?.stdin);
} Type guard
export function isStdioTransportConnected(t: unknown): t is StdioMCPTransport {
return t instanceof StdioMCPTransport && Boolean((t as any).process?.stdin);
} Try / catch
import { MCPClientError } from '../error/mcp-client-error';
try {
await transport.send(message);
} catch (error) {
if (MCPClientError.isInstance(error) && error.message === 'StdioMCPTransport not connected') {
// recreate the transport/client: the child process is gone
const fresh = new StdioMCPTransport(config);
await fresh.start();
await fresh.send(message);
} else {
throw error;
}
} Prevention
- Always start the transport (or use the DefaultMCPClient lifecycle) before any send/tool call.
- Validate the MCP server command runs standalone in a shell before wiring it into the transport.
- Capture and monitor the child process stderr to detect crashes early.
- Recreate the transport after any close/error instead of reusing a dead instance.
When it happens
Trigger: Calling any MCP client operation (tools(), tool, resources, prompts) before calling transport.start(); using a transport instance whose child process has already exited or was closed; constructing StdioMCPTransport where the spawned command failed so this.process was never set; calling send() directly on a manually created transport that was never started.
Common situations: The MCP server command is invalid or not on PATH so the spawn fails silently before send; the server process crashed mid-session (bad args, missing runtime, port/env problems) and the next tool call hits a dead stdin; forgetting to await client init/connect before the first tool call; reusing a closed transport after transport.close().
Related errors
- StdioMCPTransport already started.
- Invalid argument for parameter requests: requests must not b
- Invalid argument for parameter requests: request IDs must no
- Invalid argument for parameter requests: request IDs must be
- Invalid argument for parameter batch: batch must be a suppor
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/14a6873f6921cc45.
Report an issue: GitHub.