#!/usr/bin/env node /** * Minimal MCP server for OpenAI chat completions. * Accepts ANY model string (gpt-5.2, gpt-5.4, etc.) — no hardcoded enum. * Communicates over stdio using JSON-RPC (MCP protocol). */ import { createInterface } from "readline"; const OPENAI_API_KEY = process.env.OPENAI_API_KEY; if (!OPENAI_API_KEY) { process.stderr.write("ERROR: OPENAI_API_KEY environment variable is required\n"); process.exit(1); } const SERVER_INFO = { name: "openai-chat", version: "1.0.0", }; const TOOLS = [ { name: "openai_chat", description: "Send messages to OpenAI chat completions API. Supports all OpenAI models including GPT-5.x series.", inputSchema: { type: "object", properties: { model: { type: "string", description: "OpenAI model name (e.g. gpt-5.2, gpt-5.4, gpt-4o, etc.)", default: "gpt-5.2", }, messages: { type: "array", description: "Array of chat messages", items: { type: "object", properties: { role: { type: "string", enum: ["system", "user", "assistant"], }, content: { type: "string" }, }, required: ["role", "content"], }, }, }, required: ["messages"], }, }, ]; async function callOpenAI(model, messages) { const resp = await fetch("https://api.openai.com/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${OPENAI_API_KEY}`, }, body: JSON.stringify({ model, messages }), }); if (!resp.ok) { const errText = await resp.text(); throw new Error(`OpenAI API error ${resp.status}: ${errText}`); } const data = await resp.json(); return data.choices?.[0]?.message?.content ?? "(no response)"; } function jsonRpcResponse(id, result) { return JSON.stringify({ jsonrpc: "2.0", id, result }); } function jsonRpcError(id, code, message) { return JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }); } async function handleRequest(req) { const { id, method, params } = req; switch (method) { case "initialize": return jsonRpcResponse(id, { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: SERVER_INFO, }); case "notifications/initialized": return null; // no response needed for notifications case "tools/list": return jsonRpcResponse(id, { tools: TOOLS }); case "tools/call": { const toolName = params?.name; if (toolName !== "openai_chat") { return jsonRpcError(id, -32602, `Unknown tool: ${toolName}`); } const args = params?.arguments ?? {}; const model = args.model || "gpt-5.2"; const messages = args.messages || []; if (!messages.length) { return jsonRpcError(id, -32602, "messages array is required"); } try { const content = await callOpenAI(model, messages); return jsonRpcResponse(id, { content: [{ type: "text", text: content }], }); } catch (err) { return jsonRpcResponse(id, { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true, }); } } case "ping": return jsonRpcResponse(id, {}); default: if (method?.startsWith("notifications/")) return null; return jsonRpcError(id, -32601, `Method not found: ${method}`); } } // stdio transport const rl = createInterface({ input: process.stdin }); rl.on("line", async (line) => { try { const req = JSON.parse(line); const resp = await handleRequest(req); if (resp) { process.stdout.write(resp + "\n"); } } catch (err) { process.stderr.write(`Parse error: ${err.message}\n`); } });