142 lines
4.1 KiB
JavaScript
142 lines
4.1 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const { once } = require('node:events');
|
|
const http = require('node:http');
|
|
const net = require('node:net');
|
|
const { spawn } = require('node:child_process');
|
|
const { test } = require('node:test');
|
|
const path = require('node:path');
|
|
|
|
class LineReader {
|
|
constructor(stream) {
|
|
this.lines = [];
|
|
this.waiters = [];
|
|
this.buffer = '';
|
|
stream.setEncoding('utf8');
|
|
stream.on('data', chunk => {
|
|
this.buffer += chunk;
|
|
const parts = this.buffer.split('\n');
|
|
this.buffer = parts.pop();
|
|
for (const line of parts) {
|
|
this.push(line.replace(/\r$/, ''));
|
|
}
|
|
});
|
|
}
|
|
|
|
push(line) {
|
|
const waiter = this.waiters.shift();
|
|
if (waiter) {
|
|
waiter.resolve(line);
|
|
} else {
|
|
this.lines.push(line);
|
|
}
|
|
}
|
|
|
|
next(timeoutMs = 5000) {
|
|
if (this.lines.length > 0) return Promise.resolve(this.lines.shift());
|
|
return new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
const index = this.waiters.indexOf(waiter);
|
|
if (index >= 0) this.waiters.splice(index, 1);
|
|
reject(new Error('Timed out waiting for a line'));
|
|
}, timeoutMs);
|
|
const waiter = {
|
|
resolve: line => {
|
|
clearTimeout(timeout);
|
|
resolve(line);
|
|
}
|
|
};
|
|
this.waiters.push(waiter);
|
|
});
|
|
}
|
|
}
|
|
|
|
function getFreePort() {
|
|
return new Promise((resolve, reject) => {
|
|
const server = net.createServer();
|
|
server.once('error', reject);
|
|
server.listen(0, '127.0.0.1', () => {
|
|
const { port } = server.address();
|
|
server.close(() => resolve(port));
|
|
});
|
|
});
|
|
}
|
|
|
|
function close(server) {
|
|
if (!server) return Promise.resolve();
|
|
return new Promise(resolve => server.close(() => resolve()));
|
|
}
|
|
|
|
test('reconnects after Burp starts and handles a later tool call', async () => {
|
|
const port = await getFreePort();
|
|
const bridgePath = path.join(__dirname, '..', 'mcp-bridge.js');
|
|
const bridge = spawn(process.execPath, [bridgePath], {
|
|
env: {
|
|
...process.env,
|
|
BURP_MCP_HOST: '127.0.0.1',
|
|
BURP_MCP_PORT: String(port),
|
|
BURP_MCP_TOKEN: 'test-token'
|
|
},
|
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
});
|
|
const stdout = new LineReader(bridge.stdout);
|
|
const stderr = new LineReader(bridge.stderr);
|
|
let server;
|
|
const requests = [];
|
|
|
|
try {
|
|
const warning = await stderr.next();
|
|
assert.match(warning, /Cannot connect to Burp/);
|
|
|
|
server = http.createServer((request, response) => {
|
|
if (request.method === 'GET' && request.url === '/tools') {
|
|
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
response.end(JSON.stringify(['proxy_history']));
|
|
return;
|
|
}
|
|
|
|
let body = '';
|
|
request.on('data', chunk => { body += chunk; });
|
|
request.on('end', () => {
|
|
requests.push(JSON.parse(body));
|
|
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
response.end(JSON.stringify({ ok: true }));
|
|
});
|
|
});
|
|
await new Promise((resolve, reject) => {
|
|
server.once('error', reject);
|
|
server.listen(port, '127.0.0.1', resolve);
|
|
});
|
|
|
|
bridge.stdin.write(JSON.stringify({
|
|
jsonrpc: '2.0',
|
|
id: 1,
|
|
method: 'tools/list',
|
|
params: {}
|
|
}) + '\n');
|
|
const listResponse = JSON.parse(await stdout.next());
|
|
assert.equal(listResponse.id, 1);
|
|
assert.equal(listResponse.result.tools[0].name, 'burp_proxy_history');
|
|
|
|
bridge.stdin.write(JSON.stringify({
|
|
jsonrpc: '2.0',
|
|
id: 2,
|
|
method: 'tools/call',
|
|
params: { name: 'burp_proxy_history', arguments: { limit: 1 } }
|
|
}) + '\n');
|
|
const callResponse = JSON.parse(await stdout.next());
|
|
assert.equal(callResponse.id, 2);
|
|
assert.deepEqual(requests[0], { tool: 'proxy_history', params: { limit: 1 } });
|
|
assert.match(callResponse.result.content[0].text, /"ok": true/);
|
|
} finally {
|
|
const childClosed = bridge.exitCode === null && bridge.signalCode === null
|
|
? once(bridge, 'close')
|
|
: Promise.resolve();
|
|
bridge.stdin.end();
|
|
bridge.kill();
|
|
await close(server);
|
|
await childClosed.catch(() => {});
|
|
}
|
|
});
|