{"record":{"id":"14a6873f6921cc45","repo":"vercel/ai","slug":"stdioclienttransport-not-connected","errorCode":null,"errorMessage":"StdioClientTransport not connected","messagePattern":"StdioClientTransport not connected","errorType":"exception","errorClass":"MCPClientError","httpStatus":null,"severity":"error","filePath":"packages/mcp/src/tool/mcp-stdio/mcp-stdio-transport.ts","lineNumber":110,"sourceCode":"      try {\n        const message = await deserializeMessage(line);\n        this.onmessage?.(message);\n      } catch (error) {\n        this.onerror?.(error as Error);\n      }\n    }\n  }\n\n  async close(): Promise<void> {\n    this.abortController.abort();\n    this.process = undefined;\n    this.readBuffer.clear();\n  }\n\n  send(message: JSONRPCMessage): Promise<void> {\n    return new Promise(resolve => {\n      if (!this.process?.stdin) {\n        throw new MCPClientError({\n          message: 'StdioClientTransport not connected',\n        });\n      }\n\n      const json = serializeMessage(message);\n      if (this.process.stdin.write(json)) {\n        resolve();\n      } else {\n        this.process.stdin.once('drain', resolve);\n      }\n    });\n  }\n}\n\nclass ReadBuffer {\n  private buffer?: Buffer;\n\n  append(chunk: Buffer): void {","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/mcp/src/tool/mcp-stdio/mcp-stdio-transport.ts#L92-L128","documentation":"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.","triggerScenarios":"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.","commonSituations":"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().","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."],"exampleFix":"// before: reusing transport after process died, or sending before start\nconst transport = new StdioMCPTransport({ command: 'npx', args: ['-y', 'server'] });\nawait transport.send(message); // throws: not connected\n// after: start first and recreate after close/crash\nawait transport.start();\nawait transport.send(message);\n// if previously closed/crashed:\nconst fresh = new StdioMCPTransport({ command: 'npx', args: ['-y', 'server'] });\nawait fresh.start();","handlingStrategy":"try-catch","validationCode":"const isConnected = (t: StdioMCPTransport) =>\n  Boolean((t as any).process?.stdin); // check before send, or prefer transport state\nexport function canSend(t: unknown): boolean {\n  return t instanceof StdioMCPTransport && Boolean((t as any).process?.stdin);\n}","typeGuard":"export function isStdioTransportConnected(t: unknown): t is StdioMCPTransport {\n  return t instanceof StdioMCPTransport && Boolean((t as any).process?.stdin);\n}","tryCatchPattern":"import { MCPClientError } from '../error/mcp-client-error';\ntry {\n  await transport.send(message);\n} catch (error) {\n  if (MCPClientError.isInstance(error) && error.message === 'StdioMCPTransport not connected') {\n    // recreate the transport/client: the child process is gone\n    const fresh = new StdioMCPTransport(config);\n    await fresh.start();\n    await fresh.send(message);\n  } else {\n    throw error;\n  }\n}","preventionTips":["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."],"tags":["mcp","stdio","lifecycle","process-spawn"],"backgroundTag":"transport-not-connected","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}