tests/integration/mcp-stdio.test.ts
Rama main · réplica de lectura del repositorio original.
import { afterAll, beforeAll, test } from "bun:test";
import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createSqliteDb, openDatabase } from "../../src/runtime/bun/sqlite";
import { runMcpStdio } from "../../src/runtime/bun/mcp-stdio";
import { serve } from "../../src/runtime/bun/server";
import { provisionWith } from "../contract/harness";
const directory = mkdtempSync(join(tmpdir(), "titen-mcp-stdio-"));
const cli = join(import.meta.dir, "../../src/runtime/bun/cli.ts");
let running: Awaited<ReturnType<typeof serve>>;
let key: string;
beforeAll(async () => {
const dbPath = join(directory, "titen.db");
running = await serve({ dbPath, port: 0, hostname: "127.0.0.1", quiet: true });
const database = openDatabase(dbPath);
key = (await provisionWith(createSqliteDb(database), { scopes: ["*"] })).key;
database.close();
});
afterAll(async () => {
await running.stop();
rmSync(directory, { recursive: true, force: true });
});
const initialize = {
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "stdio-test", version: "1" } },
};
const initialized = { jsonrpc: "2.0", method: "notifications/initialized" };
const tools = { jsonrpc: "2.0", id: 2, method: "tools/list" };
async function http(message: unknown): Promise<string> {
const response = await fetch(`${running.url}/mcp`, {
method: "POST",
headers: {
accept: "application/json, text/event-stream",
authorization: `Bearer ${key}`,
"content-type": "application/json",
},
body: JSON.stringify(message),
});
return response.text();
}
test("the installed CLI bridges requests, drops notification replies, and exits on EOF", async () => {
const process = Bun.spawn(["bun", cli, "mcp"], {
env: { ...globalThis.process.env, TITEN_MCP_URL: `${running.url}/mcp`, TITEN_API_KEY: key },
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
process.stdin.write(`${JSON.stringify(initialize)}\n${JSON.stringify(initialized)}\n${JSON.stringify(tools)}`);
process.stdin.end();
const [exitCode, stdout, stderr] = await Promise.all([
Promise.race([process.exited, Bun.sleep(1_000).then(() => -1)]),
new Response(process.stdout).text(),
new Response(process.stderr).text(),
]);
assert.equal(exitCode, 0, stderr);
assert.equal(stderr, "");
assert.deepEqual(stdout.trim().split("\n"), [await http(initialize), await http(tools)]);
assert.doesNotMatch(`${stdout}${stderr}`, new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
});
test("parse and network failures are sanitized without ending the bridge", async () => {
async function* input() {
yield new TextEncoder().encode([
JSON.stringify({ jsonrpc: "2.0", id: 7, method: "ping" }),
JSON.stringify(initialized),
"{",
JSON.stringify({ jsonrpc: "2.0", id: 8, method: "tools/list" }),
].join("\n"));
}
const output: string[] = [];
const logged: string[] = [];
const secret = "never-print-this-key";
const realError = console.error;
console.error = (line: string) => void logged.push(line);
try {
await runMcpStdio({
endpoint: "http://127.0.0.1:9/mcp",
apiKey: secret,
input: input(),
write: (line) => output.push(line),
});
} finally {
console.error = realError;
}
assert.deepEqual(output.map((line) => JSON.parse(line)), [
{ jsonrpc: "2.0", id: 7, error: { code: -32000, message: "Titen MCP request failed." } },
{ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error." } },
{ jsonrpc: "2.0", id: 8, error: { code: -32000, message: "Titen MCP request failed." } },
]);
// -32000 says only that something failed. A revoked key and a dead port were
// the same sentence until the reason went somewhere readable. Three lines for
// two replies: the notification fails too, and used to vanish entirely.
assert.equal(logged.length, 3, logged.join("\n"));
for (const line of logged) assert.match(line, /^titen: http:\/\/127\.0\.0\.1:9\/mcp failed: ./);
assert.doesNotMatch(`${output.join("")}${logged.join("")}`, new RegExp(secret));
});
test("the API key reaches only the authorization header and is redacted upstream", async () => {
async function* input() {
yield new TextEncoder().encode(JSON.stringify({ jsonrpc: "2.0", id: 9, method: "ping" }));
}
const output: string[] = [];
const secret = "header-only-key";
await runMcpStdio({
endpoint: "https://titen.example/mcp",
apiKey: secret,
input: input(),
fetcher: async (_input, init) => {
assert.equal(new Headers(init?.headers).get("authorization"), `Bearer ${secret}`);
return new Response(JSON.stringify({ jsonrpc: "2.0", id: 9, result: { echo: secret } }));
},
write: (line) => output.push(line),
});
assert.deepEqual(JSON.parse(output[0]!), {
jsonrpc: "2.0",
id: 9,
result: { echo: "[redacted]" },
});
assert.doesNotMatch(output.join(""), new RegExp(secret));
});
async function bridge(lines: unknown[], fetcher?: (input: string | URL, init?: RequestInit) => Promise<Response>) {
async function* input() {
yield new TextEncoder().encode(lines.map((line) => JSON.stringify(line)).join("\n"));
}
const output: string[] = [];
await runMcpStdio({ endpoint: `${running.url}/mcp`, apiKey: key, input: input(), fetcher, write: (line) => output.push(line) });
return output;
}
test("a batch on stdin gets the same one-line array that POST /mcp returns", async () => {
const ping = { jsonrpc: "2.0", id: 1, method: "ping" };
const batch = [ping, initialized, tools];
const direct = JSON.parse(await http(batch));
const output = await bridge([batch]);
assert.equal(output.length, 1);
const bridged = JSON.parse(output[0]!);
assert.ok(Array.isArray(bridged));
assert.deepEqual(bridged.map((reply: { id: unknown }) => reply.id), [1, 2]);
assert.deepEqual(bridged, direct);
});
test("a notification-only batch writes nothing", async () => {
assert.deepEqual(await bridge([[initialized, { jsonrpc: "2.0", method: "notifications/cancelled", params: {} }]]), []);
});
test("an initialize inside a batch sets the protocol header for later requests", async () => {
const versions: Array<string | null> = [];
const output = await bridge([[initialize, { jsonrpc: "2.0", id: 3, method: "ping" }], tools], async (input, init) => {
versions.push(new Headers(init?.headers).get("mcp-protocol-version"));
return fetch(input, init);
});
assert.equal(output.length, 2);
assert.deepEqual(versions, [null, "2025-06-18"]);
});
test("a failed batch writes one error per request id", async () => {
async function* input() {
yield new TextEncoder().encode(JSON.stringify([
{ jsonrpc: "2.0", id: 4, method: "ping" },
initialized,
{ jsonrpc: "2.0", id: "five", method: "tools/list" },
]));
}
const output: string[] = [];
const realError = console.error;
console.error = () => {};
try {
await runMcpStdio({ endpoint: "http://127.0.0.1:9/mcp", apiKey: "batch-key", input: input(), write: (line) => output.push(line) });
} finally {
console.error = realError;
}
assert.deepEqual(output.map((line) => JSON.parse(line)), [
{ jsonrpc: "2.0", id: 4, error: { code: -32000, message: "Titen MCP request failed." } },
{ jsonrpc: "2.0", id: "five", error: { code: -32000, message: "Titen MCP request failed." } },
]);
});
test("unsafe endpoints fail before fetch", async () => {
async function* empty() {}
for (const endpoint of [
"file:///tmp/mcp",
"http://user:pass@localhost/mcp",
"http://localhost/mcp?token=secret",
"http://localhost/mcp#secret",
"http://localhost/not-mcp",
]) {
let fetched = false;
await assert.rejects(runMcpStdio({
endpoint,
apiKey: "safe-key",
input: empty(),
fetcher: async () => {
fetched = true;
return new Response();
},
}), /credential-free HTTP \/mcp endpoint/);
assert.equal(fetched, false);
}
});