(opts: { failWithStatus?: number; failBody?: string } = {})
| 27 | |
| 28 | // Spin up a stub MCP server that responds to initialize + tools/list. |
| 29 | function startStubMcpServer(opts: { failWithStatus?: number; failBody?: string } = {}): Promise<{ url: string; close: () => Promise<void> }> { |
| 30 | return new Promise((resolve) => { |
| 31 | const server = http.createServer((req, res) => { |
| 32 | if (req.method !== 'POST' || !(req.url ?? '').endsWith('/mcp')) { |
| 33 | res.statusCode = 404; |
| 34 | res.end(); |
| 35 | return; |
| 36 | } |
| 37 | let body = ''; |
| 38 | req.on('data', (c) => (body += c)); |
| 39 | req.on('end', () => { |
| 40 | if (opts.failWithStatus) { |
| 41 | res.statusCode = opts.failWithStatus; |
| 42 | res.setHeader('Content-Type', 'application/json'); |
| 43 | res.end(opts.failBody ?? JSON.stringify({ error: 'fail' })); |
| 44 | return; |
| 45 | } |
| 46 | const reqJson = (() => { |
| 47 | try { return JSON.parse(body); } catch { return {} as any; } |
| 48 | })(); |
| 49 | let respBody: any; |
| 50 | if (reqJson.method === 'initialize') { |
| 51 | respBody = { |
| 52 | result: { |
| 53 | protocolVersion: '2024-11-05', |
| 54 | capabilities: { tools: {} }, |
| 55 | serverInfo: { name: 'gbrain', version: '0.27.1' }, |
| 56 | }, |
| 57 | jsonrpc: '2.0', |
| 58 | id: reqJson.id, |
| 59 | }; |
| 60 | } else if (reqJson.method === 'tools/list') { |
| 61 | respBody = { result: { tools: [{ name: 'search' }, { name: 'put_page' }] }, jsonrpc: '2.0', id: reqJson.id }; |
| 62 | } else { |
| 63 | respBody = { error: { code: -32601, message: 'unknown method' }, jsonrpc: '2.0', id: reqJson.id }; |
| 64 | } |
| 65 | // SSE-shape since the verify helper supports both, and many MCP |
| 66 | // servers (including wintermute) wrap responses as SSE. |
| 67 | res.statusCode = 200; |
| 68 | res.setHeader('Content-Type', 'text/event-stream'); |
| 69 | res.end(`event: message\ndata: ${JSON.stringify(respBody)}\n\n`); |
| 70 | }); |
| 71 | }); |
| 72 | server.listen(0, '127.0.0.1', () => { |
| 73 | const addr = server.address(); |
| 74 | if (!addr || typeof addr === 'string') throw new Error('no address'); |
| 75 | resolve({ |
| 76 | url: `http://127.0.0.1:${addr.port}/mcp`, |
| 77 | close: () => new Promise((r) => server.close(() => r())), |
| 78 | }); |
| 79 | }); |
| 80 | }); |
| 81 | } |
| 82 | |
| 83 | // Stubbed `claude` binary: intercepts `mcp add` and `mcp list` commands so |
| 84 | // the skill's Step 5a registration appears to succeed, while we record |
no test coverage detected