/** Real production ConnectorsPage in isolated Electron with a controlled MCP host. * Only the host IPC is faked; the page, styles, icons and helpers are the shipped ones. * No WorkBuddy runtime or original evidence is accessed by this script. */ import fs from 'node:fs' import path from 'node:path' import assert from 'node:assert/strict' import { createRequire } from 'node:module' import { spawn } from 'node:child_process' const require = createRequire(import.meta.url) const electronModule = require('electron') const electron = typeof electronModule === 'object' && electronModule.app ? electronModule : null const root = path.resolve('.'), base = path.join(root, '.tmp') if (!electron) { fs.mkdirSync(base, { recursive: true }) const output = fs.mkdtempSync(path.join(base, 'connectors-page-')), env = { ...process.env } delete env.ELECTRON_RUN_AS_NODE; delete env.ELECTRON_RENDERER_URL const child = spawn(electronModule, ['--no-sandbox', '--disable-gpu-sandbox', path.resolve('scripts/test-connectors-page.mjs'), output], { env, windowsHide: true, stdio: 'inherit' }) const timer = setTimeout(() => child.kill(), 120_000) process.exitCode = await new Promise(resolve => child.once('exit', code => { clearTimeout(timer); resolve(code ?? 1) })) } else void (async () => { const { app, BrowserWindow, ipcMain } = electron const output = path.resolve(process.argv.at(-1)) assert.ok(path.relative(base, output).startsWith('connectors-page-') && !path.relative(base, output).includes('..')) electron.app.disableHardwareAcceleration() fs.mkdirSync(path.join(output, 'data'), { recursive: true }) app.setPath('userData', path.join(output, 'data')) await app.whenReady() // The controlled host mirrors the production McpApi contract, including the // approval hash rule and the per-server enable/connect state machine. const calls = [], servers = new Map() let failNextEnable = false const clone = value => JSON.parse(JSON.stringify(value)) const makeServer = (id, name, overrides = {}) => ({ id, name, transport: 'stdio', command: 'node', args: ['server.mjs'], enabled: false, status: 'disconnected', trusted: true, configHash: `hash-${id}`, tools: [], envKeys: [], headerKeys: [], ...overrides }) servers.set('ready', makeServer('ready', 'Ready Server', { enabled: true, status: 'connected', tools: [ { name: 'echo', description: 'Echo text', inputSchema: { type: 'object' }, enabled: true }, { name: 'search', description: 'Find needles', inputSchema: { type: 'object' }, enabled: false } ] })) servers.set('untrusted', makeServer('untrusted', 'Unapproved Server', { trusted: false })) servers.set('failing', makeServer('failing', 'Failing Server', { enabled: true, status: 'error', error: 'MCP 连接失败,请检查命令、地址和运行环境' })) const host = async (method, args) => { calls.push([method, ...args]) if (method === 'list') return clone([...servers.values()]) if (method === 'qaFailNextEnable') { failNextEnable = true; return } const server = servers.get(args[0]) if (!server) throw Error('MCP 服务不存在') if (method === 'save') { const input = args[0] if (!input?.name?.trim()) throw Error('请输入有效的 MCP 名称') if (input.transport !== 'stdio' && input.transport !== 'http') throw Error('不支持的 MCP 连接类型') if (input.transport === 'stdio' && !input.command?.trim()) throw Error('请输入有效的 MCP 启动命令') const previous = input.id ? servers.get(input.id) : undefined if (input.id && !previous) throw Error('MCP 服务不存在') if (input.id && input.id !== previous.id) throw Error('MCP 服务不存在') const next = makeServer(input.id ?? `saved-${servers.size}`, input.name.trim(), { transport: input.transport, command: input.command, args: input.args ?? [], cwd: input.cwd, url: input.url, enabled: previous?.enabled ?? false, trusted: false, tools: previous?.tools ?? [] }) servers.set(next.id, next) return clone(next) } if (method === 'trust') { if (server.configHash !== args[1]) throw Error('MCP 配置已变化,请重新查看并批准') server.trusted = true return } if (method === 'connect') { if (!server.enabled) throw Error('MCP 服务已禁用') if (!server.trusted) throw Error('请先批准当前 MCP 配置') server.status = 'connected'; delete server.error return clone(server) } if (method === 'disconnect') { server.status = 'disconnected'; return } if (method === 'setEnabled') { if (failNextEnable) { failNextEnable = false; throw Error('MCP 服务已禁用') } if (args[1] && !server.trusted) throw Error('请先批准当前 MCP 配置') server.enabled = args[1] server.status = args[1] ? 'connected' : 'disconnected' return } if (method === 'setToolEnabled') { const tool = server.tools.find(item => item.name === args[1]) if (!tool) throw Error('MCP 工具不存在') tool.enabled = args[2] return } if (method === 'remove') { servers.delete(server.id); return } throw Error('Unknown QA method ' + method) } ipcMain.handle('mcp-qa', async (_event, method, args) => host(method, args)) ipcMain.handle('resources-qa', async () => undefined) fs.writeFileSync(path.join(output, 'preload.cjs'), `const{contextBridge,ipcRenderer}=require('electron');contextBridge.exposeInMainWorld('ycode',{mcp:Object.fromEntries(['list','save','trust','connect','disconnect','setEnabled','setToolEnabled','remove','qaFailNextEnable'].map(method=>[method,(...args)=>ipcRenderer.invoke('mcp-qa',method,args)])),resources:{openExternal:async()=>undefined}});`) const location = file => JSON.stringify(path.join(root, file).replaceAll('\\', '/')) fs.writeFileSync(path.join(output, 'fixture.tsx'), ` import React from 'react' import { createRoot } from 'react-dom/client' import Center from ${location('src/renderer/src/capabilities/CapabilityCenter')} import ${location('src/renderer/src/styles/tokens.css')} import ${location('src/renderer/src/styles/app.css')} createRoot(document.getElementById('root')!).render(
{}} />
) `) await require('esbuild').build({ entryPoints: [path.join(output, 'fixture.tsx')], bundle: true, platform: 'browser', format: 'esm', jsx: 'automatic', outfile: path.join(output, 'fixture.js'), loader: { '.svg': 'file', '.png': 'file', '.woff': 'file', '.woff2': 'file', '.ttf': 'file' }, logLevel: 'warning' }) fs.writeFileSync(path.join(output, 'index.html'), '
') const win = new BrowserWindow({ width: 1052, height: 901, useContentSize: true, frame: false, show: false, webPreferences: { preload: path.join(output, 'preload.cjs'), contextIsolation: true, nodeIntegration: false, backgroundThrottling: false } }) const errors = [], checks = [] win.webContents.on('console-message', (_event, level, message) => { if (level === 3 || level === 'error') errors.push(String(message)) }) const read = code => win.webContents.executeJavaScript(code) const pause = ms => new Promise(resolve => setTimeout(resolve, ms)) const check = (name, actual, expected) => { if (typeof expected === 'function') assert.ok(expected(actual), name + ': ' + JSON.stringify(actual)) else assert.deepEqual(actual, expected, name) checks.push(name) } const wait = async code => { for (let index = 0; index < 200; index++) { if (await read(code)) return; await pause(50) } throw Error('Timeout ' + code) } const click = async selector => { const point = await read(`(()=>{const e=document.querySelector(${JSON.stringify(selector)});if(!e)throw Error('Missing '+${JSON.stringify(selector)});const r=e.getBoundingClientRect();return{x:r.x+r.width/2,y:r.y+r.height/2}})()`) win.webContents.sendInputEvent({ type: 'mouseDown', x: point.x, y: point.y, button: 'left', clickCount: 1 }) win.webContents.sendInputEvent({ type: 'mouseUp', x: point.x, y: point.y, button: 'left', clickCount: 1 }) await pause(120) } const clickText = async (selector, label) => { const match = await read(`(()=>{const e=[...document.querySelectorAll(${JSON.stringify(selector)})].find(e=>e.textContent.trim()===${JSON.stringify(label)});if(!e)throw Error('Missing text '+${JSON.stringify(label)});e.dataset.qaTarget='true';return '[data-qa-target="true"]'})()`) await click(match) await read(`document.querySelector(${JSON.stringify(match)})?.removeAttribute('data-qa-target')`) } const capture = async name => { win.webContents.invalidate() // A hidden window keeps the previous frame, so warm the paint first, give // the compositor a frame, and keep the second capture. await win.webContents.capturePage() await pause(220) const image = await win.webContents.capturePage() fs.writeFileSync(path.join(output, name + '.png'), image.toPNG()) return !image.isEmpty() } try { await win.loadFile(path.join(output, 'index.html')) // An inactive window still composites real frames; a hidden one does not. win.showInactive() await wait('!!document.querySelector(".ycode-connectors")') check('the maintenance placeholder is gone', await read('!!document.querySelector("[data-capability-maintenance]")'), false) check('the page renders the real connector list', await read('document.querySelectorAll(".connector-card").length'), 3) check('the connector card names come from the host', await read('[...document.querySelectorAll(".connector-card-name")].map(e=>e.textContent)'), ['Ready Server', 'Unapproved Server', 'Failing Server']) check('the toolbar keeps the shared search and the custom connector action', await read('[!!document.querySelector(".connectors-toolbar .ec-search-wrapper"), !!document.querySelector(".connector-topbar-action-btn")]'), [true, true]) await capture('connector-list') check('the detail overlay opens for a selected connector', await (async () => { await click('.connector-card') await wait('!!document.querySelector(".connector-detail-modal")') return read('document.querySelector(".connector-detail-name").textContent') })(), 'Ready Server') check('the detail overlay lists the real tool count', await read('document.querySelector(".connector-detail-desc").textContent'), '已连接 · 2 个工具') await capture('connector-detail') await click('.connector-detail-close') check('the detail overlay closes', await read('!!document.querySelector(".connector-detail-modal")'), false) await click('.connector-topbar-action-btn') await wait('!!document.querySelector(".mcp-modal")') check('the management modal opens with the real server list', await read('document.querySelectorAll(".mcp-item").length'), 3) check('the panel reports the enabled count from the host', await read('document.querySelector(".mcp-panel-status-summary").textContent'), '2 启用 · 1 失败') check('an unapproved server shows the approval notice', await read('!!document.querySelector(".mcp-item-approval")'), true) check('an unapproved server cannot be enabled by the toggle', await read(`(()=>{const item=[...document.querySelectorAll('.mcp-item')].find(e=>e.textContent.includes('Unapproved Server'));return item.querySelector('.mcp-toggle').disabled})()`), true) await capture('mcp-panel') await clickText('.mcp-item-approval-btn', '批准配置') await wait(`!document.querySelector('.mcp-item-approval')`) check('approving the configuration reaches the host', calls.some(call => call[0] === 'trust' && call[1] === 'untrusted'), true) check('approving also enables the approved server', await read(`(()=>{const item=[...document.querySelectorAll('.mcp-item')].find(e=>e.textContent.includes('Unapproved Server'));return item.querySelector('.mcp-toggle').getAttribute('aria-checked')})()`), 'true') await click('.mcp-item-header') await wait('!!document.querySelector(".mcp-item-tools")') check('the expanded server lists its real tools', await read('[...document.querySelectorAll(".mcp-item-tool")].map(e=>e.textContent)'), ['echo', 'search']) await clickText('.mcp-item-tool', 'search') await wait(`!document.querySelector('.mcp-item-tool')?.classList.contains('mcp-item-tool--disabled')`) check('toggling a tool reaches the host', calls.some(call => call[0] === 'setToolEnabled' && call[1] === 'ready' && call[2] === 'search'), true) check('a failed server shows the real host error', await read('!!document.querySelector(".mcp-item-error")'), true) await click('.mcp-item-error-link') await wait(`!document.querySelector('.mcp-item-error')`) check('the retry action reconnects the failed server', calls.some(call => call[0] === 'connect' && call[1] === 'failing'), true) await clickText('.mcp-item-action', '编辑') await wait('!!document.querySelector(".mcp-config-editor")') check('the config editor opens with the real server document', await read(`(()=>{const ta=document.querySelector('.mcp-config-textarea');const editor=document.querySelector('.monaco-editor');return ta?ta.value:(document.querySelector('.mcp-config-editor-monaco-wrapper')?editor?'monaco':'loading':'')})()`), value => value === 'loading' || value === 'monaco' || JSON.parse(value).mcpServers.ReadyServer) await capture('mcp-editor') await clickText('.mcp-config-editor-header-actions button', '取消') await wait('!document.querySelector(".mcp-config-editor")') await clickText('.mcp-modal-header-right button', '添加 MCP') await wait('!!document.querySelector(".mcp-config-editor")') check('the add editor starts from an empty stdio document', await read(`(()=>{const ta=document.querySelector('.mcp-config-textarea');if(ta)return JSON.parse(ta.value).mcpServers;const w=document.querySelector('.mcp-config-editor-monaco-wrapper');return w?'loading-or-monaco':''})()`), value => value === 'loading-or-monaco' || !!value['my-mcp']) await clickText('.mcp-config-editor-header-actions button', '取消') await wait('!document.querySelector(".mcp-config-editor")') // The card action opens the modal with exactly that server expanded. await click('.mcp-modal-close') await wait('!document.querySelector(".mcp-modal")') await read(`(()=>{const card=[...document.querySelectorAll('.connector-card')].find(e=>e.textContent.includes('Failing Server'));card.querySelector('.connector-connect-btn').click();return true})()`) await wait('!!document.querySelector(".mcp-modal")') await wait(`!![...document.querySelectorAll('.mcp-item')].find(e=>e.textContent.includes('Failing Server'))?.querySelector('.mcp-item-action[aria-label="删除 Failing Server"]')`) await read(`(()=>{const item=[...document.querySelectorAll('.mcp-item')].find(e=>e.textContent.includes('Failing Server'));item.querySelector('.mcp-item-action[aria-label="删除 Failing Server"]').click();return true})()`) await wait('!!document.querySelector(".yc-mcp-confirm")') check('the delete confirmation names the real server', await read('document.querySelector(".yc-mcp-confirm").textContent.includes("Failing Server")'), true) await clickText('.yc-mcp-confirm button', '取消') await wait('!document.querySelector(".yc-mcp-confirm")') check('cancelling the delete keeps the server', await read('document.querySelectorAll(".mcp-item").length'), 3) const setSearch = async value => { await read(`(()=>{const e=document.querySelector('.cb-search-input__input');if(!e)throw Error('missing MCP search input'); const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;setter.call(e,${JSON.stringify(value)}); e.dispatchEvent(new Event('input',{bubbles:true}));return true})()`) await pause(200) } await setSearch('fail') await pause(200) check('the management search filters the real list', await read('document.querySelectorAll(".mcp-item").length'), 1) await setSearch('') await click('.mcp-modal-close') await wait('!document.querySelector(".mcp-modal")') check('closing the management modal returns to the card list', await read('document.querySelectorAll(".connector-card").length'), 3) // A rejected host call must stay visible inside the management modal. await read(`window.ycode.mcp.qaFailNextEnable()`) await read(`(()=>{document.querySelector('.connector-topbar-action-btn').click();return true})()`) await wait('!!document.querySelector(".mcp-modal")') await wait(`!![...document.querySelectorAll('.mcp-item')].find(e=>e.textContent.includes('Ready Server'))`) await read(`(()=>{const item=[...document.querySelectorAll('.mcp-item')].find(e=>e.textContent.includes('Ready Server'));item.querySelector('.mcp-toggle').click();return true})()`) await wait('!!document.querySelector(".mcp-modal [role=alert]")') check('a rejected action reports the real host error inside the modal', await read('document.querySelector(".mcp-modal [role=alert]").textContent'), value => typeof value === 'string' && value.includes('MCP 服务已禁用')) check('the error banner is not only behind the modal overlay', await read(`(()=>{const el=document.querySelector('.mcp-modal [role=alert]');const r=el.getBoundingClientRect();return r.width>0&&r.height>0})()`), true) check('the error banner is painted inside the visible modal body', await read(`(()=>{const el=document.querySelector('.mcp-modal [role=alert]');const r=el.getBoundingClientRect(); const body=document.querySelector('.mcp-modal-body').getBoundingClientRect(); const s=getComputedStyle(el); return {text:el.textContent,top:Math.round(r.top),bottom:Math.round(r.bottom),bodyTop:Math.round(body.top), bodyBottom:Math.round(body.bottom),color:s.color,background:s.backgroundColor,visible:s.visibility,opacity:s.opacity}})()`), value => value.text.includes('MCP 服务已禁用') && value.top >= value.bodyTop && value.bottom <= value.bodyBottom + 1 && value.color !== 'rgba(0, 0, 0, 0)' && value.background !== 'rgba(0, 0, 0, 0)' && value.visible === 'visible' && value.opacity === '1') await capture('mcp-error') check('no renderer errors', errors, []) fs.writeFileSync(path.join(output, 'report.json'), JSON.stringify({ result: 'PASS', checks, calls, limits: 'Controlled MCP host in isolated Electron; the shipped page, helpers, icons and styles are used. Not WorkBuddy pixel acceptance and not a real MCP server connection.' }, null, 2)) console.log(JSON.stringify({ result: 'PASS', checks: checks.length, output })) win.destroy(); app.exit(0) } catch (error) { await capture('failure') fs.writeFileSync(path.join(output, 'failure.json'), JSON.stringify({ error: String(error), checks, errors, calls }, null, 2)) console.error(error) win.destroy(); app.exit(1) } })().catch(error => { console.error(error); electron.app.exit(1) })