Files
ECC/tests/scripts/memory-mcp.test.js
T
Affaan MustafaandGitHub c7d62c0c6a Distinguish declared goals, open sessions and overlap risk in coordination inventory (#3028)
* feat: add read-only coordination inventory and overlap evaluation

* test: make coordination process fixtures platform explicit

* test: report bounded Stop wrapper failure diagnostics

* test: clean up failed memory MCP sessions deterministically

* fix: update js-yaml to patched 4.3.2

* feat(coordination): distinguish declared goals from open sessions
2026-09-10 14:11:51 +03:00

900 lines
30 KiB
JavaScript

'use strict';
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { spawn, spawnSync } = require('child_process');
const { PassThrough } = require('stream');
const { pathToFileURL } = require('url');
const SERVER = path.join(__dirname, '..', '..', 'scripts', 'memory-mcp.mjs');
const {
MAX_RESULTS,
resolveVaultRoots,
saveMemory,
} = require('../../scripts/lib/memory-vault');
let passed = 0;
let failed = 0;
async function test(name, fn) {
try {
await fn();
console.log(` PASS ${name}`);
passed += 1;
} catch (error) {
console.log(` FAIL ${name}`);
console.log(` ${error.mcpDiagnostic ? JSON.stringify(error.mcpDiagnostic) : error.stack || error.message}`);
failed += 1;
}
}
function createFixture(extraEnv = {}) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-memory-mcp-'));
try {
const projectRoot = path.join(root, 'project');
const homeDir = path.join(root, 'home');
fs.mkdirSync(path.join(projectRoot, '.git'), { recursive: true });
fs.mkdirSync(homeDir, { recursive: true });
return {
root,
projectRoot,
env: Object.fromEntries(
Object.entries({
...process.env,
HOME: homeDir,
USERPROFILE: homeDir,
ECC_MEMORY_PROJECT_ROOT: path.join(projectRoot, '.ecc', 'memory'),
ECC_MEMORY_USER_ROOT: path.join(homeDir, '.ecc', 'memory'),
ECC_MEMORY_HARNESS: 'claude',
ECC_MEMORY_ALLOW_USER_SCOPE: '0',
...extraEnv,
}).filter(([, value]) => typeof value === 'string')
),
};
} catch (error) {
try { fs.rmSync(root, { recursive: true, force: true }); }
catch {
const failure = new Error('MCP fixture cleanup failed', { cause: error });
failure.mcpCleanupFailure = 'fixture_removal_error';
throw failure;
}
throw error;
}
}
function parseTextResult(result) {
const text = result.content?.find(item => item.type === 'text')?.text;
assert.ok(text, 'MCP result should contain text');
return JSON.parse(text);
}
async function withClient(fn, options = {}) {
const started = Date.now();
const pending = new Map();
const mode = options.env?.ECC_MEMORY_ALLOW_USER_SCOPE === '1' ? 'allow' : 'deny';
let fixture;
let child;
let phase = 'setup';
let nextId = 1;
let stdout = Buffer.alloc(0);
let stdoutBytes = 0;
let stderrBytes = 0;
let closed = false;
let tearingDown = false;
let transportError;
let primaryError;
let primaryFailed = false;
let failureKind;
let failureElapsedMs;
let teardownStarted;
let failurePhase;
let cleanupFailure;
let killStatus = 'not_attempted';
let notifyClose;
const closePromise = new Promise(resolve => { notifyClose = resolve; });
let rejectTransport;
const transportFailure = new Promise((_, reject) => { rejectTransport = reject; });
// The child may fail before the initialize or callback race is installed.
transportFailure.catch(() => {});
const bounded = value => Math.min(2147483647, Math.max(0, Math.trunc(value)));
const safeCode = error => [
'EPIPE', 'ENOENT', 'EACCES', 'EPERM', 'EINVAL', 'ECONNRESET',
'ERR_STREAM_DESTROYED', 'ERR_STREAM_WRITE_AFTER_END', 'ERR_ASSERTION',
].includes(error?.code) ? error.code : null;
const diagnostic = () => ({
phase: failurePhase || phase,
mode,
reason: failureKind || cleanupFailure || 'assertion_or_callback',
failureElapsedMs: failureElapsedMs ?? null,
teardownElapsedMs: bounded(Date.now() - teardownStarted),
elapsedMs: bounded(Date.now() - started),
stdoutBytes,
stderrBytes,
pendingRequests: pending.size,
childStarted: Boolean(child?.pid),
childClosed: closed,
exitCode: Number.isInteger(child?.exitCode) ? child.exitCode : null,
signal: ['SIGTERM', 'SIGKILL', 'SIGINT'].includes(child?.signalCode) ? child.signalCode : null,
errorCode: safeCode(primaryError),
cleanupFailure: cleanupFailure || null,
killStatus,
});
function settleAll(error) {
for (const waiter of pending.values()) waiter.reject(error);
pending.clear();
}
function fail(kind, cause) {
if (tearingDown) {
cleanupFailure ||= kind;
return;
}
if (transportError) return;
transportError = new Error(`MCP test client ${kind}`);
if (safeCode(cause)) transportError.code = safeCode(cause);
failurePhase = phase;
failureKind = kind;
settleAll(transportError);
rejectTransport(transportError);
}
function send(message) {
if (transportError) throw transportError;
try {
child.stdin.write(`${JSON.stringify(message)}\n`, error => {
if (error) fail('stdin_write_error', error);
});
} catch (error) {
fail('stdin_write_error', error);
throw transportError;
}
}
function request(method, params = {}) {
const id = nextId++;
const promise = new Promise((resolve, reject) => {
if (transportError || tearingDown || closed) {
reject(transportError || new Error('MCP test client is closed'));
return;
}
const timer = setTimeout(() => {
fail('request_timeout');
}, 5000);
function settle(fn, value) {
clearTimeout(timer);
pending.delete(id);
fn(value);
}
pending.set(id, {
resolve: value => settle(resolve, value),
reject: error => settle(reject, error),
});
send({ jsonrpc: '2.0', id, method, params });
});
// Teardown rejects abandoned requests too, without an unhandled rejection.
promise.catch(() => {});
return promise;
}
try {
fixture = createFixture(options.env);
phase = 'spawn';
child = spawn(process.execPath, [options.server || SERVER], {
cwd: fixture.projectRoot,
env: fixture.env,
stdio: ['pipe', 'pipe', 'pipe'],
});
child.on('error', error => fail('child_error', error));
child.on('exit', () => {
if (!tearingDown) fail('child_exit');
});
child.once('close', () => {
closed = true;
notifyClose();
if (!tearingDown) fail('child_close');
});
for (const stream of ['stdin', 'stdout', 'stderr']) {
child[stream].on('error', error => fail(`${stream}_error`, error));
}
child.stdout.on('end', () => { if (!tearingDown) fail('stdout_end'); });
for (const stream of ['stdin', 'stdout']) {
child[stream].on('close', () => { if (!tearingDown) fail(`${stream}_close`); });
}
child.stderr.on('data', chunk => {
stderrBytes = bounded(stderrBytes + chunk.length);
});
child.stdout.on('data', chunk => {
stdoutBytes = bounded(stdoutBytes + chunk.length);
if (transportError || tearingDown) return;
// Decode complete lines, so a UTF-8 character split across chunks survives.
stdout = Buffer.concat([stdout, chunk]);
let newlineIndex;
while ((newlineIndex = stdout.indexOf(10)) >= 0) {
if (newlineIndex > 1024 * 1024) { fail('oversized_frame'); return; }
const line = stdout.subarray(0, newlineIndex).toString('utf8');
stdout = stdout.subarray(newlineIndex + 1);
if (!line.trim()) continue;
let message;
try {
message = JSON.parse(line);
if (!message || message.jsonrpc !== '2.0' || !Number.isInteger(message.id)
|| (Object.hasOwn(message, 'result') === Object.hasOwn(message, 'error'))
|| (Object.hasOwn(message, 'error') && (!message.error
|| !Number.isInteger(message.error.code) || typeof message.error.message !== 'string'))) {
fail('invalid_frame');
return;
}
} catch {
fail('malformed_frame');
return;
}
const waiter = pending.get(message.id);
if (waiter) {
if (message.error) {
// Existing authorization/protocol assertions inspect this RPC error.
// The test logger emits only mcpDiagnostic when it escapes the helper.
waiter.reject(new Error(`${message.error.code}: ${message.error.message}`));
} else {
waiter.resolve(message.result);
}
}
}
if (stdout.length > 1024 * 1024) fail('oversized_frame');
});
phase = 'initialize';
const initialized = await request('initialize', {
protocolVersion: '2025-11-25',
capabilities: {},
clientInfo: { name: 'ecc-memory-test', version: '1.0.0' },
});
phase = 'protocol';
assert.strictEqual(initialized.protocolVersion, '2025-11-25');
phase = 'notification';
send({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} });
const client = {
listTools: () => request('tools/list'),
listToolsRaw: params => request('tools/list', params),
callTool: ({ name, arguments: toolArguments }) => request(
'tools/call',
{ name, arguments: toolArguments }
),
callToolRaw: params => request('tools/call', params),
};
phase = 'callback';
await Promise.race([Promise.resolve().then(() => fn(client, fixture)), transportFailure]);
if (transportError) throw transportError;
assert.strictEqual(pending.size, 0, 'MCP callback must await its requests');
} catch (error) {
primaryError = error;
primaryFailed = true;
failurePhase ||= phase;
failureElapsedMs = bounded(Date.now() - started);
if (error?.mcpCleanupFailure === 'fixture_removal_error') {
cleanupFailure ||= 'fixture_removal_error';
}
} finally {
tearingDown = true;
teardownStarted = Date.now();
phase = 'teardown';
settleAll(new Error('MCP test client is closing'));
stdout = Buffer.alloc(0);
if (child && !closed) {
// Keep the original total 2000 ms budget. Reserve its latter half for
// direct-child termination and stdio close, including on Windows.
let killTimer;
let deadlineTimer;
function terminate() {
try { killStatus = child.kill() ? 'requested' : 'not_sent'; }
catch { killStatus = 'error'; }
}
const deadline = new Promise(resolve => {
deadlineTimer = setTimeout(resolve, 2000);
killTimer = setTimeout(terminate, 1000);
});
try {
try { child.stdin.end(); }
catch {
cleanupFailure ||= 'stdin_end_error';
clearTimeout(killTimer);
terminate();
}
await Promise.race([closePromise, deadline]);
} finally {
clearTimeout(killTimer);
clearTimeout(deadlineTimer);
}
if (!closed) cleanupFailure ||= 'child_close_timeout';
}
if (fixture && (!child || closed)) {
try { fs.rmSync(fixture.root, { recursive: true, force: true }); }
catch { cleanupFailure ||= 'fixture_removal_error'; }
}
}
if (primaryFailed || cleanupFailure) {
if (!primaryFailed) primaryError = new Error('MCP test client cleanup failed');
// Keep the primary assertion/RPC/callback error; cleanup must not replace it.
// A wrapper retains non-extensible or non-Error thrown values as its cause.
if (!primaryError || typeof primaryError !== 'object' || !Object.isExtensible(primaryError)
|| Object.getOwnPropertyDescriptor(primaryError, 'mcpDiagnostic')?.configurable === false
|| Object.getOwnPropertyDescriptor(primaryError, 'mcpCleanupFailure')?.configurable === false) {
primaryError = new Error('MCP test client failed', { cause: primaryError });
}
Object.defineProperty(primaryError, 'mcpDiagnostic', { value: diagnostic(), configurable: true });
if (cleanupFailure) {
Object.defineProperty(primaryError, 'mcpCleanupFailure', { value: cleanupFailure, configurable: true });
}
throw primaryError;
}
}
async function main() {
console.log('\n=== Testing ECC memory MCP server ===\n');
await test('registers the bounded read/write/search/doctor tool surface', async () => {
await withClient(async client => {
const tools = await client.listTools();
assert.deepStrictEqual(
tools.tools.map(tool => tool.name).sort(),
['memory_doctor', 'memory_read', 'memory_save', 'memory_search']
);
const save = tools.tools.find(tool => tool.name === 'memory_save');
const search = tools.tools.find(tool => tool.name === 'memory_search');
assert.ok(save.description.includes('unreviewed'));
assert.ok(!JSON.stringify(save.inputSchema).includes('trust'));
assert.ok(!JSON.stringify(save.inputSchema).includes('sourceHarness'));
assert.ok(!JSON.stringify(search.inputSchema).includes('targetHarness'));
assert.strictEqual(save.inputSchema.properties.body.minLength, 1);
});
});
await test('accepts reserved tools/list params and rejects malformed values', async () => {
await withClient(async client => {
const withMeta = await client.listToolsRaw({
_meta: { progressToken: 'progress-123' },
});
assert.strictEqual(withMeta.tools.length, 4);
const withCursor = await client.listToolsRaw({ cursor: 'next-page' });
assert.strictEqual(withCursor.tools.length, 4);
const withCursorAndMeta = await client.listToolsRaw({
cursor: 'next-page',
_meta: { progressToken: 'progress-456' },
});
assert.strictEqual(withCursorAndMeta.tools.length, 4);
const withoutMeta = await client.listTools();
assert.deepStrictEqual(
withoutMeta.tools.map(tool => tool.name).sort(),
['memory_doctor', 'memory_read', 'memory_save', 'memory_search']
);
for (const badMeta of [null, ['not', 'an', 'object'], 'string', 42, true]) {
await assert.rejects(
client.listToolsRaw({ _meta: badMeta }),
/-32602/,
`expected _meta=${JSON.stringify(badMeta)} to be rejected`
);
}
for (const badCursor of [null, {}, [], 42, true]) {
await assert.rejects(
client.listToolsRaw({ cursor: badCursor }),
/-32602/,
`expected cursor=${JSON.stringify(badCursor)} to be rejected`
);
}
await assert.rejects(
client.listToolsRaw({ unexpected: true }),
/-32602/
);
});
});
await test('accepts the reserved _meta param on tools/call and rejects malformed values', async () => {
await withClient(async client => {
// A valid `_meta` object (e.g. progressToken) must not block the tool call.
const withMeta = await client.callToolRaw({
name: 'memory_doctor',
arguments: {},
_meta: { progressToken: 'progress-123' },
});
assert.ok(Array.isArray(withMeta.content));
// Baseline: no `_meta` still works.
const withoutMeta = await client.callToolRaw({
name: 'memory_doctor',
arguments: {},
});
assert.ok(Array.isArray(withoutMeta.content));
// A malformed `_meta` (null, array, or scalar) must be rejected.
for (const badMeta of [null, ['not', 'an', 'object'], 'string', 42, true]) {
await assert.rejects(
client.callToolRaw({ name: 'memory_doctor', arguments: {}, _meta: badMeta }),
/-32602/,
`expected _meta=${JSON.stringify(badMeta)} to be rejected`
);
}
// Unrelated top-level params must still be rejected.
await assert.rejects(
client.callToolRaw({ name: 'memory_doctor', arguments: {}, unexpected: true }),
/-32602/
);
});
});
await test('starts when the npm bin invokes the server through a symlink', async () => {
const binRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-memory-bin-'));
const binPath = path.join(binRoot, 'ecc-memory-mcp');
fs.symlinkSync(SERVER, binPath);
try {
await withClient(async client => {
const tools = await client.listTools();
assert.strictEqual(tools.tools.length, 4);
}, { server: binPath });
} finally {
fs.rmSync(binRoot, { recursive: true, force: true });
}
});
await test('rejects an oversized partial line and recovers at the next message boundary', async () => {
const {
MAX_MESSAGE_BYTES,
runStdioServer,
} = await import(pathToFileURL(SERVER).href);
const input = new PassThrough();
const output = new PassThrough();
let rawOutput = '';
output.on('data', chunk => {
rawOutput += chunk.toString('utf8');
});
runStdioServer({
input,
output,
serviceOptions: { harness: 'claude' },
});
input.write(Buffer.alloc(MAX_MESSAGE_BYTES + 1, 0x78));
input.write(`\n${JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: '2025-11-25',
capabilities: {},
clientInfo: { name: 'bounded-test', version: '1.0.0' },
},
})}\n`);
input.end();
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('Timed out waiting for bounded output.')), 3000);
const poll = () => {
if (rawOutput.trim().split('\n').length >= 2) {
clearTimeout(timeout);
resolve();
} else {
setImmediate(poll);
}
};
poll();
});
const messages = rawOutput.trim().split('\n').map(line => JSON.parse(line));
assert.strictEqual(messages.length, 2);
assert.strictEqual(messages[0].error.code, -32700);
assert.strictEqual(messages[1].result.protocolVersion, '2025-11-25');
});
await test('shares a saved handoff through MCP search and read', async () => {
await withClient(async client => {
const savedResult = await client.callTool({
name: 'memory_save',
arguments: {
title: 'Codex to Claude handoff',
body: 'The migration is green; review the rollout note.',
kind: 'handoff',
scope: 'project',
targetHarnesses: ['claude'],
tags: ['migration'],
},
});
assert.strictEqual(savedResult.isError, undefined);
const saved = parseTextResult(savedResult);
assert.strictEqual(saved.memory.trust, 'unreviewed');
assert.strictEqual(saved.memory.sourceHarness, 'claude');
assert.strictEqual(Object.hasOwn(saved.memory, 'body'), false);
const searchResult = await client.callTool({
name: 'memory_search',
arguments: {
query: 'migration rollout',
limit: 5,
},
});
const search = parseTextResult(searchResult);
assert.strictEqual(search.results.length, 1);
assert.strictEqual(search.results[0].memory.id, saved.memory.id);
const readResult = await client.callTool({
name: 'memory_read',
arguments: { id: saved.memory.id },
});
const read = parseTextResult(readResult);
assert.strictEqual(read.memory.body, 'The migration is green; review the rollout note.');
const doctorResult = await client.callTool({
name: 'memory_doctor',
arguments: {},
});
const doctor = parseTextResult(doctorResult);
assert.strictEqual(doctor.ok, true);
assert.strictEqual(doctor.memoryCount, 1);
});
});
await test('rejects caller identity spoofing and hides other-harness memories', async () => {
await withClient(async client => {
await assert.rejects(
() => client.callTool({
name: 'memory_save',
arguments: {
title: 'Spoofed source',
body: 'This must not be accepted.',
sourceHarness: 'hermes',
},
}),
/-32602/
);
await assert.rejects(
() => client.callTool({
name: 'memory_search',
arguments: {
query: '',
targetHarness: 'hermes',
},
}),
/-32602/
);
const savedResult = await client.callTool({
name: 'memory_save',
arguments: {
title: 'Hermes-only handoff',
body: 'Only Hermes should receive this context.',
kind: 'handoff',
targetHarnesses: ['hermes'],
},
});
const saved = parseTextResult(savedResult);
assert.strictEqual(saved.memory.sourceHarness, 'claude');
const searchResult = await client.callTool({
name: 'memory_search',
arguments: { query: 'Hermes-only' },
});
assert.strictEqual(parseTextResult(searchResult).results.length, 0);
const readResult = await client.callTool({
name: 'memory_read',
arguments: { id: saved.memory.id },
});
assert.strictEqual(readResult.isError, true);
assert.strictEqual(parseTextResult(readResult).error.code, 'MEMORY_READ_FAILED');
const doctor = parseTextResult(await client.callTool({
name: 'memory_doctor',
arguments: {},
}));
assert.strictEqual(doctor.memoryCount, 0);
assert.strictEqual(Object.hasOwn(doctor, 'brokenLinks'), false);
assert.strictEqual(Object.hasOwn(doctor, 'invalidFiles'), false);
assert.strictEqual(JSON.stringify(doctor).includes(saved.memory.id), false);
});
});
await test('filters harness-visible backlinks before applying the response cap', async () => {
await withClient(async (client, fixture) => {
const roots = resolveVaultRoots({
cwd: fixture.projectRoot,
env: fixture.env,
});
const saveWithId = (input, id) => saveMemory(input, {
roots,
now: () => '2026-07-26T20:00:00.000Z',
idFactory: () => id,
});
const targetId = 'mem_backlink_target';
saveWithId({
title: 'Backlink target',
body: 'Visible target body.',
targetHarnesses: ['claude'],
}, targetId);
for (let index = 0; index < MAX_RESULTS; index += 1) {
saveWithId({
title: `Hidden backlink ${index}`,
body: 'Only Hermes may see this backlink.',
targetHarnesses: ['hermes'],
links: [targetId],
}, `mem_backlink_hidden_${String(index).padStart(3, '0')}`);
}
saveWithId({
title: 'Visible backlink',
body: 'Claude must still receive this backlink.',
targetHarnesses: ['claude'],
links: [targetId],
}, 'mem_backlink_visible_zzz');
const read = parseTextResult(await client.callTool({
name: 'memory_read',
arguments: { id: targetId },
}));
assert.deepStrictEqual(
read.backlinks.map(memory => memory.id),
['mem_backlink_visible_zzz']
);
assert.strictEqual(read.backlinksTruncated, false);
});
});
await test('denies user scope unless the server explicitly grants it', async () => {
await withClient(async client => {
await assert.rejects(
() => client.callTool({
name: 'memory_save',
arguments: {
title: 'Private preference',
body: 'Keep this in the user vault.',
scope: 'user',
},
}),
/user memory scope is disabled/
);
await assert.rejects(
() => client.callTool({
name: 'memory_search',
arguments: { scopes: ['user'] },
}),
/user memory scope is disabled/
);
await assert.rejects(
() => client.callTool({
name: 'memory_read',
arguments: {
id: 'mem_20260726_user_scope_denied',
scope: 'user',
},
}),
/user memory scope is disabled/
);
});
await withClient(async client => {
const savedResult = await client.callTool({
name: 'memory_save',
arguments: {
title: 'Private preference',
body: 'Keep this in the user vault.',
scope: 'user',
},
});
const saved = parseTextResult(savedResult);
assert.strictEqual(saved.memory.scope, 'user');
const defaultSearch = parseTextResult(await client.callTool({
name: 'memory_search',
arguments: { query: 'Private preference' },
}));
assert.strictEqual(defaultSearch.results.length, 0);
const userSearch = parseTextResult(await client.callTool({
name: 'memory_search',
arguments: {
query: 'Private preference',
scopes: ['user'],
},
}));
assert.strictEqual(userSearch.results[0].memory.id, saved.memory.id);
const userRead = parseTextResult(await client.callTool({
name: 'memory_read',
arguments: {
id: saved.memory.id,
scope: 'user',
},
}));
assert.strictEqual(userRead.memory.id, saved.memory.id);
}, { env: { ECC_MEMORY_ALLOW_USER_SCOPE: '1' } });
});
await test('requires server identity and strictly validates JSON-RPC envelopes', async () => {
const { createMemoryMcpService } = await import(pathToFileURL(SERVER).href);
assert.throws(
() => createMemoryMcpService({ env: {} }),
/ECC_MEMORY_HARNESS/
);
const fixture = createFixture({ ECC_MEMORY_HARNESS: undefined });
try {
const started = spawnSync(process.execPath, [SERVER], {
cwd: fixture.projectRoot,
env: fixture.env,
encoding: 'utf8',
});
assert.strictEqual(started.error, undefined);
assert.strictEqual(started.status, 1);
assert.match(started.stderr, /ECC_MEMORY_HARNESS/);
assert.ok(!started.stderr.includes('\n at '));
} finally {
fs.rmSync(fixture.root, { recursive: true, force: true });
}
const service = createMemoryMcpService({ harness: 'claude' });
for (const id of [null, false, {}, [], 1.5, Number.MAX_SAFE_INTEGER + 1, '']) {
const response = await service.handle({
jsonrpc: '2.0',
id,
method: 'initialize',
params: {},
});
assert.strictEqual(response.id, null);
assert.strictEqual(response.error.code, -32600);
}
const initialized = await service.handle({
jsonrpc: '2.0',
id: 0,
method: 'initialize',
params: {
protocolVersion: '2025-11-25',
capabilities: {},
clientInfo: { name: 'strict-test', version: '1.0.0' },
},
});
assert.strictEqual(initialized.id, 0);
await service.handle({
jsonrpc: '2.0',
method: 'notifications/initialized',
params: {},
});
for (const toolArguments of [null, false, 0, '', []]) {
const response = await service.handle({
jsonrpc: '2.0',
id: `args-${String(toolArguments)}`,
method: 'tools/call',
params: {
name: 'memory_doctor',
arguments: toolArguments,
},
});
assert.strictEqual(response.error.code, -32602);
}
const invalidParams = await service.handle({
jsonrpc: '2.0',
id: 2,
method: 'tools/call',
params: [],
});
assert.strictEqual(invalidParams.error.code, -32600);
});
await test('bounds queued transport work under a single-chunk request flood', async () => {
const {
MAX_PENDING_MESSAGES,
runStdioServer,
} = await import(pathToFileURL(SERVER).href);
const input = new PassThrough();
const output = new PassThrough();
let rawOutput = '';
output.on('data', chunk => {
rawOutput += chunk.toString('utf8');
});
runStdioServer({
input,
output,
serviceOptions: { harness: 'claude' },
});
const requests = [
{
jsonrpc: '2.0',
id: 'init',
method: 'initialize',
params: {
protocolVersion: '2025-11-25',
capabilities: {},
clientInfo: { name: 'flood-test', version: '1.0.0' },
},
},
{
jsonrpc: '2.0',
method: 'notifications/initialized',
params: {},
},
...Array.from({ length: MAX_PENDING_MESSAGES * 4 }, (_, index) => ({
jsonrpc: '2.0',
id: `ping-${index}`,
method: 'ping',
params: {},
})),
];
input.end(`${requests.map(JSON.stringify).join('\n')}\n`);
await new Promise((resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error('Timed out waiting for queue-limit response.')),
3000
);
const poll = () => {
if (rawOutput.includes('queue limit exceeded')) {
clearTimeout(timeout);
resolve();
} else {
setImmediate(poll);
}
};
poll();
});
const messages = rawOutput.trim().split('\n').map(line => JSON.parse(line));
assert.ok(messages.some(message => message.error?.code === -32000));
assert.ok(messages.length <= MAX_PENDING_MESSAGES + 2);
});
await test('bounds serialized tool responses before writing to stdout', async () => {
const {
MAX_RESPONSE_BYTES,
textResult,
} = await import(pathToFileURL(SERVER).href);
assert.throws(
() => textResult({ body: 'x'.repeat(MAX_RESPONSE_BYTES + 1) }),
/bounded output limit/
);
});
await test('returns a structured tool error without a stack trace for secret-bearing writes', async () => {
await withClient(async client => {
await assert.rejects(
() => client.callTool({
name: 'memory_save',
arguments: {
title: 'Empty body',
body: '',
},
}),
/-32602/
);
const secret = `ghp_${'A1'.repeat(12)}`;
const result = await client.callTool({
name: 'memory_save',
arguments: {
title: 'Do not persist this',
body: `credential ${secret}`,
},
});
assert.strictEqual(result.isError, true);
const error = parseTextResult(result);
assert.strictEqual(error.error.code, 'MEMORY_WRITE_REJECTED');
assert.ok(error.error.message.includes('suspected secret'));
assert.ok(!JSON.stringify(error).includes(secret));
assert.ok(!JSON.stringify(error).includes('\n at '));
});
});
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
if (failed > 0) {
process.exit(1);
}
}
main().catch(error => {
console.error(error);
process.exit(1);
});