Skip to main content
Glama
sseToStdio.js5.04 kB
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'); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tianpeijun/email-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server