// OrbitalWake: controlled MCP stdio startup experiment. // Requires Node.js on Linux and @modelcontextprotocol/sdk exactly 1.32.2. // Creates only synthetic temporary fixtures. No network requests or real secrets. // Starts the official SDK's transport and server, but sends no MCP messages: // no initialize handshake, tools/list, or tools/call. This is not a protocol // conformance test, production-client permission test, or sandbox bypass test. import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; import { setTimeout as delay } from 'node:timers/promises'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; const SDK_VERSION = '1.32.2'; const DEMO_KEY = 'ORBITALWAKE_MCP_DEMO_VALUE'; const DEMO_VALUE = 'synthetic-fixture-not-a-secret'; const INSIDE_TEXT = 'orbitalwake synthetic inside fixture\n'; const OUTSIDE_TEXT = 'orbitalwake synthetic sibling fixture\n'; const SCRIPT = fileURLToPath(import.meta.url); const transportUrl = import.meta.resolve('@modelcontextprotocol/sdk/client/stdio.js'); const sdkPackage = JSON.parse(readFileSync(new URL('../../../package.json', transportUrl), 'utf8')); assert.equal(sdkPackage.version, SDK_VERSION, 'Use the exact reviewed SDK version.'); assert.equal(process.platform, 'linux', 'This reproducer is scoped to Linux.'); function fixturePaths(root, scenario) { return { workspace: join(root, 'workspace'), record: join(root, `${scenario}-startup.json`), calls: join(root, `${scenario}-tool-calls.json`), }; } async function runServer(root, scenario) { const paths = fixturePaths(root, scenario); const server = new McpServer({ name: 'orbitalwake-synthetic-probe', version: '1.0.0' }); server.registerTool('synthetic_ping', { inputSchema: {} }, async () => { writeFileSync(paths.calls, JSON.stringify({ called: true }), { mode: 0o600 }); return { content: [{ type: 'text', text: 'synthetic response' }] }; }); // These ordinary process operations happen before listening for MCP messages. const record = { scenario, phase: 'startup-before-MCP-listener', cwdIsConfiguredWorkspace: process.cwd() === paths.workspace, insideFixtureReadMatches: readFileSync('inside.txt', 'utf8') === INSIDE_TEXT, siblingFixtureReadMatches: readFileSync('../sibling/outside.txt', 'utf8') === OUTSIDE_TEXT, dummyEnvironmentPresent: Object.hasOwn(process.env, DEMO_KEY), dummyEnvironmentMatches: process.env[DEMO_KEY] === DEMO_VALUE, environmentKeys: Object.keys(process.env).sort(), }; writeFileSync(paths.record, JSON.stringify(record), { mode: 0o600 }); await server.connect(new StdioServerTransport()); process.stdin.on('end', () => { void server.close(); }); } async function runCase(root, scenario, supplyDummy) { const paths = fixturePaths(root, scenario); const params = { command: process.execPath, args: [SCRIPT, '--server', root, scenario], cwd: paths.workspace, stderr: 'pipe', ...(supplyDummy ? { env: { [DEMO_KEY]: DEMO_VALUE } } : {}), }; const transport = new StdioClientTransport(params); let sentMessages = 0; let receivedMessages = 0; let stderr = ''; const transportErrors = []; const originalSend = transport.send.bind(transport); transport.send = message => { sentMessages++; return originalSend(message); }; transport.onmessage = () => { receivedMessages++; }; transport.onerror = error => { transportErrors.push(String(error.message)); }; transport.stderr.on('data', chunk => { stderr += chunk.toString(); }); try { await transport.start(); const deadline = Date.now() + 10_000; while (!existsSync(paths.record) && Date.now() < deadline) await delay(20); assert(existsSync(paths.record), 'The synthetic server did not produce a startup record.'); const startup = JSON.parse(readFileSync(paths.record, 'utf8')); await transport.close(); const result = { scenario, startup, sentMessages, receivedMessages, toolHandlerInvoked: existsSync(paths.calls), stderr, transportErrors, }; result.passed = startup.cwdIsConfiguredWorkspace && startup.insideFixtureReadMatches && startup.siblingFixtureReadMatches && startup.dummyEnvironmentPresent === supplyDummy && startup.dummyEnvironmentMatches === supplyDummy && JSON.stringify(startup.environmentKeys) === JSON.stringify(supplyDummy ? [DEMO_KEY, 'PATH'] : ['PATH']) && sentMessages === 0 && receivedMessages === 0 && !result.toolHandlerInvoked && stderr === '' && transportErrors.length === 0; return result; } finally { await transport.close(); } } async function runIsolated(root) { assert.deepEqual(Object.keys(process.env).sort(), [DEMO_KEY, 'PATH']); assert.equal(process.env[DEMO_KEY], DEMO_VALUE); const cases = []; for (const [scenario, supplyDummy] of [['sdk-default-env', false], ['explicit-dummy-env', true]]) { try { cases.push(await runCase(root, scenario, supplyDummy)); } catch (error) { cases.push({ scenario, passed: false, error: String(error.message) }); } } const result = { experiment: 'MCP stdio process startup before any protocol message', testedAtUtc: new Date().toISOString(), sdk: { name: sdkPackage.name, version: sdkPackage.version, clientTransportSha256: createHash('sha256').update(readFileSync(new URL(transportUrl))).digest('hex') }, runtime: { node: process.version, platform: process.platform, architecture: process.arch, uid: process.getuid(), effectiveUid: process.geteuid() }, runnerEnvironmentKeys: Object.keys(process.env).sort(), protocolHandshakePerformed: false, clientUiOrPermissionSystemTested: false, networkAccessTested: false, operatingSystemSandboxTested: false, cases, passed: cases.length === 2 && cases.every(item => item.passed), }; process.stdout.write(JSON.stringify(result, null, 2) + '\n'); process.exitCode = result.passed ? 0 : 1; } async function main() { const [mode, root, scenario] = process.argv.slice(2); if (mode === '--server') return runServer(root, scenario); if (mode === '--runner') return runIsolated(root); assert.equal(mode, undefined, 'Run this script without arguments.'); const fixtureRoot = mkdtempSync(join(tmpdir(), 'orbitalwake-mcp-stdio-')); try { mkdirSync(join(fixtureRoot, 'workspace'), { mode: 0o700 }); mkdirSync(join(fixtureRoot, 'sibling'), { mode: 0o700 }); writeFileSync(join(fixtureRoot, 'workspace', 'inside.txt'), INSIDE_TEXT, { mode: 0o600 }); writeFileSync(join(fixtureRoot, 'sibling', 'outside.txt'), OUTSIDE_TEXT, { mode: 0o600 }); const runner = spawnSync(process.execPath, [SCRIPT, '--runner', fixtureRoot], { // A new child environment, with no HOME, user credentials, or other // inherited variables. This does not modify the invoking shell's env. env: { PATH: dirname(process.execPath), [DEMO_KEY]: DEMO_VALUE }, encoding: 'utf8', timeout: 30_000, maxBuffer: 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'], }); if (runner.stdout) process.stdout.write(runner.stdout); if (runner.stderr) process.stderr.write(runner.stderr); if (runner.error) throw runner.error; if (runner.signal) throw new Error(`Isolated runner ended by signal ${runner.signal}`); assert(runner.stdout.trim(), 'Isolated runner produced no result.'); const reported = JSON.parse(runner.stdout); assert.equal(reported.sdk?.version, SDK_VERSION, 'Unexpected result format or SDK version.'); process.exitCode = runner.status ?? 1; } finally { rmSync(fixtureRoot, { recursive: true, force: true }); } } try { await main(); } catch (error) { process.stderr.write(JSON.stringify({ passed: false, error: String(error.message) }) + '\n'); process.exitCode = 1; }