117 lines
4.1 KiB
TypeScript
117 lines
4.1 KiB
TypeScript
/**
|
|
* Usage Examples & Integration Tests
|
|
*
|
|
* This test suite demonstrates how a downstream client (like an Electron app
|
|
* or Agent OS manager) configuration translates to qiming-mcp-stdio-proxy execution.
|
|
* It is adapted from real usage patterns in agent-electron-client.
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { spawn } from 'child_process';
|
|
import * as path from 'path';
|
|
|
|
const PROXY_SCRIPT = path.resolve('dist/index.js');
|
|
|
|
/**
|
|
* Helper to test CLI arg parsing and proxy behavior
|
|
* without actually completing a full connection (we just check its stderr rejection to see if it understood args).
|
|
*/
|
|
function spawnProxy(
|
|
args: string[],
|
|
options?: { env?: Record<string, string>; timeoutMs?: number },
|
|
): Promise<{ code: number | null; stderr: string; stdout: string }> {
|
|
return new Promise((resolve) => {
|
|
const proc = spawn('node', [PROXY_SCRIPT, ...args], {
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
env: { ...process.env, ...options?.env }, // inherit to keep vitest happy
|
|
});
|
|
|
|
let stderr = '';
|
|
let stdout = '';
|
|
|
|
proc.stderr?.on('data', (d) => (stderr += d.toString()));
|
|
proc.stdout?.on('data', (d) => (stdout += d.toString()));
|
|
|
|
proc.on('exit', (code) => {
|
|
resolve({ code, stderr, stdout });
|
|
});
|
|
|
|
setTimeout(() => proc.kill(), options?.timeoutMs ?? 15000);
|
|
});
|
|
}
|
|
|
|
describe('Proxy Usage Examples (from agent-electron-client)', () => {
|
|
it('Example 1: Downstream client creates temporary stdio proxy configuration', async () => {
|
|
// In agent-electron-client, mcpProxyManager builds a config for the proxy
|
|
// to aggregate multiple temporary MCP servers.
|
|
const config = {
|
|
mcpServers: {
|
|
'test-mcp': {
|
|
command: 'node',
|
|
args: ['-e', 'console.log("dummy-server")'],
|
|
connectionTimeoutMs: 5000,
|
|
},
|
|
},
|
|
};
|
|
|
|
const configString = JSON.stringify(config);
|
|
|
|
// The proxy is then invoked with this config via standard stdio
|
|
const { code, stderr } = await spawnProxy(['--config', configString]);
|
|
|
|
// We expect the proxy to start and immediately fail because our dummy server isn't a REAL MCP server
|
|
// (it just prints 'dummy-server' and exits), but we can verify the proxy accepted the config
|
|
// and attempted to spawn "test-mcp".
|
|
expect(code).toBe(1); // Exits 1 because child server dies immediately
|
|
expect(stderr).toContain('test-mcp');
|
|
expect(stderr).toContain('Failed to connect to any MCP server');
|
|
});
|
|
|
|
it('Example 2: Downstream client routes to a persistent bridge', async () => {
|
|
// When the PersistentMcpBridge is running in agent-electron-client,
|
|
// it maps standard subprocess configs to HTTP URLs.
|
|
const proxyConfig = {
|
|
mcpServers: {
|
|
'chrome-devtools': {
|
|
url: 'http://127.0.0.1:12345/mcp/chrome-devtools',
|
|
connectionTimeoutMs: 5000,
|
|
},
|
|
},
|
|
};
|
|
|
|
const configString = JSON.stringify(proxyConfig);
|
|
|
|
// The downstream client spawns the proxy expecting it to convert that HTTP URL into stdio
|
|
const { code, stderr } = await spawnProxy(['--config', configString]);
|
|
|
|
// It should fail to connect since port 12345 has no real server.
|
|
// Protocol detection defaults to SSE, then SSE connection fails with ECONNREFUSED.
|
|
expect(stderr).toContain('chrome-devtools');
|
|
expect(stderr).toContain('Failed to connect');
|
|
}, 20000);
|
|
|
|
it('Example 3: Downstream client mixed temporary and bridged configs', async () => {
|
|
// agent-electron-client frequently mixes "temporary" (stdio) tools
|
|
// with "persistent" (bridged) tools in the same proxy call.
|
|
const mixedConfig = {
|
|
mcpServers: {
|
|
'local-tool': {
|
|
command: 'node',
|
|
args: ['-e', 'process.exit(1)'],
|
|
connectionTimeoutMs: 5000,
|
|
},
|
|
'remote-bridge': {
|
|
url: 'http://127.0.0.1:12346/mcp/remote-bridge',
|
|
connectionTimeoutMs: 5000,
|
|
}
|
|
}
|
|
};
|
|
|
|
const { code, stderr } = await spawnProxy(['--config', JSON.stringify(mixedConfig)]);
|
|
|
|
// Proxy should try to aggregate both
|
|
expect(stderr).toContain('local-tool');
|
|
expect(stderr).toContain('Failed to connect');
|
|
}, 20000);
|
|
});
|