const URL = process.env.MCP_996_GUI_URL; const AUTH = process.env.MCP_996_GUI_AUTH; const fs = require("fs"); const LOG = process.env.MCP_996_GUI_LOG || "C:\\Users\\Administrator\\.codex\\mcp_996_gui_proxy.log"; function log(message) { try { fs.appendFileSync(LOG, `${new Date().toISOString()} ${message}\n`, "utf8"); } catch { // Logging must never break the MCP server. } } log(`start pid=${process.pid}`); const tools = [ { name: "list_rule_files", description: "List all client GUI API rule files.", inputSchema: { type: "object", properties: {}, additionalProperties: false }, }, { name: "list_versions", description: "List available client GUI API versions.", inputSchema: { type: "object", properties: {}, additionalProperties: false }, }, { name: "list_apis", description: "Search client GUI APIs by keyword.", inputSchema: { type: "object", properties: { keyword: { type: "string", default: "" }, limit: { type: "integer", default: 50 }, version: { type: "string", default: "" }, }, additionalProperties: false, }, }, { name: "generate_lua", description: "Generate client Lua code from the original natural language query.", inputSchema: { type: "object", properties: { query: { type: "string" }, top_k: { type: "integer", default: 3 }, version: { type: "string", default: "" }, }, required: ["query"], additionalProperties: false, }, }, { name: "generate_lua_996", description: "Alias of generate_lua for 996 API client GUI code generation.", inputSchema: { type: "object", properties: { query: { type: "string" }, top_k: { type: "integer", default: 3 }, version: { type: "string", default: "" }, }, required: ["query"], additionalProperties: false, }, }, ]; let input = Buffer.alloc(0); let responseMode = "lsp"; process.stdin.on("data", (chunk) => { log(`stdin bytes=${chunk.length}`); log(`stdin utf8=${JSON.stringify(chunk.toString("utf8"))}`); log(`stdin hex=${chunk.toString("hex")}`); input = Buffer.concat([input, chunk]); readMessages(); }); process.stdin.on("end", () => log("stdin end")); process.on("uncaughtException", (error) => log(`uncaught ${error.stack || error.message || error}`)); function readMessages() { while (true) { let firstNonWhitespace = 0; while ( firstNonWhitespace < input.length && [9, 10, 13, 32].includes(input[firstNonWhitespace]) ) { firstNonWhitespace += 1; } if (firstNonWhitespace > 0) input = input.slice(firstNonWhitespace); if (input.length === 0) return; if (input[0] === 123) { responseMode = "ndjson"; const lineEnd = input.indexOf("\n"); if (lineEnd < 0) return; const body = input.slice(0, lineEnd).toString("utf8").trim(); input = input.slice(lineEnd + 1); if (!body) continue; log(`message ${body}`); handle(JSON.parse(body)).catch((error) => { respond(JSON.parse(body).id, null, { code: -32603, message: error && error.message ? error.message : String(error), }); }); continue; } let headerEnd = input.indexOf("\r\n\r\n"); let separatorLength = 4; if (headerEnd < 0) { headerEnd = input.indexOf("\n\n"); separatorLength = 2; } if (headerEnd < 0) return; const header = input.slice(0, headerEnd).toString("ascii"); const match = /Content-Length:\s*(\d+)/i.exec(header); if (!match) { input = input.slice(headerEnd + separatorLength); continue; } const length = Number(match[1]); const start = headerEnd + separatorLength; const end = start + length; if (input.length < end) return; const body = input.slice(start, end).toString("utf8"); input = input.slice(end); log(`message ${body}`); handle(JSON.parse(body)).catch((error) => { respond(JSON.parse(body).id, null, { code: -32603, message: error && error.message ? error.message : String(error), }); }); } } async function handle(message) { if (message.id === undefined) return; log(`handle ${message.method}`); if (message.method === "initialize") { respond(message.id, { protocolVersion: message.params?.protocolVersion || "2025-03-26", capabilities: { tools: { listChanged: false } }, serverInfo: { name: "996 Client GUI Proxy", version: "1.0.0" }, }); return; } if (message.method === "tools/list") { respond(message.id, { tools }); return; } if (message.method === "resources/list") { respond(message.id, { resources: [] }); return; } if (message.method === "resources/templates/list") { respond(message.id, { resourceTemplates: [] }); return; } if (message.method === "prompts/list") { respond(message.id, { prompts: [] }); return; } if (message.method === "tools/call") { const name = message.params?.name; const args = message.params?.arguments || {}; const result = await callRemote(name, args); respond(message.id, { content: [{ type: "text", text: stringifyResult(result) }] }); return; } respond(message.id, null, { code: -32601, message: `Unknown method: ${message.method}` }); } async function callRemote(name, args) { if (!URL || !AUTH) throw new Error("MCP_996_GUI_URL or MCP_996_GUI_AUTH is missing"); const init = await post(null, { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "codex-996-gui-proxy", version: "1.0.0" }, }, }); const session = init.session; await post(session, { jsonrpc: "2.0", method: "notifications/initialized", params: {} }); const response = await post(session, { jsonrpc: "2.0", id: 2, method: "tools/call", params: { name, arguments: args }, }); return response.data?.result ?? response.data; } async function post(session, payload) { const headers = { Authorization: AUTH, Accept: "application/json, text/event-stream", "Content-Type": "application/json", }; if (session) headers["mcp-session-id"] = session; const response = await fetch(URL, { method: "POST", headers, body: JSON.stringify(payload), }); const text = await response.text(); if (!response.ok) throw new Error(`Remote MCP HTTP ${response.status}: ${text}`); return { session: response.headers.get("mcp-session-id"), data: parseMcpBody(text), }; } function parseMcpBody(text) { if (!text || !text.trim()) return null; const dataLine = text .split(/\r?\n/) .find((line) => line.startsWith("data:")); return JSON.parse(dataLine ? dataLine.slice(5).trim() : text); } function stringifyResult(result) { if (typeof result === "string") return result; return JSON.stringify(result, null, 2); } function respond(id, result, error) { const payload = error ? { jsonrpc: "2.0", id, error } : { jsonrpc: "2.0", id, result }; const body = JSON.stringify(payload); log(`respond id=${id} bytes=${Buffer.byteLength(body, "utf8")} error=${Boolean(error)}`); if (responseMode === "ndjson") { process.stdout.write(`${body}\n`); } else { process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`); } }