#!/usr/bin/env node /** * index.ts * * Run MCP stdio servers over SSE, convert between stdio, SSE, WS. * * Usage: * # stdio→SSE * npx -y mcp-superassistant-proxy --stdio "npx -y @modelcontextprotocol/server-filesystem /" \ * --port 3006 --baseUrl http://localhost:3006 --ssePath /sse --messagePath /message * * # SSE→stdio * npx -y mcp-superassistant-proxy --sse "https://mcp-server-ab71a6b2-cd55-49d0-adba-562bc85956e3.supermachine.app" * * # SSE→SSE * npx -y mcp-superassistant-proxy --sse "https://input-sse-server.example.com/sse" --outputTransport ssetosse \ * --port 3006 --baseUrl http://localhost:3006 --ssePath /sse --messagePath /message * * # SSE→WS * npx -y mcp-superassistant-proxy --sse "https://input-sse-server.example.com/sse" --outputTransport ws \ * --port 3006 --messagePath /message * * # stdio→WS * npx -y mcp-superassistant-proxy --stdio "npx -y @modelcontextprotocol/server-filesystem /" --outputTransport ws * * # Streamable HTTP→stdio * npx -y mcp-superassistant-proxy --streamableHttp "https://mcp-server.example.com/mcp" * * # Streamable HTTP→SSE * npx -y mcp-superassistant-proxy --streamableHttp "https://mcp-server.example.com/mcp" --outputTransport sse \ * --port 3006 --baseUrl http://localhost:3006 --ssePath /sse --messagePath /message * * # Config→SSE (unified multiple servers) * npx -y mcp-superassistant-proxy --config ./config.json --outputTransport sse --port 3006 * * # Config→WS (unified multiple servers) * npx -y mcp-superassistant-proxy --config ./config.json --outputTransport ws --port 3006 * * # Config→StreamableHttp (unified multiple servers - stateless) * npx -y mcp-superassistant-proxy --config ./config.json --outputTransport streamableHttp --port 3006 * * # Config→StreamableHttp (unified multiple servers - stateful with session timeout) * npx -y mcp-superassistant-proxy --config ./config.json --outputTransport streamableHttp --port 3006 --stateful --sessionTimeout 300000 */ import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { stdioToSse } from './gateways/stdioToSse.js'; import { sseToStdio } from './gateways/sseToStdio.js'; import { sseToSse } from './gateways/sseToSse.js'; import { sseToWs } from './gateways/sseToWs.js'; import { stdioToWs } from './gateways/stdioToWs.js'; import { streamableHttpToStdio } from './gateways/streamableHttpToStdio.js'; import { streamableHttpToSse } from './gateways/streamableHttpToSse.js'; import { configToSse } from './gateways/configToSse.js'; import { configToWs } from './gateways/configToWs.js'; import { configToStreamableHttp } from './gateways/configToStreamableHttp.js'; import { headers } from './lib/headers.js'; import { corsOrigin } from './lib/corsOrigin.js'; import { getLogger } from './lib/getLogger.js'; import { stdioToStatelessStreamableHttp } from './gateways/stdioToStatelessStreamableHttp.js'; import { stdioToStatefulStreamableHttp } from './gateways/stdioToStatefulStreamableHttp.js'; async function main() { const argv = yargs(hideBin(process.argv)) .option('stdio', { type: 'string', description: 'Command to run an MCP server over Stdio', }) .option('sse', { type: 'string', description: 'SSE URL to connect to', }) .option('streamableHttp', { type: 'string', description: 'Streamable HTTP URL to connect to', }) .option('config', { type: 'string', description: 'Path to configuration file with multiple MCP servers', }) .option('outputTransport', { type: 'string', choices: ['stdio', 'sse', 'ssetosse', 'ws', 'streamableHttp'], default: () => { const args = hideBin(process.argv); if (args.includes('--stdio')) return 'sse'; if (args.includes('--sse')) return 'stdio'; if (args.includes('--streamableHttp')) return 'stdio'; if (args.includes('--config')) return 'sse'; return undefined; }, description: 'Transport for output. Default is "sse" when using --stdio or --config and "stdio" when using --sse or --streamableHttp.', }) .option('port', { type: 'number', default: 3006, description: '(stdio→SSE, stdio→WS, stdio→StreamableHttp, SSE→SSE, SSE→WS, config→SSE, config→WS, config→StreamableHttp) Port for output MCP server', }) .option('host', { type: 'string', default: 'localhost', description: '(stdio→SSE, stdio→WS, stdio→StreamableHttp, SSE→SSE, SSE→WS, config→SSE, config→WS, config→StreamableHttp) Host to bind to. Use "0.0.0.0" to bind to all interfaces.', }) .option('baseUrl', { type: 'string', default: '', description: '(stdio→SSE, SSE→SSE, config→SSE) Base URL for output MCP server', }) .option('ssePath', { type: 'string', default: '/sse', description: '(stdio→SSE, SSE→SSE, config→SSE) Path for SSE subscriptions', }) .option('messagePath', { type: 'string', default: '/message', description: '(stdio→SSE, stdio→WS, SSE→SSE, SSE→WS, config→SSE, config→WS) Path for messages', }) .option('streamableHttpPath', { type: 'string', default: '/mcp', description: '(stdio→StreamableHttp, config→StreamableHttp) Path for StreamableHttp', }) .option('streamableHttpPath', { type: 'string', default: '/mcp', description: '(stdio→StreamableHttp, config→StreamableHttp) Path for StreamableHttp', }) .option('logLevel', { choices: ['debug', 'info', 'none'], default: 'info', description: 'Logging level', }) .option('cors', { type: 'array', description: 'Configure CORS origins. CORS is enabled by default allowing all origins (*). Use --cors with no values to explicitly allow all origins, or supply one or more allowed origins (e.g. --cors "http://example.com" or --cors "/example\\.com$/" for regex matching).', }) .option('healthEndpoint', { type: 'array', default: [], description: 'One or more endpoints returning "ok", e.g. --healthEndpoint /healthz --healthEndpoint /readyz', }) .option('header', { type: 'array', default: [], description: 'Headers to be added to the request headers, e.g. --header "x-user-id: 123"', }) .option('oauth2Bearer', { type: 'string', description: 'Authorization header to be added, e.g. --oauth2Bearer "some-access-token" adds "Authorization: Bearer some-access-token"', }) .option('stateful', { type: 'boolean', default: false, description: 'Whether the server is stateful. Only supported for stdio→StreamableHttp and config→StreamableHttp.', }) .option('sessionTimeout', { type: 'number', description: 'Session timeout in milliseconds. Only supported for stateful stdio→StreamableHttp and config→StreamableHttp. If not set, the session will only be deleted when client transport explicitly terminates the session.', }) .help() .parseSync(); const hasStdio = Boolean(argv.stdio); const hasSse = Boolean(argv.sse); const hasStreamableHttp = Boolean(argv.streamableHttp); const hasConfig = Boolean(argv.config); const activeCount = [hasStdio, hasSse, hasStreamableHttp, hasConfig].filter(Boolean).length; const logger = getLogger({ logLevel: argv.logLevel, outputTransport: argv.outputTransport, }); if (activeCount === 0) { logger.error('Error: You must specify one of --stdio, --sse, --streamableHttp, or --config'); process.exit(1); } else if (activeCount > 1) { logger.error('Error: Specify only one of --stdio, --sse, --streamableHttp, or --config, not multiple'); process.exit(1); } logger.info('Starting...'); logger.info('Starting mcp-superassistant-proxy ...'); logger.info(` - outputTransport: ${argv.outputTransport}`); try { if (hasStdio) { if (argv.outputTransport === 'sse') { await stdioToSse({ stdioCmd: argv.stdio, port: argv.port, host: argv.host, baseUrl: argv.baseUrl, ssePath: argv.ssePath, messagePath: argv.messagePath, logger, corsOrigin: corsOrigin({ argv }), healthEndpoints: argv.healthEndpoint, headers: headers({ argv, logger, }), }); } else if (argv.outputTransport === 'ws') { await stdioToWs({ stdioCmd: argv.stdio, port: argv.port, host: argv.host, messagePath: argv.messagePath, logger, corsOrigin: corsOrigin({ argv }), healthEndpoints: argv.healthEndpoint, }); } else if (argv.outputTransport === 'streamableHttp') { const stateful = argv.stateful; if (stateful) { logger.info('Running stateful server'); let sessionTimeout; if (typeof argv.sessionTimeout === 'number') { if (argv.sessionTimeout <= 0) { logger.error(`Error: \`sessionTimeout\` must be a positive number, received: ${argv.sessionTimeout}`); process.exit(1); } sessionTimeout = argv.sessionTimeout; } else { sessionTimeout = null; } await stdioToStatefulStreamableHttp({ stdioCmd: argv.stdio, port: argv.port, host: argv.host, streamableHttpPath: argv.streamableHttpPath, logger, corsOrigin: corsOrigin({ argv }), healthEndpoints: argv.healthEndpoint, headers: headers({ argv, logger, }), sessionTimeout, }); } else { logger.info('Running stateless server'); await stdioToStatelessStreamableHttp({ stdioCmd: argv.stdio, port: argv.port, host: argv.host, streamableHttpPath: argv.streamableHttpPath, logger, corsOrigin: corsOrigin({ argv }), healthEndpoints: argv.healthEndpoint, headers: headers({ argv, logger, }), }); } } else { logger.error(`Error: stdio→${argv.outputTransport} not supported`); process.exit(1); } } else if (hasSse) { if (argv.outputTransport === 'stdio') { await sseToStdio({ sseUrl: argv.sse, logger, headers: headers({ argv, logger, }), }); } else if (argv.outputTransport === 'ssetosse') { await sseToSse({ inputSseUrl: argv.sse, port: argv.port, host: argv.host, baseUrl: argv.baseUrl, ssePath: argv.ssePath, messagePath: argv.messagePath, logger, corsOrigin: corsOrigin({ argv }), healthEndpoints: argv.healthEndpoint, headers: headers({ argv, logger, }), }); } else if (argv.outputTransport === 'ws') { await sseToWs({ inputSseUrl: argv.sse, port: argv.port, host: argv.host, messagePath: argv.messagePath, logger, corsOrigin: corsOrigin({ argv }), healthEndpoints: argv.healthEndpoint, headers: headers({ argv, logger, }), }); } else { logger.error(`Error: sse→${argv.outputTransport} not supported`); process.exit(1); } } else if (hasStreamableHttp) { if (argv.outputTransport === 'stdio') { await streamableHttpToStdio({ streamableHttpUrl: argv.streamableHttp, logger, headers: headers({ argv, logger, }), }); } else if (argv.outputTransport === 'sse') { await streamableHttpToSse({ streamableHttpUrl: argv.streamableHttp, port: argv.port, host: argv.host, baseUrl: argv.baseUrl, ssePath: argv.ssePath, messagePath: argv.messagePath, logger, corsOrigin: corsOrigin({ argv }), healthEndpoints: argv.healthEndpoint, headers: headers({ argv, logger, }), }); } else { logger.error(`Error: streamableHttp→${argv.outputTransport} not supported`); process.exit(1); } } else if (hasConfig) { if (argv.outputTransport === 'sse') { await configToSse({ configPath: argv.config, port: argv.port, host: argv.host, baseUrl: argv.baseUrl, ssePath: argv.ssePath, messagePath: argv.messagePath, logger, corsOrigin: corsOrigin({ argv }), healthEndpoints: argv.healthEndpoint, headers: headers({ argv, logger, }), }); } else if (argv.outputTransport === 'ws') { await configToWs({ configPath: argv.config, port: argv.port, host: argv.host, messagePath: argv.messagePath, logger, corsOrigin: corsOrigin({ argv }), healthEndpoints: argv.healthEndpoint, headers: headers({ argv, logger, }), }); } else if (argv.outputTransport === 'streamableHttp') { const stateless = !argv.stateful; let sessionTimeout = null; if (argv.stateful && typeof argv.sessionTimeout === 'number') { if (argv.sessionTimeout <= 0) { logger.error(`Error: \`sessionTimeout\` must be a positive number, received: ${argv.sessionTimeout}`); process.exit(1); } sessionTimeout = argv.sessionTimeout; } await configToStreamableHttp({ configPath: argv.config, port: argv.port, host: argv.host, streamableHttpPath: argv.streamableHttpPath, logger, corsOrigin: corsOrigin({ argv }), healthEndpoints: argv.healthEndpoint, headers: headers({ argv, logger, }), stateless, sessionTimeout, }); } else { logger.error(`Error: config→${argv.outputTransport} not supported`); process.exit(1); } } else { logger.error('Error: Invalid input transport'); process.exit(1); } } catch (err) { logger.error('Fatal error:', err); process.exit(1); } } main(); //# sourceMappingURL=index.js.map