import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { getVersion } from '../lib/getVersion.js';
import { onSignals } from '../lib/onSignals.js';
let sseClient;
const newInitializeSseClient = ({ message }) => {
const clientInfo = message.params?.clientInfo;
const clientCapabilities = message.params?.capabilities;
return new Client({
name: clientInfo?.name ?? 'supergateway',
version: clientInfo?.version ?? getVersion(),
}, {
capabilities: clientCapabilities ?? {},
});
};
const newFallbackSseClient = async ({ sseTransport, }) => {
const fallbackSseClient = new Client({
name: 'supergateway',
version: getVersion(),
}, {
capabilities: {},
});
await fallbackSseClient.connect(sseTransport);
return fallbackSseClient;
};
export async function sseToStdio(args) {
const { sseUrl, logger, headers } = args;
logger.info(` - sse: ${sseUrl}`);
logger.info(` - Headers: ${Object.keys(headers).length ? JSON.stringify(headers) : '(none)'}`);
logger.info('Connecting to SSE...');
onSignals({ logger });
const sseTransport = new SSEClientTransport(new URL(sseUrl), {
eventSourceInit: {
fetch: (...props) => {
const [url, init = {}] = props;
return fetch(url, { ...init, headers: { ...init.headers, ...headers } });
},
},
requestInit: {
headers,
},
});
sseTransport.onerror = (err) => {
logger.error('SSE error:', err);
};
sseTransport.onclose = () => {
logger.error('SSE connection closed');
process.exit(1);
};
const stdioServer = new Server({
name: 'supergateway',
version: getVersion(),
}, {
capabilities: {},
});
const stdioTransport = new StdioServerTransport();
await stdioServer.connect(stdioTransport);
const wrapResponse = (req, payload) => ({
jsonrpc: req.jsonrpc || '2.0',
id: req.id,
...payload,
});
stdioServer.transport.onmessage = async (message) => {
const isRequest = 'method' in message && 'id' in message;
if (isRequest) {
logger.info('Stdio → SSE:', message);
const req = message;
let result;
try {
if (!sseClient) {
if (message.method === 'initialize') {
sseClient = newInitializeSseClient({
message,
});
const originalRequest = sseClient.request;
sseClient.request = async function (...args) {
result = await originalRequest.apply(this, args);
return result;
};
await sseClient.connect(sseTransport);
sseClient.request = originalRequest;
}
else {
logger.info('SSE client not initialized, creating fallback client');
sseClient = await newFallbackSseClient({ sseTransport });
}
logger.info('SSE connected');
}
else {
result = await sseClient.request(req, z.any());
}
}
catch (err) {
logger.error('Request error:', err);
const errorCode = err && typeof err === 'object' && 'code' in err
? err.code
: -32000;
let errorMsg = err && typeof err === 'object' && 'message' in err
? err.message
: 'Internal error';
const prefix = `MCP error ${errorCode}:`;
if (errorMsg.startsWith(prefix)) {
errorMsg = errorMsg.slice(prefix.length).trim();
}
const errorResp = wrapResponse(req, {
error: {
code: errorCode,
message: errorMsg,
},
});
process.stdout.write(JSON.stringify(errorResp) + '\n');
return;
}
const response = wrapResponse(req, result.hasOwnProperty('error')
? { error: { ...result.error } }
: { result: { ...result } });
logger.info('Response:', response);
process.stdout.write(JSON.stringify(response) + '\n');
}
else {
logger.info('SSE → Stdio:', message);
process.stdout.write(JSON.stringify(message) + '\n');
}
};
logger.info('Stdio server listening');
}