mcp --http: SSE keepalive for long tool calls; public App Store search shares one 20-a-minute budget

This commit is contained in:
CRTkafa 2026-10-29 19:05:53 +07:00
parent c500866295
commit 9620768897
6 changed files with 65 additions and 4 deletions

View file

@ -138,3 +138,24 @@ describe('read-only and broker profiles', () => {
} finally { srv.close(); }
});
});
describe('long calls over SSE', () => {
test('a tools/call that accepts text/event-stream gets keepalive comments, then the answer as one event', async () => {
const { setKeepaliveMs } = await import('../cli/mcp-http');
setKeepaliveMs(50);
const s = await startHttp({ host: '127.0.0.1', port: 0, roots: [root], project: app, allowOrigins: [], auth: { kind: 'token', token: 'b'.repeat(64) } });
try {
const r = await fetch(`http://127.0.0.1:${s.port}/mcp`, { method: 'POST',
headers: { authorization: `Bearer ${'b'.repeat(64)}`, 'content-type': 'application/json', accept: 'application/json, text/event-stream' },
body: JSON.stringify({ jsonrpc: '2.0', id: 9, method: 'tools/call', params: { name: 'check', arguments: { project: '/nope' } } }) });
expect(r.headers.get('content-type')).toBe('text/event-stream');
const text = await r.text();
expect(text.startsWith(': started')).toBe(true);
const data = /event: message\ndata: (.*)\n\n$/.exec(text)!;
expect(JSON.parse(data[1]!)).toMatchObject({ id: 9, result: { isError: true } });
// JSON for everything else
const p = await fetch(`http://127.0.0.1:${s.port}/mcp`, { method: 'POST', headers: { authorization: `Bearer ${'b'.repeat(64)}`, 'content-type': 'application/json', accept: 'application/json, text/event-stream' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping' }) });
expect(p.headers.get('content-type')).toBe('application/json');
} finally { s.close(); setKeepaliveMs(20_000); }
});
});