Clean, developer-friendly JSON-RPC 2.0 client over stdio (child process communication)
Built on top of @gnana997/node-jsonrpc with a stdio transport layer for communicating with child processes using line-delimited JSON.
Perfect for building clients for:
- Model Context Protocol (MCP) servers
- Language servers (LSP)
- Custom stdio-based JSON-RPC services
- CLI tools with JSON-RPC interfaces
✨ Clean API - Simple, intuitive interface for stdio JSON-RPC communication 🚀 TypeScript First - Full type safety with generics 📦 Dual Package - ESM and CommonJS support 🔄 Event-Driven - Built on EventEmitter for notifications and logs 🛡️ Robust - Comprehensive error handling and process lifecycle management 🧪 Well-Tested - 47 tests with 81%+ coverage 📝 Well-Documented - Examples and TypeScript types included
npm install node-stdio-jsonrpcimport { StdioClient } from 'node-stdio-jsonrpc';
// Create a client that spawns a child process
const client = new StdioClient({
command: 'node',
args: ['./your-jsonrpc-server.js'],
debug: true, // Optional: enable debug logging
});
// Connect to the server
await client.connect();
// Make a request
const result = await client.request('yourMethod', { param: 'value' });
console.log('Result:', result);
// Send a notification (no response expected)
client.notify('log', { level: 'info', message: 'Hello' });
// Listen for server notifications
client.on('notification', (method, params) => {
console.log(`Server notification: ${method}`, params);
});
// Disconnect when done
await client.disconnect();The main class for creating JSON-RPC clients over stdio.
new StdioClient(config: StdioClientConfig)Config Options:
| Option | Type | Default | Description |
|---|---|---|---|
command |
string |
required | Command to spawn (e.g., 'node', 'python') |
args |
string[] |
[] |
Arguments to pass to the command |
cwd |
string |
process.cwd() |
Working directory for the child process |
env |
NodeJS.ProcessEnv |
process.env |
Environment variables |
connectionTimeout |
number |
10000 |
Connection timeout in milliseconds |
requestTimeout |
number |
30000 |
Request timeout in milliseconds |
debug |
boolean |
false |
Enable debug logging |
Spawns the child process and establishes the connection.
await client.connect();Terminates the child process and cleans up resources.
await client.disconnect();Sends a JSON-RPC request and waits for the response.
interface CalculateResult {
sum: number;
}
const result = await client.request<CalculateResult>('calculate', {
operation: 'add',
a: 5,
b: 3,
});
console.log(result.sum); // 8Sends a JSON-RPC notification (no response expected).
client.notify('log', { level: 'info', message: 'Task completed' });Checks if the client is currently connected.
if (client.isConnected()) {
console.log('Connected!');
}The client extends EventEmitter and emits the following events:
| Event | Parameters | Description |
|---|---|---|
connected |
() |
Emitted when connection is established |
disconnected |
() |
Emitted when disconnected from server |
notification |
(method: string, params?: unknown) |
Server sent a notification |
error |
(error: Error) |
An error occurred |
log |
(message: string) |
Server wrote to stderr |
client.on('connected', () => {
console.log('Connected to server!');
});
client.on('disconnected', () => {
console.log('Disconnected from server');
});
client.on('notification', (method, params) => {
console.log(`Notification: ${method}`, params);
});
client.on('error', (error) => {
console.error('Error:', error);
});
client.on('log', (message) => {
console.log(`Server log: ${message}`);
});Lower-level transport implementation. Usually you'll use StdioClient, but StdioTransport is available if you need direct control.
import { StdioTransport } from 'node-stdio-jsonrpc/transport';
import { JSONRPCClient } from '@gnana997/node-jsonrpc';
const transport = new StdioTransport({
command: 'node',
args: ['./server.js'],
});
const client = new JSONRPCClient({ transport });
await client.connect();import { StdioClient } from 'node-stdio-jsonrpc';
const client = new StdioClient({
command: 'node',
args: ['./echo-server.js'],
});
await client.connect();
const response = await client.request('echo', { message: 'Hello, World!' });
console.log(response); // { message: 'Hello, World!' }
await client.disconnect();Perfect for connecting to Model Context Protocol servers:
import { StdioClient } from 'node-stdio-jsonrpc';
const client = new StdioClient({
command: 'npx',
args: ['@modelcontextprotocol/server-filesystem', '~/Documents'],
debug: true,
});
await client.connect();
// Initialize MCP session
const initResult = await client.request('initialize', {
protocolVersion: '2024-11-05',
capabilities: { roots: { listChanged: true } },
clientInfo: { name: 'my-client', version: '1.0.0' },
});
client.notify('notifications/initialized');
// List available tools
const { tools } = await client.request('tools/list');
console.log('Available tools:', tools);
await client.disconnect();See the MCP client example for a complete implementation.
import { StdioClient, JSONRPCError } from 'node-stdio-jsonrpc';
const client = new StdioClient({
command: 'node',
args: ['./server.js'],
});
try {
await client.connect();
const result = await client.request('someMethod', { param: 'value' });
console.log('Success:', result);
} catch (error) {
if (error instanceof JSONRPCError) {
console.error('JSON-RPC Error:', error.code, error.message);
} else {
console.error('Connection/Transport Error:', error);
}
} finally {
if (client.isConnected()) {
await client.disconnect();
}
}import { StdioClient } from 'node-stdio-jsonrpc';
// Define your request/response types
interface CalculateParams {
operation: 'add' | 'subtract' | 'multiply' | 'divide';
a: number;
b: number;
}
interface CalculateResult {
result: number;
}
const client = new StdioClient({
command: 'node',
args: ['./calculator-server.js'],
});
await client.connect();
// Type-safe requests
const result = await client.request<CalculateResult>('calculate', {
operation: 'add',
a: 10,
b: 5,
} satisfies CalculateParams);
console.log(result.result); // TypeScript knows this is a number
await client.disconnect();This library implements JSON-RPC 2.0 over stdio (standard input/output) using line-delimited JSON framing:
- Each JSON message is on its own line
- Messages are terminated with
\n - stdin: Client → Server
- stdout: Server → Client (JSON-RPC messages)
- stderr: Server logs (not JSON-RPC)
Request:
{"jsonrpc":"2.0","id":1,"method":"methodName","params":{"key":"value"}}Success Response:
{"jsonrpc":"2.0","id":1,"result":{"data":"value"}}Error Response:
{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"Error message"}}Notification (no id):
{"jsonrpc":"2.0","method":"notifyMethod","params":{"key":"value"}}- Node.js 18 or higher
- TypeScript 5.x (if using TypeScript)
@gnana997/node-jsonrpc- Core JSON-RPC 2.0 implementationnode-ipc-jsonrpc- JSON-RPC over IPC (Unix sockets / Windows named pipes)
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
MIT © gnana997
See CHANGELOG.md for version history.
Built with ❤️ using TypeScript and @gnana997/node-jsonrpc