Skip to main content
Glama
Atomic-Germ

MCP Ollama Consult Server

handlers.ts11.2 kB
import axios from "axios"; import fs from "fs/promises"; import path from "path"; const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL || "http://localhost:11434"; // Ensure a reasonable default timeout for Ollama calls axios.defaults.timeout = axios.defaults.timeout || 60_000; // Cloud models that are always available as fallbacks const CLOUD_MODELS = [ "deepseek-v3.1:671b-cloud", "kimi-k2-thinking:cloud", "claude-3-5-sonnet:cloud", ]; // Local fallback models (in preference order) const LOCAL_FALLBACKS = ["mistral", "llama2", "neural-chat"]; // Cache for available models (to avoid repeated API calls) let availableModelsCache: string[] | null = null; let cacheTimestamp = 0; const CACHE_TTL = 30000; // 30 seconds /** * Get list of available local models from Ollama */ async function getAvailableLocalModels(): Promise<string[]> { const now = Date.now(); if (availableModelsCache && now - cacheTimestamp < CACHE_TTL) { return availableModelsCache; } try { const response = await axios.get(`${OLLAMA_BASE_URL}/api/tags`); const models = (response.data.models || []).map((m: any) => m.name); availableModelsCache = models; cacheTimestamp = now; return models; } catch { // If we can't reach Ollama, return empty (we'll fall back to cloud) return []; } } /** * Resolve a model name to an available model * If the requested model isn't available, falls back to cloud or local alternatives */ async function resolveModel(requestedModel: string): Promise<{ model: string; isCloud: boolean; fallback: boolean; }> { // If it's explicitly a cloud model, use it if (requestedModel.includes(":cloud")) { return { model: requestedModel, isCloud: true, fallback: false }; } // Check if it's available locally const available = await getAvailableLocalModels(); if (available.includes(requestedModel)) { return { model: requestedModel, isCloud: false, fallback: false }; } // Not available locally - try cloud models for (const cloudModel of CLOUD_MODELS) { // In real implementation, you might verify cloud models are actually available // For now, we assume they are return { model: cloudModel, isCloud: true, fallback: true }; } // If cloud fails, fall back to local models for (const localFallback of LOCAL_FALLBACKS) { if (available.includes(localFallback)) { return { model: localFallback, isCloud: false, fallback: true }; } } // Last resort: use whatever is available, or the original request if (available.length > 0) { return { model: available[0], isCloud: false, fallback: true }; } // If nothing is available, return the requested model anyway // (will fail gracefully in the actual API call) return { model: requestedModel, isCloud: false, fallback: true }; } export function listTools() { return { tools: [ { name: "consult_ollama", description: "Consult an Ollama model with a prompt and get its response for reasoning from another viewpoint. If the requested model is unavailable locally, automatically falls back to: cloud models (deepseek-v3.1:671b-cloud, kimi-k2-thinking:cloud) or local alternatives (mistral, llama2). Never fails on model availability.", inputSchema: { type: "object", properties: { model: { type: "string" }, prompt: { type: "string" }, system_prompt: { type: "string" }, }, required: ["model", "prompt"], }, }, { name: "list_ollama_models", description: "List all available Ollama models on the local instance.", inputSchema: { type: "object", properties: {} }, }, { name: "compare_ollama_models", description: "Run the same prompt against multiple Ollama models and return their outputs side-by-side for comparison. Requested models that are unavailable automatically fall back to cloud models or local alternatives. Handles unavailable models gracefully without breaking the comparison.", inputSchema: { type: "object", properties: { models: { type: "array", items: { type: "string" } }, prompt: { type: "string" }, system_prompt: { type: "string" }, }, required: ["prompt"], }, }, { name: "remember_consult", description: "Store the result of a consult into a local memory store (or configured memory service).", inputSchema: { type: "object", properties: { key: { type: "string" }, prompt: { type: "string" }, model: { type: "string" }, response: { type: "string" }, }, required: ["prompt"], }, }, ], }; } // The handler expects the `params` object (i.e. the CallTool request params). export async function callToolHandler(params: { name: string; arguments?: any }): Promise<any> { const name = params.name; const args = params.arguments || {}; switch (name) { case "consult_ollama": { let model = args?.model as string; const prompt = args?.prompt as string; const system_prompt = args?.system_prompt as string | undefined; // Resolve model with fallback const resolved = await resolveModel(model); if (resolved.fallback) { console.log( `[mcp-consult] Model '${model}' unavailable. Falling back to '${resolved.model}'` ); } model = resolved.model; try { const response = await axios.post(`${OLLAMA_BASE_URL}/api/generate`, { model, prompt, system: system_prompt, stream: false, }); return { content: [ { type: "text", text: response.data.response, }, ], }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; return { content: [ { type: "text", text: `Error consulting Ollama: ${message}`, }, ], isError: true, }; } } case "list_ollama_models": { try { const response = await axios.get(`${OLLAMA_BASE_URL}/api/tags`); const models = (response.data.models || []).map((m: any) => m.name).join(", "); return { content: [ { type: "text", text: `Available models: ${models}`, }, ], }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; return { content: [ { type: "text", text: `Error listing models: ${message}`, }, ], isError: true, }; } } case "compare_ollama_models": { let modelsArg = args?.models as string[] | undefined; const prompt = args?.prompt as string; const system_prompt = args?.system_prompt as string | undefined; if (!prompt) { return { content: [ { type: "text", text: "Missing required argument: prompt", }, ], isError: true, }; } let modelsToUse: string[] = []; if (Array.isArray(modelsArg) && modelsArg.length > 0) { // Resolve each requested model with fallback const resolved = await Promise.all(modelsArg.map((m) => resolveModel(m))); modelsToUse = resolved.map((r) => r.model); } else { try { const resp = await axios.get(`${OLLAMA_BASE_URL}/api/tags`); modelsToUse = (resp.data.models || []).map((m: any) => m.name).slice(0, 2); } catch (err) { modelsToUse = ["llama2"]; } } const contents: any[] = []; for (const m of modelsToUse) { try { const gen = await axios.post(`${OLLAMA_BASE_URL}/api/generate`, { model: m, prompt, system: system_prompt, stream: false, }); contents.push({ type: "text", text: `Model ${m}:\n${gen.data.response}` }); } catch (e) { const message = e instanceof Error ? e.message : String(e); contents.push({ type: "text", text: `Model ${m} failed: ${message}` }); } } return { content: contents }; } case "remember_consult": { const key = args?.key as string | undefined; const prompt = args?.prompt as string | undefined; const model = args?.model as string | undefined; let responseText = args?.response as string | undefined; if (!prompt) { return { content: [ { type: "text", text: "Missing required argument: prompt", }, ], isError: true, }; } if (!responseText) { // generate using Ollama if a model was provided if (!model) { return { content: [ { type: "text", text: "Missing 'response' and no 'model' provided to generate it." }, ], isError: true, }; } try { const gen = await axios.post(`${OLLAMA_BASE_URL}/api/generate`, { model, prompt, stream: false, }); responseText = gen.data.response; } catch (e) { const message = e instanceof Error ? e.message : String(e); return { content: [{ type: "text", text: `Failed to generate response: ${message}` }], isError: true, }; } } // Persist to a simple local memory directory by default. Can be overridden with MEMORY_DIR. const memoryDir = process.env.MEMORY_DIR || path.join("/tmp", "mcp-consult-memory"); try { await fs.mkdir(memoryDir, { recursive: true }); const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; const filePath = path.join(memoryDir, `observation-${id}.json`); const payload = { key: key || null, prompt, model: model || null, response: responseText, _meta: { createdAt: new Date().toISOString() } }; await fs.writeFile(filePath, JSON.stringify(payload, null, 2), "utf-8"); return { content: [ { type: "text", text: `Saved consult to ${filePath}`, }, { type: "resource", resource: { uri: `file://${filePath}`, text: responseText, }, }, ], }; } catch (err) { const message = err instanceof Error ? err.message : String(err); return { content: [{ type: "text", text: `Failed to save memory: ${message}` }], isError: true }; } } default: throw new Error(`Unknown tool: ${name}`); } } export default { listTools, callToolHandler };

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/Atomic-Germ/mcp-consult'

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