Skip to main content
Glama

AI Consultant MCP Server

by filipkrayem
ConsultationService.ts10.6 kB
/** * Consultation service * Orchestrates the AI consultation workflow with caching, rate limiting, and history management */ import type { IApiClient, IModelSelector, ICache, IHistoryManager, IRateLimiter, ConsultArgs, ConsultResult, OpenRouterMessage, } from "../types/index.js"; import { generateCacheKey } from "../infrastructure/Cache.js"; import { Config } from "../config/Config.js"; export class ConsultationService { private readonly config: Config; constructor( private readonly apiClient: IApiClient, private readonly modelSelector: IModelSelector, private readonly cache: ICache<ConsultResult>, private readonly historyManager: IHistoryManager, private readonly rateLimiter: IRateLimiter, ) { this.config = Config.getInstance(); } /** * Consult with AI model (single or multiple models) */ public async consult(args: ConsultArgs): Promise<ConsultResult> { const { prompt, model, models, task_description, conversation_id, clear_history = false, } = args; // If multiple models specified, use multi-model consultation if (models && models.length > 0) { return this.consultMultipleModels(args); } // Check rate limit const rateLimitId = conversation_id || "global"; if (this.config.verboseLogging) { console.error(`[Service] Checking rate limit for: ${rateLimitId}`); } this.rateLimiter.checkLimit(rateLimitId); // Clear history if requested if (clear_history && conversation_id) { if (this.config.verboseLogging) { console.error(`[Service] Clearing conversation history for: ${conversation_id}`); } this.historyManager.clearHistory(conversation_id); } // Select model const selectedModel = this.selectModel(model, task_description, prompt); if (this.config.verboseLogging) { console.error(`[Service] Selected model: ${selectedModel}`); } // Check cache (only for non-conversation queries) if (!conversation_id) { const cachedResult = this.checkCache(prompt, selectedModel); if (cachedResult) { if (this.config.verboseLogging) { console.error("[Service] Returning cached result"); } return { ...cachedResult, model: `${selectedModel} (cached)`, }; } } // Get conversation history const history = conversation_id ? this.historyManager.getHistory(conversation_id) : []; if (this.config.verboseLogging && conversation_id) { console.error(`[Service] Loaded ${history.length} messages from conversation history`); } // Make API call if (this.config.verboseLogging) { console.error(`[Service] Making API call to OpenRouter with model: ${selectedModel}`); } const result = await this.apiClient.consultAI( prompt, selectedModel, history, ); // Update conversation history if conversation_id provided if (conversation_id) { const userMessage: OpenRouterMessage = { role: "user", content: prompt, }; const assistantMessage: OpenRouterMessage = { role: "assistant", content: result.response, }; if (this.config.verboseLogging) { console.error(`[Service] Updating conversation history for: ${conversation_id}`); } this.historyManager.updateHistory( conversation_id, userMessage, assistantMessage, ); } // Cache result (only for non-conversation queries) if (!conversation_id) { if (this.config.verboseLogging) { console.error("[Service] Caching result"); } this.cacheResult(prompt, selectedModel, result); } return result; } /** * Consult with multiple AI models sequentially */ private async consultMultipleModels(args: ConsultArgs): Promise<ConsultResult> { const { prompt, models, conversation_id, clear_history = false, } = args; if (!models || models.length === 0) { throw new Error("No models specified for multi-model consultation"); } if (this.config.verboseLogging) { console.error(`[Service] Starting multi-model consultation with ${models.length} models`); console.error(`[Service] Models: ${models.join(", ")}`); } // Check rate limit const rateLimitId = conversation_id || "global"; this.rateLimiter.checkLimit(rateLimitId); // Clear history if requested if (clear_history && conversation_id) { if (this.config.verboseLogging) { console.error(`[Service] Clearing conversation history for: ${conversation_id}`); } this.historyManager.clearHistory(conversation_id); } const results: Array<{ model: string; response: string; usage: any; cached: boolean; }> = []; let totalPromptTokens = 0; let totalCompletionTokens = 0; let totalTokens = 0; // Consult each model sequentially for (const modelId of models) { if (this.config.verboseLogging) { console.error(`[Service] Consulting model: ${modelId}`); } try { // Validate and get full model ID const modelConfig = this.modelSelector.getModelById(modelId); const selectedModel = modelConfig ? modelConfig.id : modelId; // Get conversation history const history = conversation_id ? this.historyManager.getHistory(conversation_id) : []; // Check cache (only for non-conversation queries) let result; let isCached = false; if (!conversation_id) { const cachedResult = this.checkCache(prompt, selectedModel); if (cachedResult) { result = cachedResult; isCached = true; if (this.config.verboseLogging) { console.error(`[Service] Using cached result for model: ${modelId}`); } } } // Make API call if not cached if (!result) { result = await this.apiClient.consultAI( prompt, selectedModel, history, ); // Cache result (only for non-conversation queries) if (!conversation_id) { this.cacheResult(prompt, selectedModel, result); } } // Collect result results.push({ model: modelId, response: result.response, usage: result.usage, cached: isCached, }); // Aggregate token usage totalPromptTokens += result.usage.prompt_tokens || 0; totalCompletionTokens += result.usage.completion_tokens || 0; totalTokens += result.usage.total_tokens || 0; if (this.config.verboseLogging) { console.error(`[Service] Completed consultation with ${modelId}`); } } catch (error) { console.error(`[Service] Error consulting model ${modelId}:`, error); // Continue with other models even if one fails results.push({ model: modelId, response: `Error: ${error instanceof Error ? error.message : String(error)}`, usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, cached: false, }); } } // Combine responses const combinedResponse = this.combineResponses(results); // Update conversation history if conversation_id provided if (conversation_id) { const userMessage: OpenRouterMessage = { role: "user", content: prompt, }; const assistantMessage: OpenRouterMessage = { role: "assistant", content: combinedResponse, }; this.historyManager.updateHistory( conversation_id, userMessage, assistantMessage, ); } // Return combined result in ConsultResult format return { model: `Multi-model: ${models.join(", ")}`, response: combinedResponse, usage: { prompt_tokens: totalPromptTokens, completion_tokens: totalCompletionTokens, total_tokens: totalTokens, }, }; } /** * Combine responses from multiple models */ private combineResponses( results: Array<{ model: string; response: string; usage: any; cached: boolean; }>, ): string { let combined = "# Multi-Model Consultation Results\n\n"; results.forEach((result, index) => { combined += `## Model ${index + 1}: ${result.model}${result.cached ? " (cached)" : ""}\n\n`; combined += `${result.response}\n\n`; combined += `**Tokens used:** ${result.usage.total_tokens || 0}\n\n`; combined += "---\n\n"; }); return combined; } /** * Select appropriate model */ private selectModel( explicitModel: string | undefined, taskDescription: string | undefined, prompt: string, ): string { // If explicit model provided, validate and use it if (explicitModel) { const modelConfig = this.modelSelector.getModelById(explicitModel); if (modelConfig) { return modelConfig.id; } // If invalid model ID, fall through to auto-selection console.warn( `Invalid model ID: ${explicitModel}. Using auto-selection.`, ); } // Auto-select based on task description or prompt const description = taskDescription || prompt; const modelConfig = this.modelSelector.selectModel(description); return modelConfig.id; } /** * Check cache for existing result */ private checkCache( prompt: string, model: string, ): ConsultResult | undefined { const cacheKey = generateCacheKey(prompt, model); const cached = this.cache.get(cacheKey); if (cached) { if (this.config.verboseLogging) { console.error("[Service] Cache hit for query"); } else { console.error("Cache hit for query"); } } else if (this.config.verboseLogging) { console.error("[Service] Cache miss for query"); } return cached; } /** * Cache consultation result */ private cacheResult( prompt: string, model: string, result: ConsultResult, ): void { const cacheKey = generateCacheKey(prompt, model); this.cache.set(cacheKey, result); } /** * List all available models */ public listModels() { const models = this.modelSelector.getAllModels(); return Object.entries(models).map(([key, model]) => ({ name: key, id: model.id, description: model.description, bestFor: model.bestFor, })); } }

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/filipkrayem/ai-consultant-mcp'

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