Skip to main content
Glama
index.ts6.57 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { ListToolsRequestSchema, CallToolRequestSchema, TextContent, } from "@modelcontextprotocol/sdk/types.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { spawnSync } from "child_process"; import { readFileSync } from "fs"; import { homedir } from "os"; import { join } from "path"; interface OracleConfig { model?: string; models?: string[]; reasoning?: string; command?: string; } interface OracleMcpConfig { oracle?: OracleConfig; oracles?: OracleConfig[]; } const DEFAULT_ORACLE_CONFIG: OracleConfig = { models: ["gpt-5.1-codex-mini"], reasoning: "medium", command: "codex", }; /** * Normalizes an OracleConfig to always have a models array. * Supports backward compatibility with single model field. */ function normalizeModels(config: OracleConfig): string[] { if (config.models && config.models.length > 0) { return config.models; } if (config.model) { return [config.model]; } return []; } function loadConfig(): OracleMcpConfig { const configPath = join(homedir(), ".oracle-mcp.json"); try { const content = readFileSync(configPath, "utf-8"); return JSON.parse(content) as OracleMcpConfig; } catch { return {}; } } /** * Constructs command-line arguments for invoking the oracle CLI. * Returns arguments as an array to avoid shell escaping issues. */ export function buildOracleArgs( command: string, model: string, reasoning: string | undefined, prompt: string ): string[] { if (command === "codex" || command.includes("codex")) { // Codex: codex exec --model <model> [-c reasoning_level=<level>] "<prompt>" const args = ["exec", "--model", model]; if (reasoning) { args.push("-c", `reasoning_level=${reasoning}`); } args.push(prompt); return args; } else if (command === "gemini" || command.includes("gemini")) { // Gemini: gemini -p --model <model> "<prompt>" return ["-p", "--model", model, prompt]; } else { // Claude: claude -p --model <model> "<prompt>" return ["-p", "--model", model, prompt]; } } /** * Invokes the oracle CLI and returns the response. * Extracted for testability. */ export function invokeOracle( command: string, args: string[] ): { stdout: string; status: number; error?: Error } { const result = spawnSync(command, args, { encoding: "utf-8" }); return { stdout: result.stdout, status: result.status ?? 1, error: result.error, }; } const config = loadConfig(); // Support both single oracle and multiple oracles (with fallback) let oracleConfigs: OracleConfig[]; if (config.oracles && config.oracles.length > 0) { oracleConfigs = config.oracles; } else if (config.oracle) { oracleConfigs = [config.oracle]; } else { oracleConfigs = [DEFAULT_ORACLE_CONFIG]; } // Primary oracle for descriptions const oracleConfig: OracleConfig = oracleConfigs[0]; const server = new Server({ name: "oracle-mcp", version: "0.1.0", }); server.registerCapabilities({ tools: {}, }); server.setRequestHandler(ListToolsRequestSchema, async () => { const models = normalizeModels(oracleConfig); const modelStr = models.length === 1 ? models[0] : `${models.length} models`; const baseDescription = `Consult the oracle (${modelStr}) via ${oracleConfig.command || "oracle"} CLI`; const reasoningPart = oracleConfig.reasoning ? ` with ${oracleConfig.reasoning}-level reasoning` : ""; const capabilityPart = `. The oracle provides expert reasoning and analysis for complex problem-solving.`; const usagePart = " Use when: (1) planning complex tasks with multiple tradeoffs, (2) you are <=90% confident in your approach, (3) you need analysis of architectural decisions or design patterns."; const fullDescription = baseDescription + reasoningPart + capabilityPart + usagePart; return { tools: [ { name: "consult_oracle", description: fullDescription, inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The question or problem to consult the oracle about. Be specific about context, constraints, and what decision or analysis you need.", }, }, required: ["prompt"], }, }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const name = request.params.name; const args = request.params.arguments || {}; if (name === "consult_oracle") { const prompt = (args as Record<string, unknown>).prompt as string; const errors: string[] = []; // Try each oracle in sequence, and within each oracle try each model (fallback) for (let oracleIdx = 0; oracleIdx < oracleConfigs.length; oracleIdx++) { const cfg = oracleConfigs[oracleIdx]; const models = normalizeModels(cfg); for (let modelIdx = 0; modelIdx < models.length; modelIdx++) { const model = models[modelIdx]; try { const cmdName = cfg.command || "oracle"; const spawnArgs = buildOracleArgs( cmdName, model, cfg.reasoning, prompt ); const result = invokeOracle(cmdName, spawnArgs); if (result.error) { throw result.error; } if (result.status !== 0) { throw new Error(`Command failed with status ${result.status}`); } return { content: [ { type: "text", text: result.stdout, } as TextContent, ], }; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); errors.push( `Oracle ${oracleIdx + 1}, model ${modelIdx + 1} (${model}): ${errorMsg}` ); } } } // All oracles and models failed, return accumulated errors return { content: [ { type: "text", text: `All oracles failed:\n${errors.join("\n")}`, } as TextContent, ], isError: true, }; } return { content: [ { type: "text", text: `Unknown tool: ${name}`, } as TextContent, ], isError: true, }; }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Oracle MCP server running on stdio"); } main().catch(console.error);

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/becksclair/oracle-mcp'

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