Native Node.js SDK for the stdio Bus kernel — a deterministic C runtime that routes NDJSON-framed JSON-RPC messages between your application and worker processes. Session-aware routing, automatic worker lifecycle management, and prebuilt native binaries for macOS and Linux. Docker fallback for Windows and unsupported platforms.
npm install @stdiobus/nodePrebuilt native binaries are included. No C compiler or build tools required.
Requirements:
- Node.js >= 18.0.0
- macOS (x64, arm64) or Linux (x64, arm64) for native backend
- Docker (optional) — required only on Windows or unsupported platforms
const { StdioBus } = require('@stdiobus/node');
const bus = new StdioBus({
config: {
pools: [{
id: 'echo',
command: 'node',
args: ['./workers/echo-worker.js'],
instances: 1,
}],
},
});
await bus.start();
const result = await bus.request('echo', { message: 'hello' });
console.log(result);
await bus.stop();
bus.destroy();The package ships with full type declarations.
import { StdioBus, BusState } from '@stdiobus/node';
import type { StdioBusOptions, BusStats } from '@stdiobus/node';
const options: StdioBusOptions = {
config: {
pools: [{
id: 'worker',
command: 'node',
args: ['./worker.js'],
instances: 2,
}],
limits: {
max_input_buffer: 1_048_576,
max_output_queue: 4_194_304,
max_restarts: 5,
restart_window_sec: 60,
},
},
};
const bus = new StdioBus(options);
try {
await bus.start();
bus.onMessage((msg: string) => {
console.log('Received:', msg);
});
const result = await bus.request('tools/list', {}, { timeout: 10_000 });
console.log('Tools:', result);
} finally {
await bus.stop();
bus.destroy();
}For typed streaming events, use
@stdiobus/agenticwithpromptStream()orprompt(). The example below shows the low-level transport approach.
const { StdioBus } = require('@stdiobus/node');
const bus = new StdioBus({
config: {
pools: [{
id: 'acp-worker',
command: 'node',
args: ['./workers/acp-worker.js'],
instances: 1,
}],
},
});
await bus.start();
const init = await bus.request('initialize', {
protocolVersion: 1,
clientInfo: { name: 'my-app', version: '1.0.0' },
clientCapabilities: {},
}, { timeout: 60_000 });
const session = await bus.request('session/new', {
cwd: process.cwd(),
mcpServers: [],
});
const result = await bus.request('session/prompt', {
sessionId: session.sessionId,
prompt: [{ type: 'text', text: 'What is 2+2?' }],
});
console.log('Response:', result);
await bus.stop();
bus.destroy();const bus = new StdioBus({
config: {
pools: [{
id: 'mcp-tools',
command: 'node',
args: ['./workers/mcp-tools-worker.js'],
instances: 2,
}],
},
});
await bus.start();
const tools = await bus.request('tools/list');
const output = await bus.request('tools/call', {
name: 'search_docs',
arguments: { query: 'retry policy' },
});
console.log('Tools:', tools);
console.log('Output:', output);
await bus.stop();
bus.destroy();Accept external client connections over TCP:
const port = Number(process.env.PORT) || 8080;
const bus = new StdioBus({
config: {
pools: [{
id: 'worker',
command: 'node',
args: ['./worker.js'],
instances: 4,
}],
},
listenMode: 'tcp',
tcpHost: '0.0.0.0',
tcpPort: port,
});
await bus.start();
console.log(`Listening on TCP port ${port}`);
// Clients connect and send NDJSON: nc localhost 8080On Windows or when native binaries are unavailable, the SDK runs stdio Bus inside a Docker container and communicates over TCP:
const bus = new StdioBus({
configPath: './config.json',
backend: 'docker',
docker: {
image: 'stdiobus/stdiobus:node20',
pullPolicy: 'if-missing',
startupTimeoutMs: 15_000,
},
});Set backend: 'auto' (default) to use native when available, Docker otherwise.
| Platform | Architecture | Native | Docker |
|---|---|---|---|
| macOS | x64 | ✓ | ✓ |
| macOS | arm64 | ✓ | ✓ |
| Linux | x64 | ✓ | ✓ |
| Linux | arm64 | ✓ | ✓ |
| Windows | x64 | — | ✓ |
new StdioBus(options: StdioBusOptions)| Option | Type | Default | Description |
|---|---|---|---|
configJson |
object |
— | Programmatic config (recommended) |
configPath |
string |
— | Path to JSON config file |
backend |
'auto' | 'native' | 'docker' |
'auto' |
Backend selection |
listenMode |
'none' | 'tcp' | 'unix' |
'none' |
External listener mode |
tcpHost |
string |
'127.0.0.1' |
TCP bind address |
tcpPort |
number |
— | TCP port (required for tcp mode) |
unixPath |
string |
— | Unix socket path (required for unix mode) |
logLevel |
number |
1 |
0=DEBUG, 1=INFO, 2=WARN, 3=ERROR |
pollIntervalMs |
number |
10 |
Native backend poll interval |
docker |
DockerOptions |
— | Docker backend configuration |
configJson and configPath are mutually exclusive. One is required.
| Method | Returns | Description |
|---|---|---|
start() |
Promise<void> |
Start bus and spawn workers |
stop(timeoutSec?) |
Promise<void> |
Graceful shutdown (default: 30s) |
destroy() |
void |
Release all resources |
| Method | Returns | Description |
|---|---|---|
request(method, params?, options?) |
Promise<T> |
Send request, await response |
send(message) |
boolean |
Send raw JSON-RPC string |
onMessage(handler) |
void |
Register message handler |
| Method | Returns | Description |
|---|---|---|
getState() |
number |
Bus state (0–4) |
getStats() |
BusStats |
Runtime statistics |
getWorkerCount() |
number |
Running workers |
getClientCount() |
number |
Connected clients (TCP/Unix) |
getBackendType() |
'native' | 'docker' |
Active backend |
getListenMode() |
string |
Listen mode |
isRunning() |
boolean |
Convenience check |
import { BusState, ListenMode, BackendMode } from '@stdiobus/node';
BusState.CREATED // 0
BusState.STARTING // 1
BusState.RUNNING // 2
BusState.STOPPING // 3
BusState.STOPPED // 4
ListenMode.NONE // 'none'
ListenMode.TCP // 'tcp'
ListenMode.UNIX // 'unix'
BackendMode.AUTO // 'auto'
BackendMode.NATIVE // 'native'
BackendMode.DOCKER // 'docker'- Workers that crash beyond
max_restartswithinrestart_window_secare not restarted. stop()sends SIGTERM to workers and waits up totimeoutSecfor graceful exit.request()correlates responses by JSON-RPCid. Each request gets a unique ID.- In embedded mode (
listenMode: 'none'), messages flow throughsend()/onMessage(). - In TCP/Unix modes, external clients connect and send NDJSON directly.
configJsonis serialized to a temp file internally, cleaned up ondestroy().- Always call
destroy()afterstop()to release native resources and clean up temp files.
npm install # install dependencies
npm run build # esbuild (JS) + tsc (declarations)
npm run typecheck # type-check without emit
npm run test:e2e # npm pack → install → verify on macOS + Docker Linux
npm run test:e2e:native # macOS only
npm run test:e2e:docker # Docker Linux onlyBuild output:
out/
dist/index.js # CJS bundle (esbuild, minified)
tsc/*.d.ts # Type declarations (tsc)
- Open an issue describing the change before submitting a PR.
- All PRs must include tests covering the change.
- Run
npm run typecheck && npm run test:e2ebefore submitting.
To report a security vulnerability, email raman@stdiobus.com. Do not open a public issue.