Skip to main content
Glama

MCP Specification Server

by MCPJam
23
6
  • Apple
mcp-proxy.js•8.81 kB
#!/usr/bin/env node import { InMemoryEventStore, proxyServer, startHTTPServer } from "../chunk-MXVPEZER.js"; // src/bin/mcp-proxy.ts import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { EventSource } from "eventsource"; import { setTimeout } from "timers"; import util from "util"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; // src/StdioClientTransport.ts import { ReadBuffer, serializeMessage } from "@modelcontextprotocol/sdk/shared/stdio.js"; import { spawn } from "child_process"; // src/JSONFilterTransform.ts import { Transform } from "stream"; var JSONFilterTransform = class extends Transform { buffer = ""; constructor() { super({ objectMode: false }); } _flush(callback) { if (this.buffer.trim().startsWith("{")) { callback(null, Buffer.from(this.buffer)); } else { callback(null, null); } } _transform(chunk, _encoding, callback) { this.buffer += chunk.toString(); const lines = this.buffer.split("\n"); this.buffer = lines.pop() || ""; const jsonLines = []; const nonJsonLines = []; for (const line of lines) { if (line.trim().startsWith("{")) { jsonLines.push(line); } else { nonJsonLines.push(line); } } if (nonJsonLines.length > 0) { console.warn("[mcp-proxy] ignoring non-JSON output", nonJsonLines); } if (jsonLines.length > 0) { const output = jsonLines.join("\n") + "\n"; callback(null, Buffer.from(output)); } else { callback(null, null); } } }; // src/StdioClientTransport.ts var StdioClientTransport = class { onclose; onerror; onmessage; /** * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped". * * This is only available after the process has been started. */ get stderr() { return this.process?.stderr ?? null; } abortController = new AbortController(); onEvent; process; readBuffer = new ReadBuffer(); serverParams; constructor(server) { this.serverParams = server; this.onEvent = server.onEvent; } async close() { this.onEvent?.({ type: "close" }); this.abortController.abort(); this.process = void 0; this.readBuffer.clear(); } send(message) { return new Promise((resolve) => { if (!this.process?.stdin) { throw new Error("Not connected"); } const json = serializeMessage(message); if (this.process.stdin.write(json)) { resolve(); } else { this.process.stdin.once("drain", resolve); } }); } /** * Starts the server process and prepares to communicate with it. */ async start() { if (this.process) { throw new Error( "StdioClientTransport already started! If using Client class, note that connect() calls start() automatically." ); } return new Promise((resolve, reject) => { this.process = spawn( this.serverParams.command, this.serverParams.args ?? [], { cwd: this.serverParams.cwd, env: this.serverParams.env, shell: this.serverParams.shell ?? false, signal: this.abortController.signal, stdio: ["pipe", "pipe", this.serverParams.stderr ?? "inherit"] } ); this.process.on("error", (error) => { if (error.name === "AbortError") { this.onclose?.(); return; } reject(error); this.onerror?.(error); }); this.process.on("spawn", () => { resolve(); }); this.process.on("close", (_code) => { this.onEvent?.({ type: "close" }); this.process = void 0; this.onclose?.(); }); this.process.stdin?.on("error", (error) => { this.onEvent?.({ error, type: "error" }); this.onerror?.(error); }); const jsonFilterTransform = new JSONFilterTransform(); this.process.stdout?.pipe(jsonFilterTransform); jsonFilterTransform.on("data", (chunk) => { this.onEvent?.({ chunk: chunk.toString(), type: "data" }); this.readBuffer.append(chunk); this.processReadBuffer(); }); jsonFilterTransform.on("error", (error) => { this.onEvent?.({ error, type: "error" }); this.onerror?.(error); }); }); } processReadBuffer() { while (true) { try { const message = this.readBuffer.readMessage(); if (message === null) { break; } this.onEvent?.({ message, type: "message" }); this.onmessage?.(message); } catch (error) { this.onEvent?.({ error, type: "error" }); this.onerror?.(error); } } } }; // src/bin/mcp-proxy.ts util.inspect.defaultOptions.depth = 8; if (!("EventSource" in global)) { global.EventSource = EventSource; } var argv = await yargs(hideBin(process.argv)).scriptName("mcp-proxy").command("$0 <command> [args...]", "Run a command with MCP arguments").positional("command", { demandOption: true, describe: "The command to run", type: "string" }).positional("args", { array: true, describe: "The arguments to pass to the command", type: "string" }).env("MCP_PROXY").parserConfiguration({ "populate--": true }).options({ debug: { default: false, describe: "Enable debug logging", type: "boolean" }, endpoint: { describe: "The endpoint to listen on", type: "string" }, gracefulShutdownTimeout: { default: 5e3, describe: "The timeout (in milliseconds) for graceful shutdown", type: "number" }, host: { default: "::", describe: "The host to listen on", type: "string" }, port: { default: 8080, describe: "The port to listen on", type: "number" }, server: { choices: ["sse", "stream"], describe: "The server type to use (sse or stream). By default, both are enabled", type: "string" }, shell: { default: false, describe: "Spawn the server via the user's shell", type: "boolean" }, sseEndpoint: { default: "/sse", describe: "The SSE endpoint to listen on", type: "string" }, streamEndpoint: { default: "/mcp", describe: "The stream endpoint to listen on", type: "string" } }).help().parseAsync(); if (!argv.command) { throw new Error("No command specified"); } var finalCommand = argv.command; var finalArgs = argv["--"] || argv.args; var connect = async (client) => { const transport = new StdioClientTransport({ args: finalArgs, command: finalCommand, env: process.env, onEvent: (event) => { if (argv.debug) { console.debug("transport event", event); } }, shell: argv.shell, stderr: "pipe" }); await client.connect(transport); }; var proxy = async () => { const client = new Client( { name: "mcp-proxy", version: "1.0.0" }, { capabilities: {} } ); await connect(client); const serverVersion = client.getServerVersion(); const serverCapabilities = client.getServerCapabilities(); console.info("starting server on port %d", argv.port); const createServer = async () => { const server2 = new Server(serverVersion, { capabilities: serverCapabilities }); proxyServer({ client, server: server2, serverCapabilities }); return server2; }; const server = await startHTTPServer({ createServer, eventStore: new InMemoryEventStore(), host: argv.host, port: argv.port, sseEndpoint: argv.server && argv.server !== "sse" ? null : argv.sseEndpoint ?? argv.endpoint, streamEndpoint: argv.server && argv.server !== "stream" ? null : argv.streamEndpoint ?? argv.endpoint }); return { close: () => { return server.close(); } }; }; var createGracefulShutdown = ({ server, timeout }) => { const gracefulShutdown = () => { console.info("received shutdown signal; shutting down"); server.close(); setTimeout(() => { process.exit(1); }, timeout).unref(); }; process.on("SIGTERM", gracefulShutdown); process.on("SIGINT", gracefulShutdown); return () => { server.close(); }; }; var main = async () => { try { const server = await proxy(); createGracefulShutdown({ server, timeout: argv.gracefulShutdownTimeout }); } catch (error) { console.error("could not start the proxy", error); setTimeout(() => { process.exit(1); }, 1e3); } }; await main(); //# sourceMappingURL=mcp-proxy.js.map

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/MCPJam/mcp-spec'

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