UNPKG

@mokemokechicken/mcp-ai-assistant-iris

Version:

AI assistant MCP server with web search and code execution capabilities

279 lines 12.2 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import OpenAI from "openai"; import { z } from "zod"; import fetch, { Headers, Blob, FormData, File, Request, Response } from "node-fetch"; const VERSION = "0.5.3"; // Set up fetch and Web API polyfills for Node.js environment if (!globalThis.fetch) { globalThis.fetch = fetch; } if (!globalThis.Headers) { globalThis.Headers = Headers; } if (!globalThis.Blob) { globalThis.Blob = Blob; } if (!globalThis.FormData) { globalThis.FormData = FormData; } if (!globalThis.File) { globalThis.File = File; } if (!globalThis.Request) { globalThis.Request = Request; } if (!globalThis.Response) { globalThis.Response = Response; } if (!globalThis.URL && typeof URL !== 'undefined') { globalThis.URL = URL; } if (!globalThis.URLSearchParams && typeof URLSearchParams !== 'undefined') { globalThis.URLSearchParams = URLSearchParams; } // Create server instance const server = new McpServer({ name: "mcp-ai-assistant-iris", version: VERSION, }); // Initialize OpenAI client const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); // Task3: Model Selection Logic function selectModel(modelParam) { if (modelParam && modelParam !== 'gpt-5' && modelParam !== 'o3') { throw new Error(`Invalid model: ${modelParam}. Supported models: gpt-5, o3`); } return modelParam === 'o3' ? 'o3' : 'gpt-5'; } // Task4: Dynamic Tools Construction function buildToolsArray(contextSize, useCodeInterpreter) { const tools = [ { type: 'web_search_preview', search_context_size: contextSize } ]; if (useCodeInterpreter) { tools.push({ type: 'code_interpreter', container: { type: 'auto' } }); } return tools; } // Task7: Error Handling Enhancement const errorHandling = { // 無効なモデル指定 invalidModel: (model) => `Invalid model: ${model}. Supported models: gpt-5, o3`, // Code Interpreter エラー codeInterpreterError: (error) => `Code Interpreter error: ${error.message || error}`, // OpenAI API エラー apiError: (error) => `OpenAI API error: ${error.message || error}`, // previous_response_id 関連エラー invalidResponseId: (id) => `Invalid or expired previous_response_id: ${id}. Response IDs are valid for 30 days.`, responseIdNotFound: (id) => `Previous response not found: ${id}. The response may have expired (30 day limit) or never existed.`, // 一般的なエラー generalError: (error) => `Error: ${error instanceof Error ? error.message : "Unknown error occurred"}` }; // Task6: Response Processing Extension with Error Handling function processOpenAIResponse(response) { try { if (!response.output || !Array.isArray(response.output)) { return response.output_text || "No response available."; } const textParts = []; for (const outputItem of response.output) { try { if (outputItem.type === 'code_interpreter_call') { // Process code interpreter results if (outputItem.results && Array.isArray(outputItem.results)) { for (const result of outputItem.results) { try { if (result.type === 'logs' && result.logs) { textParts.push(`Code Execution:\n${result.logs}`); } else if (result.type === 'error' && result.error) { // Handle code interpreter execution errors textParts.push(`Code Execution Error:\n${result.error}`); } } catch (resultError) { console.error("Error processing code interpreter result:", resultError); textParts.push("Error processing code execution result."); } } } } else if (outputItem.type === 'message') { // Process message content if (outputItem.content && Array.isArray(outputItem.content)) { for (const contentItem of outputItem.content) { try { if (contentItem.type === 'output_text' && contentItem.text) { textParts.push(contentItem.text); } } catch (contentError) { console.error("Error processing message content:", contentError); textParts.push("Error processing message content."); } } } } } catch (outputError) { console.error("Error processing output item:", outputError); textParts.push("Error processing response item."); } } // Fall back to output_text if no parts were extracted if (textParts.length === 0) { return response.output_text || "No response available."; } return textParts.join('\n\n'); } catch (error) { console.error("Error in processOpenAIResponse:", error); return response.output_text || "Error processing response."; } } // Task3: Response Processing with ID Extension function processOpenAIResponseWithId(response) { try { // 既存のテキスト処理 const processedText = processOpenAIResponse(response); // response.id を抽出 const responseId = response.id; // テキストにResponse IDを追加 const textWithId = responseId ? `${processedText}\n\n[Response ID: ${responseId}]` : processedText; return { text: textWithId, response_id: responseId }; } catch (error) { console.error("Error in processOpenAIResponseWithId:", error); return { text: response.output_text || "Error processing response." }; } } // Define the iris tool server.tool("iris", `iris (v${VERSION}): An AI agent with advanced web search and code execution capabilities. Supports model selection (gpt-5/o3) and optional code interpreter for data analysis. Useful for finding latest information, troubleshooting errors, and executing code. Supports natural language queries.`, { input: z.string().describe('Ask questions, search for information, or consult about complex problems in English.'), searchContextSize: z.enum(['low', 'medium', 'high']).optional().describe('Search context size for web search (low/medium/high). Defaults to medium.'), reasoningEffort: z.enum(['low', 'medium', 'high']).optional().describe('Reasoning effort level (low/medium/high). Defaults to medium.'), model: z.enum(['gpt-5', 'o3']).optional().describe('AI model to use (gpt-5/o3). Defaults to gpt-5 (is better).'), useCodeInterpreter: z.boolean().optional().describe('Enable code interpreter for data analysis and code execution. Defaults to false.'), previous_response_id: z.string().optional().describe('Previous OpenAI response ID for conversation continuity. ' + 'Valid for 30 days from creation. Enables context preservation ' + 'across multiple tool calls. Use the Response ID from previous iris tool response.'), }, async ({ input, searchContextSize: contextSize, reasoningEffort: effort, model, useCodeInterpreter, previous_response_id }) => { const finalContextSize = contextSize || 'medium'; const finalReasoningEffort = effort || 'medium'; const finalUseCodeInterpreter = useCodeInterpreter || false; try { // Task3: Use dynamic model selection - may throw for invalid model const selectedModel = selectModel(model); // Task4: Use dynamic tools construction const dynamicTools = buildToolsArray(finalContextSize, finalUseCodeInterpreter); const apiRequest = { model: selectedModel, input, tools: dynamicTools, tool_choice: 'auto', parallel_tool_calls: true, reasoning: { effort: finalReasoningEffort }, }; // previous_response_id が指定されている場合のみ追加 if (previous_response_id) { apiRequest.previous_response_id = previous_response_id; } const response = await openai.responses.create(apiRequest); // Task6: Use enhanced response processing with ID const { text, response_id } = processOpenAIResponseWithId(response); const result = { content: [ { type: "text", text: text, }, ], }; // structuredContent を追加(response_id が存在する場合) if (response_id) { result.structuredContent = { response_id: response_id }; } return result; } catch (error) { // Task7: Enhanced Error Handling let errorMessage; if (error instanceof Error) { // Check for specific error types based on error patterns if (error.message.includes('Invalid model:')) { // Invalid model error from selectModel function console.error("Model selection error:", error.message); errorMessage = error.message; // Already formatted by selectModel function } else if (error.message.includes('code_interpreter') || error.message.includes('Code Interpreter') || error.message.includes('container') || error.type === 'code_interpreter_error') { // Code Interpreter specific error console.error("Code Interpreter error:", error); errorMessage = errorHandling.codeInterpreterError(error); } else if (error.message.includes('previous_response_id') || error.message.includes('response not found') || error.message.includes('invalid response') || error.code === 'invalid_response_id') { // previous_response_id 関連エラー console.error("Previous response ID error:", error); errorMessage = errorHandling.invalidResponseId(previous_response_id || 'unknown'); } else if (error.name === 'OpenAIError' || error.name === 'APIError' || error.message.includes('OpenAI') || error.message.includes('API') || error.message.includes('status') || error.message.includes('rate limit')) { // OpenAI API specific error console.error("OpenAI API error:", error); errorMessage = errorHandling.apiError(error); } else { // General error console.error("General error:", error); errorMessage = errorHandling.generalError(error); } } else { // Unknown error type (not an Error instance) console.error("Unknown error type:", error); errorMessage = errorHandling.generalError(error); } return { content: [ { type: "text", text: errorMessage, }, ], }; } }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); //# sourceMappingURL=index.js.map