import test from 'node:test'; import assert from 'node:assert/strict'; import * as Plugin from '../src/index.js'; import { agent, fixtureCommand, harness, provideSettings, workspace, writeFixture } from './helpers.js'; import WebServer from '@deepseek-ai/dsh-host-webserver'; /** Start the real host web server on an OS-assigned port and serve the shell index. */ async function startServer(t, ctx) { await ctx.plugin(WebServer, { host: '127.0.0.1', port: 0 }); ctx.webServer.registerFallback((_req, res) => { const html = ctx.webServer.renderIndex('t'); res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); res.end(html); }); return `http://127.0.0.1:${ctx.webServer.port}`; } const get = async (url, headers = {}) => { const response = await fetch(url, { headers }); const text = await response.text(); let json; try { json = JSON.parse(text); } catch { json = undefined; } return { status: response.status, text, json }; }; test('the management route works over real HTTP: token injection, status, connect, disconnect', { timeout: 60000 }, async t => { const ctx = await harness(t), dir = await workspace(t); await writeFixture(dir, '.mcp.json', { mcpServers: { demo: fixtureCommand('http') } }); const base = await startServer(t, ctx); const agentA = agent(ctx, dir, 'http-session'); ctx.agents.enter(agentA, undefined); await ctx.plugin(Plugin, { trustMode: 'manual' }); const index = await get(`${base}/`); assert.equal(index.status, 200); const token = /window\.__WORKSPACE_MCP_TOKEN__="([0-9a-f]{48})"/.exec(index.text)?.[1]; assert.ok(token, 'index carries the control token'); // A page served before this plugin activated has no inline token; the handshake covers it. const handshake = await get(`${base}/workspace-mcp/handshake`); assert.equal(handshake.status, 200); assert.equal(handshake.json.token, token, 'the handshake returns the same per-process token'); const auth = { 'x-workspace-mcp-token': token }; const anonymous = await get(`${base}/workspace-mcp/status?sessionId=http-session`); assert.equal(anonymous.status, 403); const status = await get(`${base}/workspace-mcp/status?sessionId=http-session`, auth); assert.equal(status.status, 200); assert.equal(status.json.workspace, dir); assert.equal(status.json.loaded, false); assert.equal(status.json.trusted, false); assert.deepEqual(status.json.servers.map(row => row.id), ['demo']); assert.equal(status.json.servers[0].state, 'unauthorized', 'nothing authorized this workspace yet'); assert.equal(status.json.trustMode, 'manual'); assert.deepEqual(status.json.allowance, { workspace: null, allowMcp: [], allowTools: [] }); const connected = await get(`${base}/workspace-mcp/connect`, auth); assert.equal(connected.status, 405, 'connect requires POST'); const connectResponse = await fetch(`${base}/workspace-mcp/connect`, { method: 'POST', headers: { ...auth, 'content-type': 'application/json' }, body: JSON.stringify({ sessionId: 'http-session' }), }); const connectedBody = await connectResponse.json(); assert.equal(connectResponse.status, 200); assert.equal(connectedBody.servers[0].state, 'connected'); assert.deepEqual(connectedBody.servers[0].tools, ['mcp__demo__identity', 'mcp__demo__blocked']); assert.equal(connectedBody.selectionMode, 'custom', 'a manual connect pins the selection'); assert.deepEqual(connectedBody.selected, ['demo']); const perServer = await fetch(`${base}/workspace-mcp/disconnect`, { method: 'POST', headers: { ...auth, 'content-type': 'application/json' }, body: JSON.stringify({ sessionId: 'http-session', serverId: 'demo' }), }); const droppedBody = await perServer.json(); assert.equal(perServer.status, 200); assert.equal(droppedBody.servers[0].state, 'unloaded'); assert.deepEqual(droppedBody.selected, []); const unknown = await get(`${base}/workspace-mcp/nope`, auth); assert.equal(unknown.status, 404); }); test('the declaration route lists, edits and removes a project file over real HTTP', { timeout: 60000 }, async t => { const ctx = await harness(t), dir = await workspace(t); const source = [ '{', ' // keep me', ' "mcpServers": {', ' "demo": { "command": "node", "args": ["a"] },', ' "other": { "command": "deno" }', ' }', '}', ].join('\n'); await writeFixture(dir, '.mcp.json', source); const base = await startServer(t, ctx); const agentA = agent(ctx, dir, 'declaration-session'); ctx.agents.enter(agentA, undefined); await ctx.plugin(Plugin, { trustMode: 'all' }); const index = await get(`${base}/`); const token = /window\.__WORKSPACE_MCP_TOKEN__="([0-9a-f]{48})"/.exec(index.text)?.[1]; const headers = { 'content-type': 'application/json', 'x-workspace-mcp-token': token }; const post = async body => { const response = await fetch(`${base}/workspace-mcp/declaration`, { method: 'POST', headers, body: JSON.stringify(body) }); const text = await response.text(); let json; try { json = JSON.parse(text); } catch { json = undefined; } return { status: response.status, json }; }; const { readFile } = await import('node:fs/promises'); const file = `${dir}/.mcp.json`; const listed = await post({ sessionId: 'declaration-session', action: 'list' }); assert.equal(listed.status, 200); assert.equal(listed.json.writable, true); assert.deepEqual(listed.json.targets.map(target => target.serverId).sort(), ['demo', 'other']); const demo = listed.json.targets.find(target => target.serverId === 'demo'); assert.ok(typeof demo.version === 'string', 'each target carries the version it was read at'); const saved = await post({ sessionId: 'declaration-session', action: 'save', serverId: 'demo', changes: [{ name: 'command', value: 'bun' }] }); assert.equal(saved.status, 200); assert.equal(saved.json.ok, true); const afterSave = await readFile(file, 'utf8'); assert.match(afterSave, /"command": "bun"/); assert.match(afterSave, /\/\/ keep me/); const removed = await post({ sessionId: 'declaration-session', action: 'remove', serverId: 'other' }); assert.equal(removed.status, 200); const afterRemove = await readFile(file, 'utf8'); assert.ok(!afterRemove.includes('deno')); // A file that moved under the editor is a conflict the page can retry, not a silent overwrite. await writeFixture(dir, '.mcp.json', '{ "mcpServers": { "demo": { "command": "node" } } }'); const stale = await post({ sessionId: 'declaration-session', action: 'save', serverId: 'demo', changes: [{ name: 'command', value: 'x' }], version: demo.version }); assert.equal(stale.status, 409); assert.equal(stale.json.code, 'DECLARATION_STALE'); // Creating is a write too, and it works even for a preset whose file does not exist yet. const created = await post({ sessionId: 'declaration-session', action: 'write', preset: 'vscode', name: 'fresh', fields: [{ name: 'command', value: 'node' }] }); assert.equal(created.status, 200); assert.equal(created.json.file, '.vscode/mcp.json'); assert.equal(created.json.created, true); const unknown = await post({ sessionId: 'declaration-session', action: 'nope' }); assert.equal(unknown.status, 400); }); test('TOML allowLossy crosses HTTP, control and runtime for save, append, remove and migration', { timeout: 60000 }, async t => { const ctx = await harness(t), dir = await workspace(t); const source = '# original comments\nmodel = "gpt"\n[mcp_servers.demo]\ncommand = "node"\n[mcp_servers.other]\ncommand = "deno"\n'; await writeFixture(dir, '.codex/config.toml', source); const base = await startServer(t, ctx); ctx.agents.enter(agent(ctx, dir, 'toml-session'), undefined); await ctx.plugin(Plugin, { trustMode: 'manual' }); const token = /window\.__WORKSPACE_MCP_TOKEN__="([0-9a-f]{48})"/.exec((await get(`${base}/`)).text)?.[1]; const headers = { 'content-type': 'application/json', 'x-workspace-mcp-token': token }; const post = async body => { const response = await fetch(`${base}/workspace-mcp/declaration`, { method: 'POST', headers, body: JSON.stringify({ sessionId: 'toml-session', ...body }) }); return { status: response.status, json: await response.json() }; }; const { readFile, stat } = await import('node:fs/promises'); const listed = await post({ action: 'list' }); const demo = listed.json.targets.find(item => item.serverId === 'demo'); assert.equal(demo.editable, true); assert.equal(demo.lossy, true); assert.equal(listed.json.presets.find(item => item.id === 'codex').exists, true); const save = { action: 'save', serverId: 'demo', changes: [{ name: 'command', value: 'bun' }], version: demo.version }; const append = { action: 'write', preset: 'codex', name: 'fresh', fields: [{ name: 'command', value: 'node' }] }; const remove = { action: 'remove', serverId: 'other' }; const migrate = { action: 'write', preset: 'root', name: 'demo', fromServerId: 'demo', removeSource: true }; for (const action of [save, append, remove, migrate]) { for (const allowLossy of [undefined, 'true']) { const rejected = await post({ ...action, allowLossy }); assert.equal(rejected.status, 400); assert.equal(rejected.json.code, 'DECLARATION_CONFIRM_REQUIRED'); assert.equal(await readFile(`${dir}/.codex/config.toml`, 'utf8'), source); } } await assert.rejects(stat(`${dir}/.mcp.json`), { code: 'ENOENT' }); for (const action of [save, append, remove, migrate]) { const accepted = await post({ ...action, allowLossy: true }); assert.equal(accepted.status, 200, JSON.stringify(accepted.json)); assert.equal(accepted.json.ok, true); } assert.equal(JSON.parse(await readFile(`${dir}/.mcp.json`, 'utf8')).mcpServers.demo.command, 'bun'); const remaining = await readFile(`${dir}/.codex/config.toml`, 'utf8'); assert.match(remaining, /model = "gpt"/); assert.match(remaining, /mcp_servers\.fresh/); assert.doesNotMatch(remaining, /mcp_servers\.(demo|other)/); }); test('nested under another fiber the route still registers', { timeout: 30000 }, async t => { const ctx = await harness(t), dir = await workspace(t); await writeFixture(dir, '.mcp.json', { mcpServers: { demo: fixtureCommand('nested') } }); const base = await startServer(t, ctx); ctx.agents.enter(agent(ctx, dir, 'nested-session'), undefined); // Load the plugin the way the Cordis loader does: from inside another plugin's fiber. // In the real host `webServer` belongs to a sibling row, so only an injected lookup // can reach it; a bare `ctx.get('webServer')` silently returned undefined there. const host = ctx.plugin({ name: 'test-host', apply(inner) { inner.plugin(Plugin, { trustMode: 'manual' }); }, }); await host; await host.await(); const index = await get(`${base}/`); const token = /window\.__WORKSPACE_MCP_TOKEN__="([0-9a-f]{48})"/.exec(index.text)?.[1]; assert.ok(token, 'the nested plugin injected the control token'); const status = await get(`${base}/workspace-mcp/status?sessionId=nested-session`, { 'x-workspace-mcp-token': token }); assert.equal(status.status, 200); assert.deepEqual(status.json.servers.map(row => row.id), ['demo']); }); test('the preset configuration is editable over the same route', { timeout: 30000 }, async t => { const ctx = await harness(t), dir = await workspace(t); const settings = provideSettings(ctx, { enabledBuiltins: ['root'], presets: [] }); const base = await startServer(t, ctx); ctx.agents.enter(agent(ctx, dir, 'config-session'), undefined); await ctx.plugin(Plugin, {}); const token = /window\.__WORKSPACE_MCP_TOKEN__="([0-9a-f]{48})"/.exec((await get(`${base}/`)).text)?.[1]; const auth = { 'x-workspace-mcp-token': token }; const before = await get(`${base}/workspace-mcp/config`, auth); assert.equal(before.status, 200); assert.equal(before.json.editable, true); assert.deepEqual(before.json.config.enabledBuiltins, ['root']); assert.deepEqual(before.json.builtins.map(preset => preset.id), ['root', 'vscode', 'codex', 'cursor', 'gemini']); const patch = { enabledBuiltins: ['root', 'codex'], presets: [{ id: 'team', files: ['.team/mcp.yaml'], serversPaths: ['/integrations/mcp'], format: 'yaml' }], inputValues: { secret: 'never-forwarded' }, }; const saved = await fetch(`${base}/workspace-mcp/config`, { method: 'POST', headers: auth, body: JSON.stringify({ patch }) }); const savedBody = await saved.json(); assert.equal(saved.status, 200); assert.deepEqual(savedBody.config.enabledBuiltins, ['root', 'codex']); assert.deepEqual(savedBody.config.presets.map(preset => preset.id), ['team']); assert.equal(settings.updates.length, 1); assert.equal(settings.updates[0].ns, 'entry-workspace-mcp-glue'); assert.equal(settings.updates[0].revision, 1); assert.deepEqual(Object.keys(settings.updates[0].patch).sort(), ['enabledBuiltins', 'presets']); assert.equal(settings.state.value.inputValues, undefined); const invalid = await fetch(`${base}/workspace-mcp/config`, { method: 'POST', headers: auth, body: JSON.stringify({ patch: { presets: [{ id: 'bad', files: [], serversPaths: ['x'] }] } }), }); assert.equal(invalid.status, 400); assert.match((await invalid.json()).error, /files must be nonempty/); }); test('webControl: false leaves the workspace state without a control route', { timeout: 30000 }, async t => { const ctx = await harness(t), dir = await workspace(t); await writeFixture(dir, '.mcp.json', { mcpServers: { demo: fixtureCommand('off') } }); const base = await startServer(t, ctx); ctx.agents.enter(agent(ctx, dir, 'off-session'), undefined); await ctx.plugin(Plugin, { trustMode: 'manual', webControl: false }); const index = await get(`${base}/`); assert.ok(!index.text.includes('__WORKSPACE_MCP_TOKEN__'), 'no token is injected'); // Without the route the shell fallback answers, so the body is never our JSON. const probe = await get(`${base}/workspace-mcp/status?sessionId=off-session`); assert.equal(probe.json, undefined); assert.equal(probe.status, 200); });