Files
yvgude--lean-ctx/rust/tests/fixtures/mcp_stdio_echo.mjs
T
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

103 lines
3.0 KiB
JavaScript

#!/usr/bin/env node
// Minimal MCP stdio server fixture for the lean-ctx gateway E2E test (#1077).
//
// Speaks newline-delimited JSON-RPC 2.0 over stdin/stdout — the MCP stdio wire
// protocol — implementing just enough to exercise the gateway's real spawn path:
// initialize -> handshake (echoes the client's protocolVersion)
// tools/list -> advertises `echo` and `boom`
// tools/call -> `echo` returns `echo:<text>`; `boom` exits without replying
// (simulates a child dying mid-call, for the pool self-heal test)
// No dependencies, no network; only Node.js is required.
import { createInterface } from 'node:readline';
const rl = createInterface({ input: process.stdin });
function send(msg) {
process.stdout.write(JSON.stringify(msg) + '\n');
}
rl.on('line', (line) => {
const trimmed = line.trim();
if (!trimmed) return;
let req;
try {
req = JSON.parse(trimmed);
} catch {
return;
}
const { id, method, params } = req;
if (method === 'initialize') {
send({
jsonrpc: '2.0',
id,
result: {
protocolVersion: (params && params.protocolVersion) || '2025-06-18',
capabilities: { tools: {} },
serverInfo: { name: 'mcp-stdio-echo', version: '0.1.0' },
},
});
} else if (method === 'notifications/initialized') {
// Notification: no response.
} else if (method === 'tools/list') {
send({
jsonrpc: '2.0',
id,
result: {
tools: [
{
name: 'echo',
description: 'Echo back the provided text',
inputSchema: {
type: 'object',
properties: { text: { type: 'string' } },
required: ['text'],
},
},
{
name: 'boom',
description: 'Exit the process without replying (test fault injection)',
inputSchema: { type: 'object', properties: {} },
},
],
},
});
} else if (method === 'tools/call') {
const name = params && params.name;
const args = (params && params.arguments) || {};
if (name === 'boom') {
// Die mid-request, never sending a response: the client's transport sees
// EOF on a pending call. Exercises pool eviction + reopen (no blind retry).
process.exit(1);
} else if (name === 'echo') {
send({
jsonrpc: '2.0',
id,
result: {
content: [{ type: 'text', text: 'echo:' + (args.text ?? '') }],
isError: false,
},
});
} else {
send({
jsonrpc: '2.0',
id,
error: { code: -32602, message: 'unknown tool: ' + name },
});
}
} else if (id !== undefined && id !== null) {
// Any other request gets a method-not-found so the client never hangs.
send({
jsonrpc: '2.0',
id,
error: { code: -32601, message: 'method not found: ' + method },
});
}
});
// When the client closes stdin (session dropped from the pool), exit cleanly.
rl.on('close', () => process.exit(0));