Transport-level routing for MCP/ACP protocols

Echo Worker

Simple reference implementation demonstrating the NDJSON worker protocol for stdio Bus kernel.

Getting Started

Echo Worker

Overview

The Echo Worker is a minimal worker implementation that echoes back any JSON-RPC messages it receives. It serves as both a testing tool for stdio Bus kernel functionality and a reference implementation for developers creating custom workers.

Key Features:

  • Simple NDJSON protocol implementation
  • JSON-RPC 2.0 compliant
  • Session affinity support
  • Graceful shutdown handling
  • Reference code for custom workers

Purpose

The Echo Worker serves multiple purposes:

  1. Testing: Verify stdio Bus kernel functionality and message routing
  2. Reference Implementation: Demonstrate the NDJSON worker protocol through clean, documented code
  3. Development: Quick testing during development without complex protocol implementations
  4. Learning: Understand how workers communicate with stdio Bus kernel

Usage

Standalone Testing

Run the Echo Worker directly to test the NDJSON protocol:

echo '{"jsonrpc":"2.0","id":"1","method":"test","params":{"foo":"bar"}}' | \
node node_modules/@stdiobus/workers-registry/workers/echo-worker/echo-worker.js
Code block in Bash, 2 lines

Expected output:

{"jsonrpc":"2.0","id":"1","result":{"method":"test","params":{"foo":"bar"}}}
Code block in JSON, 1 line

Using with stdio Bus

Configuration file (echo-worker-config.json):

{
"pools": [
{
"id": "echo-worker",
"command": "npx",
"args": [
"@stdiobus/workers-registry",
"echo-worker"
],
"instances": 1
}
]
}
Code block in JSON, 13 lines

Run with Docker:

docker run \
--name stdiobus-echo \
-p 9000:9000 \
-v $(pwd):/stdiobus:ro \
-v $(pwd)/echo-worker-config.json:/config.json:ro \
stdiobus/stdiobus:latest \
--config /config.json --tcp 0.0.0.0:9000
Code block in Bash, 7 lines

Run with binary:

./stdio_bus --config echo-worker-config.json --tcp 0.0.0.0:9000
Code block in Bash, 1 line

Testing the Connection

Send a test message to the Echo Worker:

echo '{"jsonrpc":"2.0","id":"1","method":"echo","params":{"test":true}}' | nc localhost 9000
Code block in Bash, 1 line

Response:

{"jsonrpc":"2.0","id":"1","result":{"method":"echo","params":{"test":true}}}
Code block in JSON, 1 line

Configuration

Basic Configuration

Minimal configuration for running a single Echo Worker instance:

{
"pools": [
{
"id": "echo-worker",
"command": "npx",
"args": [
"@stdiobus/workers-registry",
"echo-worker"
],
"instances": 1
}
]
}
Code block in JSON, 13 lines

Multiple Instances

Run multiple Echo Worker instances for load testing:

{
"pools": [
{
"id": "echo-worker",
"command": "npx",
"args": [
"@stdiobus/workers-registry",
"echo-worker"
],
"instances": 4
}
]
}
Code block in JSON, 13 lines

With Custom Limits

Configure resource limits for testing backpressure and error handling:

{
"pools": [
{
"id": "echo-worker",
"command": "npx",
"args": [
"@stdiobus/workers-registry",
"echo-worker"
],
"instances": 1
}
],
"limits": {
"max_input_buffer": 1048576,
"max_output_queue": 4194304,
"max_restarts": 5,
"restart_window_sec": 60,
"drain_timeout_sec": 30,
"backpressure_timeout_sec": 60
}
}
Code block in JSON, 21 lines

Protocol Implementation

The Echo Worker demonstrates the core NDJSON protocol requirements:

Reading from stdin

import readline from 'readline';
 
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
 
