| 1 | // Real official SDK + committed builtin bundle; the Rust broker is a fake. |
| 2 | // Rust launch/ticket/HumanDecision receipts live in extension_host::mcp::tests. |
| 3 | import { test } from 'node:test' |
| 4 | import assert from 'node:assert/strict' |
| 5 | import { randomUUID } from 'node:crypto' |
| 6 | import { createMcpModule } from '../dist/builtin/mcp.mjs' |
| 7 | import { validateMessage } from '../dist/protocol.mjs' |
| 8 | |
| 9 | const owner = { plugin_id: 'host:mcp', generation: 1, owner_token: 'builtin-test-token' } |
| 10 | const initParams = { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'codewhale-tui', version: '0.10.1' } } |
| 11 | class FakeBroker { |
| 12 | frames = [] |
| 13 | writes = [] |
| 14 | accepted = [] |
| 15 | grants = new Map() |
| 16 | nextFacadeId = 1 |
| 17 | serverPings = new Set() |
| 18 | waiting = undefined |
| 19 | closed = false |
| 20 | failWrite = undefined |
| 21 | silent = undefined |
| 22 | grant(method, params) { |
| 23 | const grant = { ticket: randomUUID(), operation_id: randomUUID(), method, ...(method.startsWith('notifications/') ? {} : { wire_id: String(this.nextFacadeId++) }), params: structuredClone(params) } |
| 24 | this.grants.set(grant.ticket, structuredClone(grant)) |
| 25 | return grant |
| 26 | } |
| 27 | push(frame) { if (frame.method === 'ping') this.serverPings.add(JSON.stringify(frame.id)); if (this.waiting) { const take = this.waiting; this.waiting = undefined; take.resolve({ frame }) } else this.frames.push(frame) } |
| 28 | async request(method, params, signal) { |
| 29 | assert.deepEqual(params.owner, owner) |
| 30 | if (method === 'proc/launch') { assert.equal(params.ticket, 'launch-once'); return {} } |
| 31 | if (method === 'proc/close') { this.closed = true; this.waiting?.reject(new Error('fake broker closed')); this.waiting = undefined; return {} } |
| 32 | if (method === 'proc/read') { |
| 33 | if (this.closed) return { closed: true } |
| 34 | if (this.frames.length) return { frame: this.frames.shift() } |
| 35 | return new Promise((resolve, reject) => { |
| 36 | const abort = () => { this.waiting = undefined; reject(new Error('fake read aborted')) } |
| 37 | this.waiting = { resolve: value => { signal?.removeEventListener('abort', abort); resolve(value) }, reject: error => { signal?.removeEventListener('abort', abort); reject(error) } } |
| 38 | if (signal?.aborted) abort(); else signal?.addEventListener('abort', abort, { once: true }) |
| 39 | }) |
| 40 | } |
| 41 | assert.equal(method, 'proc/write') |
| 42 | const frame = params.frame |
| 43 | this.writes.push(structuredClone(frame)) |
| 44 | if (frame.method === undefined) { |
| 45 | assert.ok(this.serverPings.delete(JSON.stringify(frame.id)), 'response must target an observed ping') |
| 46 | assert.deepEqual(frame.result, {}) |
| 47 | } else if (frame.method !== 'notifications/cancelled') { |
| 48 | const grant = this.grants.get(params.ticket) |
| 49 | assert.ok(grant, 'single-use grant must exist') |
| 50 | assert.equal(grant.operation_id, params.operation_id) |
| 51 | assert.equal(grant.method, frame.method) |
| 52 | if (grant.wire_id !== undefined) assert.equal(frame.id, grant.wire_id) |
| 53 | assert.deepEqual(grant.params, frame.params ?? {}) |
| 54 | this.grants.delete(params.ticket) |
| 55 | } |
| 56 | if (this.failWrite !== undefined && frame.method === this.failWrite) throw new Error('fake partial write failure') |
| 57 | this.accepted.push(structuredClone(frame)) |
| 58 | if (frame.method === undefined || !Object.hasOwn(frame, 'id') || frame.method === this.silent) return {} |
| 59 | const result = frame.method === 'initialize' |
| 60 | ? { protocolVersion: '2025-06-18', capabilities: { tools: {}, resources: {}, prompts: {} }, serverInfo: { name: 'fixture', version: '1.0' } } |
| 61 | : frame.method === 'tools/list' |
| 62 | ? { tools: [{ name: 'echo', inputSchema: { type: 'object' } }], nextCursor: 'next-page' } |
| 63 | : frame.method === 'resources/list' ? { resources: [] } |
| 64 | : frame.method === 'resources/templates/list' ? { resourceTemplates: [] } |
| 65 | : frame.method === 'prompts/list' ? { prompts: [] } |
| 66 | : { content: [{ type: 'text', text: 'ok' }], isError: false } |
| 67 | this.push({ jsonrpc: '2.0', id: frame.id, result }) |
| 68 | return {} |
| 69 | } |
| 70 | } |
| 71 | async function fixture() { |
| 72 | const broker = new FakeBroker(), module = createMcpModule(broker, owner) |
| 73 | const open = { owner, session_id: randomUUID(), launch_ticket: 'launch-once', initialize_grant: broker.grant('initialize', initParams), initialized_grant: broker.grant('notifications/initialized', {}), client_version: '0.10.1', deadline_ms: 1000 } |
| 74 | return { broker, module, open, signal: new AbortController().signal } |
| 75 | } |
| 76 | |
| 77 | test('pinned SDK performs one legacy handshake and preserves per-page cursor without list cache aggregation', async () => { |
| 78 | const f = await fixture() |
| 79 | try { |
| 80 | const ready = await f.module.open(f.open, f.signal) |
| 81 | assert.equal(ready.protocolVersion, '2025-06-18') |
| 82 | assert.deepEqual(f.broker.accepted.map(f => f.method), ['initialize', 'notifications/initialized']) |
| 83 | assert.equal(f.broker.accepted[0].id, f.open.initialize_grant.wire_id) |
| 84 | assert.equal(f.broker.accepted[0].id, '1') |
| 85 | const result = await f.module.request({ owner, session_id: f.open.session_id, grant: f.broker.grant('tools/list', {}), deadline_ms: 1000 }, f.signal) |
| 86 | assert.equal(result.nextCursor, 'next-page') |
| 87 | assert.equal(f.broker.accepted.filter(f => f.method === 'tools/list').length, 1) |
| 88 | assert.equal(f.broker.accepted.some(f => f.method === 'server/discover'), false) |
| 89 | } finally { await f.module.dispose() } |
| 90 | assert.equal(f.broker.closed, true) |
| 91 | }) |
| 92 | |
| 93 | test('decoded initialize mismatch fails before a broker write', async () => { |
| 94 | const f = await fixture() |
| 95 | f.open.initialize_grant.params.capabilities = { sampling: {} } |
| 96 | await assert.rejects(f.module.open(f.open, f.signal), /exact operation grant/) |
| 97 | assert.equal(f.broker.writes.length, 0) |
| 98 | assert.equal(f.broker.closed, true) |
| 99 | }) |
| 100 | |
| 101 | test('plugin-tier frames cannot redeem imported builtin code, and stale owner identity is refused', async () => { |
| 102 | const frame = { jsonrpc: '2.0', id: 1, method: 'proc/write', params: { owner, session_id: 's', frame: { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }, ticket: 't', operation_id: 'o' } } |
| 103 | assert.throws(() => validateMessage(frame, 'host_to_core', 'plugin'), /not allowed/) |
| 104 | validateMessage(frame, 'host_to_core', 'builtin') |
| 105 | assert.throws(() => createMcpModule(new FakeBroker(), { ...owner, plugin_id: 'user/p/foreign' }), /pinned builtin/) |
| 106 | const f = await fixture() |
| 107 | await assert.rejects(f.module.open({ ...f.open, owner: { ...owner, generation: 2 } }, f.signal), /no longer live/) |
| 108 | assert.equal(f.broker.writes.length, 0) |
| 109 | await f.module.dispose() |
| 110 | }) |
| 111 | |
| 112 | test('partial write retires the pipe and never replays tools/call', async () => { |
| 113 | const f = await fixture() |
| 114 | await f.module.open(f.open, f.signal) |
| 115 | f.broker.failWrite = 'tools/call' |
| 116 | await assert.rejects(f.module.request({ owner, session_id: f.open.session_id, grant: f.broker.grant('tools/call', { name: 'echo', arguments: {} }), deadline_ms: 1000 }, f.signal), /partial write|Connection closed/) |
| 117 | assert.equal(f.broker.closed, true) |
| 118 | assert.equal(f.broker.writes.filter(f => f.method === 'tools/call').length, 1) |
| 119 | await f.module.dispose() |
| 120 | }) |
| 121 | |
| 122 | test('single-use request replay is refused and shuts down the same session', async () => { |
| 123 | const f = await fixture() |
| 124 | try { |
| 125 | await f.module.open(f.open, f.signal) |
| 126 | const grant = f.broker.grant('tools/list', {}) |
| 127 | await f.module.request({ owner, session_id: f.open.session_id, grant, deadline_ms: 1000 }, f.signal) |
| 128 | await assert.rejects(f.module.request({ owner, session_id: f.open.session_id, grant, deadline_ms: 1000 }, f.signal), /single-use grant|Connection closed/) |
| 129 | assert.equal(f.broker.accepted.filter(f => f.method === 'tools/list').length, 1) |
| 130 | assert.equal(f.broker.closed, true) |
| 131 | } finally { await f.module.dispose() } |
| 132 | }) |
| 133 | |
| 134 | test('deadline cancellation targets the admitted request and owner disposal refuses queued work', async () => { |
| 135 | const f = await fixture() |
| 136 | await f.module.open(f.open, f.signal) |
| 137 | f.broker.silent = 'tools/list' |
| 138 | const first = f.module.request({ owner, session_id: f.open.session_id, grant: f.broker.grant('tools/list', {}), deadline_ms: 20 }, f.signal) |
| 139 | const queued = f.module.request({ owner, session_id: f.open.session_id, grant: f.broker.grant('prompts/list', {}), deadline_ms: 1000 }, f.signal) |
| 140 | const waitFirst = assert.rejects(first, /timed out/) |
| 141 | const waitQueued = assert.rejects(queued, /no longer live|closed|cancelled|stale/) |
| 142 | await waitFirst |
| 143 | await f.module.dispose() |
| 144 | await waitQueued |
| 145 | const request = f.broker.accepted.find(f => f.method === 'tools/list') |
| 146 | const cancel = f.broker.writes.find(f => f.method === 'notifications/cancelled') |
| 147 | assert.equal(cancel?.params.requestId, request.id) |
| 148 | assert.equal(f.broker.accepted.some(f => f.method === 'prompts/list'), false) |
| 149 | }) |
| 150 | |
| 151 | test('SDK answers an observed ping with exactly empty result and no operation ticket', async () => { |
| 152 | const f = await fixture() |
| 153 | try { |
| 154 | await f.module.open(f.open, f.signal) |
| 155 | f.broker.push({ jsonrpc: '2.0', id: 'server-ping', method: 'ping', params: {} }) |
| 156 | await new Promise((resolve, reject) => { const timer = setInterval(() => { |
| 157 | if (f.broker.accepted.some(frame => frame.id === 'server-ping')) { clearInterval(timer); resolve() } |
| 158 | }, 1); setTimeout(() => { clearInterval(timer); reject(new Error('ping not answered')) }, 500).unref() }) |
| 159 | const answer = f.broker.accepted.find(frame => frame.id === 'server-ping') |
| 160 | assert.deepEqual(answer, { jsonrpc: '2.0', id: 'server-ping', result: {} }) |
| 161 | } finally { await f.module.dispose() } |
| 162 | }) |
| 163 |