#!/usr/bin/env node /* eslint-disable */ // Generic stdio MCP smoke runner. // // Usage: // node scripts/smoke-mcp.js [--call toolName] [--args '{"foo":"bar"}'] // // Server names map to dist/mcp/-server.js. Dummy env vars are injected so // the server boots without needing real credentials — we're exercising the // protocol surface, not the upstream API. const { spawn } = require('child_process'); const path = require('path'); const SERVER_DUMMY_ENV = { coolify: { COOLIFY_API_URL: 'https://smoke.example', COOLIFY_API_TOKEN: 'smoke-token' }, gitea: { GITEA_API_URL: 'https://smoke.example', GITEA_API_TOKEN: 'smoke-token', GITEA_USERNAME: 'smoke' }, workspace: { WORKSPACE_ROOT: path.resolve(__dirname, '..'), GITEA_API_URL: 'https://smoke.example', GITEA_API_TOKEN: 'smoke-token', GITEA_USERNAME: 'smoke', }, 'vibn-platform': { SESSION_KEY: 'smoke-session', GITEA_API_URL: 'https://smoke.example', GITEA_API_TOKEN: 'smoke-token', }, agent: { AGENT_RUNNER_URL: 'http://127.0.0.1:65535' }, }; const DEFAULT_CALL = { coolify: { name: 'coolify_list_projects', args: {} }, gitea: { name: 'list_repos', args: {} }, workspace: { name: 'list_directory', args: { path: '.' } }, 'vibn-platform': { name: 'save_memory', args: { key: 'smoke_probe', type: 'note', value: 'hello from smoke test' }, }, agent: { name: 'get_job_status', args: { job_id: 'smoke-nonexistent' } }, }; const args = process.argv.slice(2); const serverName = args[0]; if (!serverName) { console.error('usage: node scripts/smoke-mcp.js [--call name] [--args json]'); process.exit(2); } let callName = DEFAULT_CALL[serverName]?.name; let callArgs = DEFAULT_CALL[serverName]?.args || {}; for (let i = 1; i < args.length; i++) { if (args[i] === '--call') callName = args[++i]; else if (args[i] === '--args') callArgs = JSON.parse(args[++i]); } const serverPath = path.resolve(__dirname, '..', 'dist', 'mcp', `${serverName}-server.js`); const env = { ...process.env, ...(SERVER_DUMMY_ENV[serverName] || {}) }; // Only inject dummies for keys that aren't already set by the real environment. for (const [k, v] of Object.entries(SERVER_DUMMY_ENV[serverName] || {})) { if (!process.env[k]) env[k] = v; } const child = spawn(process.execPath, [serverPath], { stdio: ['pipe', 'pipe', 'inherit'], env }); let buf = ''; const pending = new Map(); let nextId = 1; function send(method, params) { const id = nextId++; return new Promise((resolve, reject) => { pending.set(id, { resolve, reject }); child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n'); }); } function notify(method, params) { child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method, params }) + '\n'); } child.stdout.on('data', (chunk) => { buf += chunk.toString('utf8'); let idx; while ((idx = buf.indexOf('\n')) !== -1) { const line = buf.slice(0, idx).trim(); buf = buf.slice(idx + 1); if (!line) continue; try { const msg = JSON.parse(line); if (msg.id && pending.has(msg.id)) { const { resolve, reject } = pending.get(msg.id); pending.delete(msg.id); if (msg.error) reject(new Error(msg.error.message)); else resolve(msg.result); } } catch { console.error('[smoke] non-JSON line:', line); } } }); child.on('error', (err) => { console.error('[smoke] spawn error:', err); process.exit(2); }); (async () => { try { const init = await send('initialize', { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'vibn-smoke', version: '0.0.0' }, }); console.log('initialize.serverInfo:', JSON.stringify(init.serverInfo)); notify('notifications/initialized', {}); const tools = await send('tools/list', {}); console.log(`\ntools/list returned ${tools.tools.length} tools:`); for (const t of tools.tools) { const required = t.inputSchema?.required ?? []; console.log(` - ${t.name}${required.length ? ` (required: ${required.join(', ')})` : ''}`); } if (callName) { const callResult = await send('tools/call', { name: callName, arguments: callArgs }); console.log(`\ntools/call ${callName}(${JSON.stringify(callArgs)}):`); console.log(JSON.stringify(callResult, null, 2).slice(0, 500)); } console.log('\n[smoke] ✓ MCP stdio round-trip successful'); child.kill(); process.exit(0); } catch (err) { console.error('[smoke] FAILED:', err.message); child.kill(); process.exit(1); } })();