rl.on('line', (line) => {
try {
const msg = JSON.parse(line);
handleMessage(msg);
} catch (err) {
console.error('Parse error:', err.message);
}
});
Code block in JavaScript, 16 lines

Writing to stdout

function sendResponse(id, result, sessionId) {
const response = {
jsonrpc: '2.0',
id: id,
result: result
};
if (sessionId) {
response.sessionId = sessionId;
}
console.log(JSON.stringify(response));
}
Code block in JavaScript, 13 lines

Handling SIGTERM

process.on('SIGTERM', () => {
console.error('Shutting down...');
rl.close();
});
 
rl.on('close', () => process.exit(0));
Code block in JavaScript, 6 lines

Testing Use Cases

Protocol Verification

Test that stdio Bus correctly routes messages to workers:

# Send request
echo '{"jsonrpc":"2.0","id":"1","method":"test","params":{}}' | nc localhost 9000
 
# Verify response format
# Should receive: {"jsonrpc":"2.0","id":"1","result":{"method":"test","params":{}}}
Code block in Bash, 5 lines

Session Affinity Testing

Test that messages with the same sessionId are routed to the same worker instance:

# Send multiple messages with same sessionId
echo '{"jsonrpc":"2.0","id":"1","method":"test","sessionId":"sess-123","params":{}}' | nc localhost 9000
echo '{"jsonrpc":"2.0","id":"2","method":"test","sessionId":"sess-123","params":{}}' | nc localhost 9000
echo '{"jsonrpc":"2.0","id":"3","method":"test","sessionId":"sess-123","params":{}}' | nc localhost 9000
 
# All responses should come from the same worker instance
Code block in Bash, 6 lines

Load Testing

Test stdio Bus performance with multiple concurrent connections:

# Run multiple clients in parallel
for i in {1..100}; do
echo '{"jsonrpc":"2.0","id":"'$i'","method":"test","params":{}}' | nc localhost 9000 &
done
wait
Code block in Bash, 5 lines

Error Handling

Test how stdio Bus handles malformed messages:

# Send invalid JSON
echo 'not-json' | nc localhost 9000
 
# Send JSON-RPC without required fields
echo '{"method":"test"}' | nc localhost 9000
Code block in Bash, 5 lines

Development Reference

The Echo Worker source code serves as a reference for creating custom workers. Key implementation patterns:

Message Handling

function handleMessage(msg) {
// Requests have an 'id' field and require a response
if (msg.id !== undefined) {
const result = {
method: msg.method,
params: msg.params
};
sendResponse(msg.id, result, msg.sessionId);
}
// Notifications don't have an 'id' and don't require a response
else {
console.error('Received notification:', msg.method);
}
}
Code block in JavaScript, 14 lines

Error Responses

function sendError(id, code, message, sessionId) {
const response = {
jsonrpc: '2.0',
id: id,
error: {
code: code,
message: message
}
};
if (sessionId) {
response.sessionId = sessionId;
}
console.log(JSON.stringify(response));
}
Code block in JavaScript, 16 lines

Logging

// Always log to stderr, never to stdout
console.error('Worker started');
console.error('Processing message:', msg.method);
console.error('Error occurred:', err.message);
Code block in JavaScript, 4 lines

Best Practices

When using the Echo Worker as a reference for custom workers:

  1. Never write non-JSON to stdout - All output to stdout must be valid NDJSON
  2. Log to stderr only - Use console.error() for all logging and debugging
  3. Preserve sessionId - Always include sessionId in responses when present in requests
  4. Handle SIGTERM - Implement graceful shutdown to avoid data loss
  5. Parse errors carefully - Catch JSON parse errors and log them to stderr
  6. Validate JSON-RPC - Check for required fields (jsonrpc, id, method)
  7. Use readline interface - Process stdin line-by-line for NDJSON protocol

Next Steps

Resources

  • Echo Worker Source Code
  • NDJSON Specification
stdioBus
© 2026 stdio Bus. All rights reserved.