mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
feat: serve the MCP server at /mcp on the local servers
Mount the local MCP server at /mcp on the development and preview servers for clients that connect by URL. The route accepts only requests from this machine that name a loopback host and, when an Origin is sent, come from a loopback origin, and drains oversized bodies so the 413 response is delivered. The provider-order regression test now pins the route between the providers and the API fallback.
This commit is contained in:
1 parent
b6af8b34e0
commit
469b1e8d89
8 files changed
+268
-7
No files matched your search
@@ -63,10 +63,12 @@ test('root config retains existing named exports and standalone provider order',
|
||||
assert.equal(compatibility[name], value, name);
|
||||
const config = standaloneConfig({ mode: 'test' });
|
||||
assert.deepEqual(
|
||||
config.plugins.slice(2, -1).map((plugin) => plugin.name),
|
||||
config.plugins.slice(2, -2).map((plugin) => plugin.name),
|
||||
providers.localProviderPlugins().map((plugin) => plugin.name),
|
||||
);
|
||||
assert.equal(config.plugins.at(-2).name, 'gev-key-setup');
|
||||
assert.equal(config.plugins.at(-3).name, 'gev-key-setup');
|
||||
// The local MCP route follows every provider and precedes the API fallback.
|
||||
assert.equal(config.plugins.at(-2).name, 'local-mcp');
|
||||
assert.equal(config.plugins.at(-1).name, 'api-not-found');
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createServer, request as httpRequest } from 'node:http';
|
||||
import test from 'node:test';
|
||||
import { isLocalMcpRequest, localMcpPlugin } from '../../server/mcp/plugin.js';
|
||||
|
||||
test('only loopback connections with loopback hosts and origins are local', () => {
|
||||
const local = { remoteAddress: '127.0.0.1', host: 'localhost:4173' };
|
||||
assert.equal(isLocalMcpRequest(local), true);
|
||||
assert.equal(
|
||||
isLocalMcpRequest({ ...local, remoteAddress: '::1', host: '[::1]:4173' }),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
isLocalMcpRequest({ ...local, origin: 'http://127.0.0.1:5173' }),
|
||||
true,
|
||||
);
|
||||
for (const request of [
|
||||
{ ...local, remoteAddress: '192.168.1.20' },
|
||||
{ ...local, host: 'attacker.example:4173' },
|
||||
{ ...local, host: 'localhost.attacker.example' },
|
||||
{ ...local, host: '' },
|
||||
{ ...local, origin: 'https://attacker.example' },
|
||||
{ ...local, origin: 'null' },
|
||||
{ ...local, origin: 'file:///tmp/page.html' },
|
||||
])
|
||||
assert.equal(isLocalMcpRequest(request), false, JSON.stringify(request));
|
||||
});
|
||||
|
||||
test('the /mcp route answers local MCP requests and refuses others', async (t) => {
|
||||
let middleware;
|
||||
const created = [];
|
||||
const plugin = localMcpPlugin({
|
||||
createServer: ({ apiBase }) => {
|
||||
created.push(apiBase);
|
||||
return {
|
||||
handle: async (message) =>
|
||||
message.id === undefined
|
||||
? null
|
||||
: { jsonrpc: '2.0', id: message.id, result: { apiBase } },
|
||||
};
|
||||
},
|
||||
});
|
||||
plugin.configureServer({
|
||||
middlewares: { use: (path, handler) => (middleware = handler) },
|
||||
});
|
||||
const http = createServer((req, res) => middleware(req, res));
|
||||
await new Promise((resolve) => http.listen(0, '127.0.0.1', resolve));
|
||||
t.after(() => http.close());
|
||||
const { port } = http.address();
|
||||
const send = (method, body, headers = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const request = httpRequest(
|
||||
{
|
||||
host: '127.0.0.1',
|
||||
port,
|
||||
path: '/mcp',
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Host: `localhost:${port}`,
|
||||
...headers,
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
let text = '';
|
||||
response.on('data', (chunk) => (text += chunk));
|
||||
response.on('end', () =>
|
||||
resolve({
|
||||
status: response.statusCode,
|
||||
json: () => JSON.parse(text),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
request.on('error', reject);
|
||||
request.end(
|
||||
body === undefined
|
||||
? undefined
|
||||
: typeof body === 'string'
|
||||
? body
|
||||
: JSON.stringify(body),
|
||||
);
|
||||
});
|
||||
const post = (body, headers) => send('POST', body, headers);
|
||||
|
||||
const ok = await post({ jsonrpc: '2.0', id: 1, method: 'ping' });
|
||||
assert.equal(ok.status, 200);
|
||||
assert.deepEqual(await ok.json(), {
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
result: { apiBase: `http://localhost:${port}` },
|
||||
});
|
||||
assert.equal(
|
||||
(await post({ jsonrpc: '2.0', method: 'notifications/initialized' }))
|
||||
.status,
|
||||
202,
|
||||
);
|
||||
await post({ jsonrpc: '2.0', id: 2, method: 'ping' });
|
||||
assert.deepEqual(created, [`http://localhost:${port}`]);
|
||||
|
||||
const foreign = await post(
|
||||
{ jsonrpc: '2.0', id: 3, method: 'ping' },
|
||||
{ Origin: 'https://attacker.example' },
|
||||
);
|
||||
assert.equal(foreign.status, 403);
|
||||
assert.deepEqual(await foreign.json(), {
|
||||
error: 'The MCP server only accepts local requests',
|
||||
});
|
||||
assert.equal((await post({}, { Host: 'attacker.example' })).status, 403);
|
||||
assert.equal(
|
||||
(await post({}, { Host: `localhost.attacker.example:${port}` })).status,
|
||||
403,
|
||||
);
|
||||
assert.equal((await post('x'.repeat(1024 * 1024 + 1))).status, 413);
|
||||
assert.equal(
|
||||
(
|
||||
await fetch(`http://127.0.0.1:${port}/mcp`, {
|
||||
headers: { Host: `localhost:${port}` },
|
||||
})
|
||||
).status,
|
||||
405,
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user