mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
Panels on one MCP server share six requests in flight; further requests wait their turn (up to 256 waiting) instead of failing, and a waiting request leaves the queue when its caller cancels. Held responses belong to their own MCP server rather than to the process. /mcp request bodies must arrive within 30 seconds. Over stdio, a client's notifications/cancelled aborts the named request, which then gets no response. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
214 lines
7.0 KiB
JavaScript
214 lines
7.0 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { PassThrough } from 'node:stream';
|
|
import { createLocalMcpServer } from '../../server/mcp/server.js';
|
|
import { createApiFetch } from '../../server/mcp/services.js';
|
|
import { parseArgs, serveStdio } from '../../server/mcp/stdio.js';
|
|
import { coreTools, toolsForSurface } from './index.js';
|
|
|
|
const usgs = {
|
|
type: 'FeatureCollection',
|
|
features: [
|
|
{
|
|
id: 'us1',
|
|
geometry: { type: 'Point', coordinates: [121.5, 24, 10] },
|
|
properties: { mag: 5.1, place: 'near Hualien', time: 1767229200000 },
|
|
},
|
|
],
|
|
};
|
|
|
|
test('relative API requests resolve against the configured base', async () => {
|
|
const seen = [];
|
|
const apiFetch = createApiFetch({
|
|
apiBase: 'http://127.0.0.1:5000',
|
|
fetchImpl: async (url) => seen.push(String(url)),
|
|
});
|
|
await apiFetch('/api/launches');
|
|
await apiFetch('https://earthquake.usgs.gov/feed.geojson');
|
|
assert.deepEqual(seen, [
|
|
'http://127.0.0.1:5000/api/launches',
|
|
'https://earthquake.usgs.gov/feed.geojson',
|
|
]);
|
|
assert.throws(
|
|
() => createApiFetch({ apiBase: 'file:///etc' }),
|
|
/http\(s\) URL/,
|
|
);
|
|
});
|
|
|
|
test('the stdio server answers newline-delimited requests using only its data sources', async () => {
|
|
const contacted = [];
|
|
const server = createLocalMcpServer({
|
|
apiBase: 'http://127.0.0.1:5000',
|
|
fetchImpl: async (url) => {
|
|
contacted.push(new URL(url).origin);
|
|
return Response.json(usgs);
|
|
},
|
|
});
|
|
const input = new PassThrough();
|
|
const output = new PassThrough();
|
|
let written = '';
|
|
output.on('data', (chunk) => (written += chunk));
|
|
const served = serveStdio(server, { input, output });
|
|
input.write(
|
|
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}\n',
|
|
);
|
|
input.write('{"jsonrpc":"2.0","method":"notifications/initialized"}\n\n');
|
|
input.write('not json\n');
|
|
input.write('{"jsonrpc":"2.0","id":2,"method":"tools/list"}\n');
|
|
input.end(
|
|
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_earthquakes","arguments":{}}}\n',
|
|
);
|
|
await served;
|
|
const responses = written
|
|
.trim()
|
|
.split('\n')
|
|
.map((line) => JSON.parse(line));
|
|
const byId = new Map(responses.map((response) => [response.id, response]));
|
|
assert.equal(responses.length, 4);
|
|
assert.equal(byId.get(1).result.serverInfo.name, 'gods-eye-view');
|
|
assert.equal(byId.get(null).error.code, -32700);
|
|
// Every Core tool's services are composed locally.
|
|
assert.deepEqual(
|
|
byId.get(2).result.tools.map((tool) => tool.name),
|
|
toolsForSurface(coreTools, 'mcp').map((tool) => tool.name),
|
|
);
|
|
assert.equal(
|
|
byId.get(3).result.content[0].text,
|
|
'1 earthquake of M2.5+ in the last 24 hours worldwide; strongest M5.1 near Hualien.',
|
|
);
|
|
// The earthquake feed is fetched directly; nothing else was contacted.
|
|
assert.deepEqual(contacted, ['https://earthquake.usgs.gov']);
|
|
});
|
|
|
|
test('command-line arguments are strict', () => {
|
|
assert.deepEqual(parseArgs([]), {});
|
|
assert.deepEqual(parseArgs(['--api-base', 'http://localhost:5173']), {
|
|
apiBase: 'http://localhost:5173',
|
|
});
|
|
assert.throws(() => parseArgs(['--port', '1']), /Unknown argument/);
|
|
});
|
|
|
|
test('the stdio log names methods, tools and resources but never arguments', async () => {
|
|
const { describeRequest } = await import('../../server/mcp/stdio.js');
|
|
assert.equal(describeRequest({ method: 'tools/list' }), 'tools/list');
|
|
assert.equal(
|
|
describeRequest({
|
|
method: 'tools/call',
|
|
params: { name: 'get_weather', arguments: { location: { place: 'x' } } },
|
|
}),
|
|
'tools/call get_weather',
|
|
);
|
|
assert.equal(
|
|
describeRequest({ method: 'resources/read', params: { uri: 'ui://a/b' } }),
|
|
'resources/read ui://a/b',
|
|
);
|
|
assert.equal(describeRequest({ result: {} }), null);
|
|
});
|
|
|
|
test('a failed tool call is logged with its reason', async () => {
|
|
const server = {
|
|
handle: async (message) => ({
|
|
jsonrpc: '2.0',
|
|
id: message.id,
|
|
result: { isError: true, content: [{ type: 'text', text: 'bad area' }] },
|
|
}),
|
|
};
|
|
const input = new PassThrough();
|
|
const output = new PassThrough();
|
|
output.resume();
|
|
const logged = [];
|
|
const served = serveStdio(server, {
|
|
input,
|
|
output,
|
|
log: (line) => logged.push(line),
|
|
});
|
|
input.end(
|
|
'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"show_in_gods_eye_view"}}\n',
|
|
);
|
|
await served;
|
|
assert.deepEqual(logged, [
|
|
'tools/call show_in_gods_eye_view',
|
|
' tools/call show_in_gods_eye_view failed: bad area',
|
|
]);
|
|
});
|
|
|
|
test("only the key in the server's own panel page opens panel requests", async () => {
|
|
const requested = [];
|
|
const server = createLocalMcpServer({
|
|
apiBase: 'http://127.0.0.1:5000',
|
|
fetchImpl: async (url) => {
|
|
requested.push(new URL(url).pathname);
|
|
return new Response('ok');
|
|
},
|
|
});
|
|
const call = (id, args) =>
|
|
server.handle({
|
|
jsonrpc: '2.0',
|
|
id,
|
|
method: 'tools/call',
|
|
params: { name: 'panel_request', arguments: args },
|
|
});
|
|
const page = await server.handle({
|
|
jsonrpc: '2.0',
|
|
id: 1,
|
|
method: 'resources/read',
|
|
params: { uri: 'ui://gods-eye-view/globe' },
|
|
});
|
|
const [, key] = page.result.contents[0].text.match(/"panelKey":"([^"]+)"/);
|
|
assert.ok(key.length >= 40);
|
|
const refused = await call(2, { path: '/panel/index.html' });
|
|
assert.equal(refused.result.isError, true);
|
|
const opened = await call(3, { key, path: '/panel/index.html' });
|
|
assert.equal(opened.result.isError, false);
|
|
assert.deepEqual(requested, ['/panel/index.html']);
|
|
// Every server makes its own key.
|
|
const other = createLocalMcpServer({ apiBase: 'http://127.0.0.1:5000' });
|
|
const otherPage = await other.handle({
|
|
jsonrpc: '2.0',
|
|
id: 4,
|
|
method: 'resources/read',
|
|
params: { uri: 'ui://gods-eye-view/globe' },
|
|
});
|
|
assert.doesNotMatch(otherPage.result.contents[0].text, new RegExp(key));
|
|
});
|
|
|
|
test('a request the client cancels over stdio is aborted and gets no answer', async () => {
|
|
let seen;
|
|
const server = {
|
|
handle: (message, { signal } = {}) => {
|
|
if (message.id === 1) {
|
|
seen = signal;
|
|
return new Promise((resolve) =>
|
|
signal.addEventListener(
|
|
'abort',
|
|
() => resolve({ jsonrpc: '2.0', id: 1, result: {} }),
|
|
{ once: true },
|
|
),
|
|
);
|
|
}
|
|
return Promise.resolve(
|
|
message.id === undefined
|
|
? null
|
|
: { jsonrpc: '2.0', id: message.id, result: {} },
|
|
);
|
|
},
|
|
};
|
|
const input = new PassThrough();
|
|
const output = new PassThrough();
|
|
let written = '';
|
|
output.on('data', (chunk) => (written += chunk));
|
|
const served = serveStdio(server, { input, output });
|
|
input.write('{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{}}\n');
|
|
input.write('{"jsonrpc":"2.0","id":2,"method":"ping"}\n');
|
|
input.end(
|
|
'{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":1}}\n',
|
|
);
|
|
await served;
|
|
assert.equal(seen.aborted, true);
|
|
const ids = written
|
|
.trim()
|
|
.split('\n')
|
|
.map((line) => JSON.parse(line).id);
|
|
assert.deepEqual(ids, [2]);
|
|
});
|