{"version":3,"sources":["../src/mcp/stdio.ts"],"names":["StringDecoder","stdin","stdout","stderr"],"mappings":"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","file":"mcp-stdio.cjs","sourcesContent":["import { stderr, stdin, stdout } from \"node:process\";\nimport { StringDecoder } from \"node:string_decoder\";\nimport type { MicroMcpServer, Transport } from \"./server\";\nimport type { JsonRpcMessage } from \"./types\";\n\n/**\n * StdioTransport — newline-delimited JSON-RPC over a byte stream pair.\n *\n * This is the framing every MCP client uses when it launches a server as a\n * subprocess (Claude Code, Codex, Cursor, Claude Desktop): one JSON-RPC message\n * per line on stdin, one per line on stdout. Anything else a server wants to say\n * goes to stderr, because a stray write to stdout corrupts the stream and the\n * client sees a parse error instead of a message.\n *\n * NODE ONLY, which is why it is its own subpath (`/mcp/stdio`) rather than part\n * of `/mcp`: browser bundles import `/mcp`, and `node:*` imports there would\n * break them.\n *\n *   import { MicroMcpServer } from \"@particle-academy/agent-integrations/mcp\";\n *   import { attachStdio } from \"@particle-academy/agent-integrations/mcp/stdio\";\n *\n *   const server = new MicroMcpServer({ info: { name: \"my-server\", version: \"1.0.0\" } });\n *   server.registerTool(...);\n *   attachStdio(server);\n *\n * What it deliberately does:\n *\n * - **A bad line costs that line, not the session.** A line that is not JSON, or\n *   is JSON but not a single JSON-RPC message (a number, `null`, a batch), is\n *   reported through `onError` and skipped. No reply is written for it — there\n *   is no request id to address one to, and stdout carries protocol frames only.\n * - **Input ending does not drop a reply that is still owed.** `printf\n *   '<request>\\n' | server` closes stdin straight after the request; replies\n *   already in flight are delivered, then the transport closes.\n * - **Bytes, not chunks, are decoded.** A chunk boundary can cut a multi-byte\n *   character in half; decoding chunk by chunk would corrupt it.\n */\n\n/** The readable half. `process.stdin` and any Node `Readable` satisfy it. */\nexport interface StdioInput {\n  on(event: \"data\", listener: (chunk: unknown) => void): unknown;\n  on(event: \"end\", listener: () => void): unknown;\n  on(event: \"error\", listener: (error: Error) => void): unknown;\n  removeListener(event: string, listener: (...args: any[]) => void): unknown;\n  pause?(): unknown;\n}\n\n/** The writable half. `process.stdout` and any Node `Writable` satisfy it. */\nexport interface StdioOutput {\n  write(chunk: string): unknown;\n  on?(event: \"error\", listener: (error: Error) => void): unknown;\n  removeListener?(event: string, listener: (...args: any[]) => void): unknown;\n}\n\nexport type StdioOptions = {\n  /** Defaults to `process.stdin`. */\n  input?: StdioInput;\n  /** Defaults to `process.stdout`. */\n  output?: StdioOutput;\n  /**\n   * A line that could not be served, or a stream error. Defaults to one line on\n   * `process.stderr` — the channel the MCP spec reserves for exactly this.\n   */\n  onError?: (error: Error) => void;\n  /** Called once, when the transport closes (input ended, or `close()`). */\n  onClose?: () => void;\n};\n\nexport class StdioTransport implements Transport {\n  private server?: MicroMcpServer;\n  private readonly input: StdioInput;\n  private readonly output: StdioOutput;\n  private readonly reportError: (error: Error) => void;\n  private readonly onClose?: () => void;\n  private readonly decoder = new StringDecoder(\"utf8\");\n  private buffer = \"\";\n  private closed = false;\n  private started = false;\n  private readonly inFlight = new Set<Promise<void>>();\n\n  constructor(options: StdioOptions = {}) {\n    this.input = options.input ?? stdin;\n    this.output = options.output ?? stdout;\n    const onError = options.onError ?? ((error: Error) => stderr.write(`[mcp stdio] ${error.message}\\n`));\n    // A host's error handler that throws must not take the session down with\n    // it: it runs inside a stream callback, where a throw is an uncaught error.\n    this.reportError = (error) => {\n      try {\n        onError(error);\n      } catch {\n        /* nothing useful left to report it to */\n      }\n    };\n    this.onClose = options.onClose;\n  }\n\n  /** Bind to a server. `attachStdio` does this for you. */\n  bindServer(server: MicroMcpServer): void {\n    this.server = server;\n  }\n\n  /** Begin reading input. `attachStdio` does this for you. */\n  start(): void {\n    if (this.started || this.closed) return;\n    this.started = true;\n    this.input.on(\"data\", this.onData);\n    this.input.on(\"end\", this.onEnd);\n    this.input.on(\"error\", this.onStreamError);\n    this.output.on?.(\"error\", this.onStreamError);\n  }\n\n  /** Server → client: one message, one line. `JSON.stringify` escapes every newline inside it. */\n  send(message: JsonRpcMessage): void {\n    if (this.closed) return;\n    this.output.write(`${JSON.stringify(message)}\\n`);\n  }\n\n  close(): void {\n    if (this.closed) return;\n    this.closed = true;\n    this.input.removeListener(\"data\", this.onData);\n    this.input.removeListener(\"end\", this.onEnd);\n    this.input.removeListener(\"error\", this.onStreamError);\n    this.output.removeListener?.(\"error\", this.onStreamError);\n    // Removing the data listener does not stop a flowing stream by itself, and\n    // a flowing stdin keeps the process alive.\n    this.input.pause?.();\n    this.onClose?.();\n  }\n\n  private readonly onData = (chunk: unknown): void => {\n    if (this.closed) return;\n    this.buffer += typeof chunk === \"string\" ? chunk : this.decoder.write(chunk as Uint8Array);\n\n    let newline = this.buffer.indexOf(\"\\n\");\n    while (newline !== -1) {\n      const line = this.buffer.slice(0, newline);\n      this.buffer = this.buffer.slice(newline + 1);\n      this.serve(line);\n      newline = this.buffer.indexOf(\"\\n\");\n    }\n  };\n\n  private readonly onEnd = (): void => {\n    if (this.closed) return;\n    this.buffer += this.decoder.end();\n    const last = this.buffer;\n    this.buffer = \"\";\n    this.serve(last);\n\n    // Deliver what is owed before closing, or a request followed immediately by\n    // end-of-input never gets its reply.\n    void Promise.allSettled([...this.inFlight]).then(() => {\n      this.server?.detach(this);\n      this.close();\n    });\n  };\n\n  private readonly onStreamError = (error: Error): void => {\n    this.reportError(error);\n  };\n\n  private serve(raw: string): void {\n    const line = raw.endsWith(\"\\r\") ? raw.slice(0, -1) : raw;\n    if (line.trim() === \"\") return;\n\n    let message: unknown;\n    try {\n      message = JSON.parse(line);\n    } catch (error) {\n      this.reportError(new Error(`Ignored a line that is not JSON (${(error as Error).message}): ${preview(line)}`));\n      return;\n    }\n\n    if (message === null || typeof message !== \"object\") {\n      this.reportError(new Error(`Ignored a line that is JSON but not a JSON-RPC message: ${preview(line)}`));\n      return;\n    }\n    if (Array.isArray(message)) {\n      this.reportError(new Error(`Ignored a JSON-RPC batch; this transport serves one message per line: ${preview(line)}`));\n      return;\n    }\n    if (!this.server) {\n      this.reportError(new Error(\"StdioTransport has no bound server; use attachStdio(server).\"));\n      return;\n    }\n\n    const pending: Promise<void> = this.server\n      .receive(this, message as JsonRpcMessage)\n      .catch((error: unknown) => this.reportError(error instanceof Error ? error : new Error(String(error))))\n      .finally(() => this.inFlight.delete(pending));\n    this.inFlight.add(pending);\n  }\n}\n\nfunction preview(line: string): string {\n  return line.length > 120 ? `${line.slice(0, 120)}…` : line;\n}\n\n/**\n * Serve `server` over stdio (or the streams you pass). Returns the transport;\n * `server.detach(transport)` or `transport.close()` stops it.\n */\nexport function attachStdio(server: MicroMcpServer, options: StdioOptions = {}): StdioTransport {\n  const transport = new StdioTransport(options);\n  transport.bindServer(server);\n  server.attach(transport);\n  transport.start();\n  return transport;\n}\n"]}