Initial Commit
This commit is contained in:
66
packages/devtools/tooling/mcp/src/transport/stdio/stdio.md
Normal file
66
packages/devtools/tooling/mcp/src/transport/stdio/stdio.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# stdio
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`StdioTransport` frames JSON-RPC messages over a newline-delimited JSON byte stream — the MCP spec default for sub-process child servers. The transport is universal: it accepts an injectable `readable: AsyncIterable<Uint8Array>` and a `writable: UniversalWritable`, so callers wire `Readable.toWeb(process.stdin)`, `Bun.stdin.stream()`, browser `ReadableStream`, or an in-memory queue without the package depending on any Node-only module.
|
||||
|
||||
## Features
|
||||
|
||||
- Newline-delimited JSON framing — one message per `\n`-terminated line.
|
||||
- Injectable streams: no `node:fs` / `node:process` / `node:stream` imports.
|
||||
- Streaming parser tolerates partial chunks via a buffer + `TextDecoder({ stream: true })`.
|
||||
- Malformed lines are skipped silently; framing recovers on the next newline.
|
||||
- Inherits `closed` guard, listener registry, and idempotent close from `BaseTransport`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { createStdioTransport } from "@suaveplan/mcp/transport/stdio";
|
||||
import type { JsonRpcMessage } from "@suaveplan/mcp";
|
||||
|
||||
const sentLines: string[] = [];
|
||||
const inbox = new ReadableStream<Uint8Array>({ start() { /* … */ } });
|
||||
|
||||
const transport = createStdioTransport({
|
||||
readable: inbox,
|
||||
writable: {
|
||||
async write(chunk) { sentLines.push(new TextDecoder().decode(chunk)); },
|
||||
async close() {},
|
||||
},
|
||||
});
|
||||
|
||||
transport.onMessage((m: JsonRpcMessage) => console.log("rx", m));
|
||||
await transport.send({ jsonrpc: "2.0", id: 1, method: "ping" });
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { createStdioTransport } from "@suaveplan/mcp";
|
||||
|
||||
// Bridge to a child process spawned by your test harness.
|
||||
async function bridgeProcess(child: { stdout: AsyncIterable<Uint8Array>; stdin: WritableStream }) {
|
||||
const writer = child.stdin.getWriter();
|
||||
return createStdioTransport({
|
||||
readable: child.stdout,
|
||||
writable: {
|
||||
async write(chunk) { await writer.write(chunk); },
|
||||
async close() { await writer.close(); },
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface StdioTransportOptions` — `{ readable: AsyncIterable<Uint8Array>; writable: UniversalWritable }`.
|
||||
- `class StdioTransport extends BaseTransport` — constructor takes `StdioTransportOptions`; starts reading asynchronously.
|
||||
- `createStdioTransport(options): StdioTransport` — convenience factory mirroring the documented public API.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The reader loop maintains an internal text buffer; each chunk is decoded with `TextDecoder({ stream: true })` so multi-byte sequences split across reads still decode cleanly. After each newline the line is `trim`-ed and `JSON.parse`-d; failures or schema rejections are dropped because line framing makes the next message independent. Send framing writes `JSON.stringify(message) + "\n"` — `JSON.stringify` always escapes control characters in strings, so a literal newline in payload text becomes `\\n` and never breaks the framing. `closeInternal` flips a `stopped` flag the reader checks on each iteration and then closes the writable side; calling close while a long read is pending lets the reader exit on its next `for await` step.
|
||||
Reference in New Issue
Block a user