Skip to main content
Glama
palolxx

Pollinations Think MCP Server

index.jsβ€’28.2 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; /** * Advanced Thinking Engine for strategic analysis and web search */ class AdvancedThinkingEngine { constructor() { // Configuration from environment variables (for cloud deployment) or defaults this.baseUrl = process.env.BASE_URL || 'https://text.pollinations.ai'; this.defaultModel = process.env.DEFAULT_MODEL || 'deepseek-reasoning'; this.searchModel = process.env.SEARCH_MODEL || 'searchgpt'; this.debugMode = process.env.DEBUG_MODE === 'true' || process.env.NODE_ENV !== 'production'; this.continuationData = null; this.isReady = false; this.initializationPromise = null; // Thinking parameters (configurable for cloud deployment) this.maxCycles = parseInt(process.env.MAX_CYCLES) || 5; this.convergenceThreshold = parseFloat(process.env.CONVERGENCE_THRESHOLD) || 0.85; // Retry mechanism for first-time tool calls (configurable) this.calledTools = new Set(); this.maxRetries = parseInt(process.env.MAX_RETRIES) || 2; // Total of 3 attempts (initial + 2 retries) this.baseBackoffMs = parseInt(process.env.BASE_BACKOFF_MS) || 100; // API timeout configuration (configurable for cloud deployment) this.apiTimeout = parseInt(process.env.API_TIMEOUT) || 600000; // 10 minutes default this.log('🧠 Advanced Thinking Engine initializing...'); this.initializationPromise = this.initialize(); } /** * Initialize the engine and verify API connectivity */ async initialize() { try { this.log('πŸ”„ Performing initialization checks...'); // Test API connectivity with a simple request await this.performHealthCheck(); this.isReady = true; this.log('βœ… Advanced Thinking Engine fully initialized and ready'); } catch (error) { this.log(`❌ Initialization failed: ${error.message}`); throw error; } } /** * Perform health check to verify API connectivity with retry logic */ async performHealthCheck() { const maxRetries = 3; const retryDelay = 2000; // 2 seconds between retries for (let attempt = 1; attempt <= maxRetries; attempt++) { try { this.log(`πŸ” Health check attempt ${attempt}/${maxRetries}...`); const testPrompt = 'test'; const encodedPrompt = encodeURIComponent(testPrompt); const url = `${this.baseUrl}/${encodedPrompt}?model=${this.searchModel}`; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout for health check const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'text/plain', 'User-Agent': 'Pollinations-Think-MCP/2.0.0' }, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`Health check failed: ${response.statusText}`); } this.log('βœ… API health check passed'); return; // Success, exit the retry loop } catch (error) { this.log(`⚠️ Health check attempt ${attempt} failed: ${error.message}`); if (error.name === 'AbortError') { this.log('❌ Health check timed out - API may be unavailable'); } else if (error.code === 'ECONNRESET' || error.message.includes('TLS connection')) { this.log('❌ TLS/Network connection issue detected'); } // If this is the last attempt, handle the failure if (attempt === maxRetries) { this.log('⚠️ All health check attempts failed. Starting in degraded mode...'); this.log('πŸ’‘ The server will attempt to connect when tools are actually used.'); return; // Don't throw error, allow server to start in degraded mode } // Wait before retrying this.log(`⏳ Waiting ${retryDelay}ms before retry...`); await new Promise(resolve => setTimeout(resolve, retryDelay)); } } } /** * Ensure the engine is ready before processing requests */ async ensureReady() { if (!this.isReady) { this.log('⏳ Waiting for initialization to complete...'); await this.initializationPromise; } } /** * Execute tool call with retry logic for first-time calls */ async executeWithRetry(toolId, toolFunc, ...args) { if (this.calledTools.has(toolId)) { // Tool has been called before, no retry this.log(`πŸ”„ Tool '${toolId}' called before, executing without retry`); return await toolFunc(...args); } // First time calling this tool, apply retry logic this.log(`πŸ†• First time calling tool '${toolId}', applying retry logic`); try { let attempts = 0; while (true) { try { const result = await toolFunc(...args); this.log(`βœ… Tool '${toolId}' succeeded on attempt ${attempts + 1}`); return result; } catch (error) { if (attempts >= this.maxRetries) { this.log(`❌ Tool '${toolId}' failed after ${attempts + 1} attempts: ${error.message}`); throw error; } // Calculate exponential backoff delay const delay = this.baseBackoffMs * Math.pow(2, attempts); this.log(`⏳ Tool '${toolId}' failed on attempt ${attempts + 1}, retrying in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); attempts++; } } } finally { // Mark tool as called regardless of success or failure this.calledTools.add(toolId); this.log(`πŸ“ Tool '${toolId}' marked as called`); } } // Note: Thinking operations now work directly through MCP server calls // The internal thinking API methods have been removed as they were causing issues // Direct MCP calls to the think tool are functioning correctly /** * Execute advanced thinking process */ async executeThinking(text, model = null, seed = null, maxCycles = 3) { // Ensure engine is ready before processing await this.ensureReady(); this.log(`🧠 Starting thinking process: ${text.substring(0, 100)}...`); try { // Use the thinking model for reasoning const thinkingModel = model || this.defaultModel; // Build thinking prompt const thinkingPrompt = this.buildThinkingPrompt(text); // Call thinking API const result = await this.callPollinationsAPI(thinkingPrompt, thinkingModel); return this.formatThinkingOutput(text, result); } catch (error) { this.log(`❌ Thinking Error: ${error.message}`); throw error; } } /** * Execute web search using SearchGPT */ async executeSearch(query) { // Ensure engine is ready before processing await this.ensureReady(); this.log(`πŸ” Starting web search: ${query}`); try { // Build search prompt const searchPrompt = this.buildSearchPrompt(query); // Call search API const searchResults = await this.callSearchAPI(searchPrompt); // Format search output return this.formatSearchOutput(query, searchResults); } catch (error) { this.log(`❌ Search Error: ${error.message}`); throw error; } } /** * Build search prompt */ buildSearchPrompt(query) { return `Search the web for: ${query}`; } /** * Build thinking prompt for strategic analysis */ buildThinkingPrompt(text) { return `Analyze this topic strategically with deep reasoning: ${text} Provide a comprehensive analysis that considers multiple perspectives, potential implications, and actionable insights.`; } /** * Call Pollinations Thinking API directly */ async callThinkingAPIDirectly(prompt, model) { const encodedPrompt = encodeURIComponent(prompt); const url = `${this.baseUrl}/${encodedPrompt}?model=${model}`; try { // Create AbortController for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.apiTimeout); // Configurable timeout for AI inference const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'text/plain', 'User-Agent': 'Pollinations-Think-MCP/2.0.0' }, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`Thinking API request failed: ${response.statusText}`); } const result = await response.text(); this.log(`βœ… Thinking API Response received (${result.length} chars)`); return result; } catch (error) { if (error.name === 'AbortError') { this.log(`⏰ Thinking API Timeout: Request exceeded 60 seconds`); throw new Error('Thinking API request timed out after 60 seconds'); } this.log(`❌ Thinking API Error: ${error.message}`); throw error; } } /** * Format thinking output */ formatThinkingOutput(originalText, analysis) { return `# Strategic Analysis **Topic:** ${originalText} ## Analysis ${analysis} `; } /** * Call Pollinations Search API with hybrid GET/POST approach */ async callSearchAPI(prompt) { this.log(`🌐 Search API Call: ${this.searchModel}`); // Determine if we need POST for long queries (>200 chars to be safe) const usePost = prompt.length > 200; if (usePost) { this.log(`πŸ“ Using POST request for long query (${prompt.length} chars)`); return this.callSearchAPIPOST(prompt); } else { this.log(`πŸ”— Using GET request for short query (${prompt.length} chars)`); return this.callSearchAPIGET(prompt); } } /** * Call Pollinations Search API using GET method */ async callSearchAPIGET(prompt) { const encodedPrompt = encodeURIComponent(prompt); const url = `${this.baseUrl}/${encodedPrompt}/?model=${this.searchModel}&token=`; try { const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'text/plain', 'User-Agent': 'Pollinations-Think-MCP/2.0.0' } }); if (!response.ok) { throw new Error(`Search API GET request failed: ${response.statusText}`); } const result = await response.text(); this.log(`βœ… Search API GET Response received (${result.length} chars)`); return result; } catch (error) { this.log(`❌ Search API GET Error: ${error.message}`); throw error; } } /** * Call Pollinations Search API using POST method for long queries */ async callSearchAPIPOST(prompt) { const url = 'https://text.pollinations.ai/'; const payload = { messages: [ { role: 'system', content: 'You are SearchGPT, a helpful search assistant that provides comprehensive web search results.' }, { role: 'user', content: prompt } ], model: this.searchModel, seed: Math.floor(Math.random() * 1000000) }; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/plain', 'User-Agent': 'Pollinations-Think-MCP/2.0.0' }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error(`Search API POST request failed: ${response.statusText}`); } const result = await response.text(); this.log(`βœ… Search API POST Response received (${result.length} chars)`); return result; } catch (error) { this.log(`❌ Search API POST Error: ${error.message}`); throw error; } } /** * Format search output */ formatSearchOutput(query, searchResults) { return `# πŸ” Web Search Results: ${query} ${searchResults} --- *Search powered by SearchGPT via Pollinations.ai*`; } /** * Continue thinking from previous state */ async continueThinking(continuationId, additionalInput = null) { // Ensure engine is ready before processing await this.ensureReady(); if (!this.continuationData || this.continuationData.id !== continuationId) { throw new Error('Invalid continuation ID or no continuation data available'); } this.log(`πŸ”„ Continuing thinking from: ${continuationId}`); const { originalText, currentAnalysis, cycles, model, seed } = this.continuationData; // Incorporate additional input if provided let enhancedText = originalText; if (additionalInput) { enhancedText += `\n\nAdditional context: ${additionalInput}`; } // Continue with enhanced analysis return await this.executeThinking(enhancedText, model, seed, cycles + 2); } /** * Build initial analysis prompt */ buildInitialAnalysisPrompt(text) { return `Analyze: ${text}`; } /** * Build contradiction prompt */ buildContradictionPrompt(originalText, currentAnalysis, cycle) { return `Challenge this analysis of "${originalText}": ${currentAnalysis.substring(0, 1000)} Identify flaws, present alternative perspectives, and question assumptions. Provide substantive counterpoints.`; } /** * Build synthesis prompt */ buildSynthesisPrompt(originalText, currentAnalysis, contradiction, history) { return `Synthesize a new analysis of "${originalText}" by integrating: Analysis: ${currentAnalysis.substring(0, 800)} Counterpoint: ${contradiction.substring(0, 800)} Create a balanced synthesis that acknowledges valid points from both perspectives and develops a more nuanced understanding.`; } /** * Build final synthesis prompt */ buildFinalSynthesisPrompt(text, currentAnalysis, contradictionHistory, insightEvolution) { return `Create a final analysis of "${text}": ${currentAnalysis.substring(0, 1000)} Provide a comprehensive perspective that integrates insights, acknowledges complexity, and offers actionable recommendations.`; } /** * Build meta-analysis prompt */ buildMetaAnalysisPrompt(text, finalAnalysis, cycles) { return `Meta-analysis of thinking process for "${text}": ${finalAnalysis.substring(0, 800)} Reflect on the quality of the ${cycles}-cycle thinking process, key insights emerged, and confidence level in conclusions.`; } /** * Check convergence between recent insights */ checkConvergence(insightEvolution) { if (insightEvolution.length < 2) return false; const recent = insightEvolution.slice(-2); const similarity = this.calculateSimilarity(recent[0].content, recent[1].content); return similarity > this.convergenceThreshold; } /** * Calculate similarity between two texts (simple implementation) */ calculateSimilarity(text1, text2) { const words1 = new Set(text1.toLowerCase().split(/\s+/)); const words2 = new Set(text2.toLowerCase().split(/\s+/)); const intersection = new Set([...words1].filter(x => words2.has(x))); const union = new Set([...words1, ...words2]); return intersection.size / union.size; } /** * Format final output */ formatFinalOutput(originalText, finalAnalysis, metaAnalysis, cycles, contradictions) { const timestamp = new Date().toISOString(); const continuationId = `think_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Store continuation data this.continuationData = { id: continuationId, originalText, currentAnalysis: finalAnalysis, cycles, model: this.defaultModel, seed: Math.floor(Math.random() * 1000000), timestamp }; return `# 🧠 Advanced Strategic Analysis **Topic:** ${originalText} **Analysis Depth:** ${cycles} thinking cycles, ${contradictions} contradictions processed **Timestamp:** ${timestamp} --- ## πŸ“Š Final Analysis ${finalAnalysis} --- ## πŸ” Meta-Analysis ${metaAnalysis} --- ## πŸ”„ Continuation To continue this analysis with additional context, use: \`\`\` continuation_id: ${continuationId} \`\`\` *Analysis powered by Pollinations.ai Advanced Thinking Engine*`; } /** * Call Pollinations API with hybrid GET/POST approach */ async callPollinationsAPI(prompt, model = null, seed = null) { const actualModel = model || this.defaultModel; const actualSeed = seed || Math.floor(Math.random() * 1000000); this.log(`🌐 API Call: ${actualModel} (seed: ${actualSeed})`); // Determine if we need POST for long queries (>200 chars to be safe) const usePost = prompt.length > 200; if (usePost) { this.log(`πŸ“ Using POST request for long query (${prompt.length} chars)`); return this.callPollinationsAPIPOST(prompt, actualModel, actualSeed); } else { this.log(`πŸ”— Using GET request for short query (${prompt.length} chars)`); return this.callPollinationsAPIGET(prompt, actualModel, actualSeed); } } /** * Call Pollinations API using GET method */ async callPollinationsAPIGET(prompt, model, seed) { const encodedPrompt = encodeURIComponent(prompt); const url = `${this.baseUrl}/${encodedPrompt}?model=${model}&seed=${seed}`; try { // Create AbortController for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.apiTimeout); // Configurable timeout for AI inference const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'text/plain', 'User-Agent': 'Pollinations-Think-MCP/2.0.0' }, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`API GET request failed: ${response.statusText}`); } const result = await response.text(); this.log(`βœ… API GET Response received (${result.length} chars)`); // Handle large responses return this.handleLargeResponse(result); } catch (error) { if (error.name === 'AbortError') { this.log(`⏰ API GET Timeout: Request exceeded ${this.apiTimeout/1000} seconds`); throw new Error(`API request timed out after ${this.apiTimeout/1000} seconds`); } // Handle TLS/Network connection issues if (error.code === 'ECONNRESET' || error.message.includes('TLS connection') || error.message.includes('fetch failed')) { this.log(`πŸ”Œ Network/TLS connection issue: ${error.message}`); throw new Error('Network connection failed. Please check your internet connection and try again.'); } this.log(`❌ API GET Error: ${error.message}`); throw error; } } /** * Call Pollinations API using POST method for long queries */ async callPollinationsAPIPOST(prompt, model, seed) { const url = 'https://text.pollinations.ai/'; const payload = { messages: [ { role: 'system', content: 'You are an advanced strategic thinking and analysis AI using contradiction cycles and synthesis. Process complex topics through multiple analytical phases to develop nuanced, well-reasoned insights.' }, { role: 'user', content: prompt } ], model: model, seed: seed }; try { // Create AbortController for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 600000); // 10 minute timeout for long-running AI inference const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/plain', 'User-Agent': 'Pollinations-Think-MCP/2.0.0' }, body: JSON.stringify(payload), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`API POST request failed: ${response.statusText}`); } const result = await response.text(); this.log(`βœ… API POST Response received (${result.length} chars)`); // Handle large responses return this.handleLargeResponse(result); } catch (error) { if (error.name === 'AbortError') { this.log(`⏰ API POST Timeout: Request exceeded ${this.apiTimeout/1000} seconds`); throw new Error(`API request timed out after ${this.apiTimeout/1000} seconds`); } // Handle TLS/Network connection issues if (error.code === 'ECONNRESET' || error.message.includes('TLS connection') || error.message.includes('fetch failed')) { this.log(`πŸ”Œ Network/TLS connection issue: ${error.message}`); throw new Error('Network connection failed. Please check your internet connection and try again.'); } this.log(`❌ API POST Error: ${error.message}`); throw error; } } /** * Handle large API responses */ handleLargeResponse(response) { const maxLength = 8000; // Reasonable limit for MCP responses if (response.length <= maxLength) { return response; } this.log(`⚠️ Large response detected (${response.length} chars), truncating...`); // Try to find a good breaking point const truncated = response.substring(0, maxLength); const lastParagraph = truncated.lastIndexOf('\n\n'); const lastSentence = truncated.lastIndexOf('.'); let breakPoint = maxLength; if (lastParagraph > maxLength * 0.8) { breakPoint = lastParagraph; } else if (lastSentence > maxLength * 0.8) { breakPoint = lastSentence + 1; } const result = response.substring(0, breakPoint); const remaining = response.length - breakPoint; return `${result}\n\n---\n\n*[Response truncated - ${remaining} characters omitted for optimal performance]*`; } /** * Debug logging */ log(message) { if (this.debugMode) { console.error(`[${new Date().toISOString()}] ${message}`); } } } // Create the MCP server const server = new Server( { name: 'pollinations-think', version: '2.0.0', }, { capabilities: { tools: {}, }, } ); // Create thinking engine instance const thinkingEngine = new AdvancedThinkingEngine(); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'think', description: 'Advanced strategic thinking and analysis using contradiction cycles and synthesis. Processes complex topics through multiple analytical phases to develop nuanced, well-reasoned insights.', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'The topic, question, or problem to analyze strategically' }, model: { type: 'string', description: 'AI model to use (default: openai-reasoning)', default: 'openai-reasoning' }, seed: { type: 'number', description: 'Random seed for reproducible results (optional)' } }, required: ['text'] } }, { name: 'continue_thinking', description: 'Continue a previous thinking session with additional context or refinement. Requires a continuation ID from a previous think operation.', inputSchema: { type: 'object', properties: { continuation_id: { type: 'string', description: 'The continuation ID from a previous think operation' }, additional_input: { type: 'string', description: 'Additional context or questions to incorporate (optional)' } }, required: ['continuation_id'] } }, { name: 'search', description: 'Perform real-time web search using SearchGPT. Returns current information from the internet on any topic.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The search query to find information about' } }, required: ['query'] } } ] }; });// Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'think': { const { text, model, seed } = args; if (!text || typeof text !== 'string') { throw new Error('Text parameter is required and must be a string'); } const result = await thinkingEngine.executeWithRetry( 'think', () => thinkingEngine.executeThinking(text, model, seed) ); return { content: [ { type: 'text', text: result } ] }; } case 'continue_thinking': { const { continuation_id, additional_input } = args; if (!continuation_id || typeof continuation_id !== 'string') { throw new Error('Continuation ID is required and must be a string'); } const result = await thinkingEngine.executeWithRetry( 'continue_thinking', () => thinkingEngine.continueThinking(continuation_id, additional_input) ); return { content: [ { type: 'text', text: result } ] }; } case 'search': { const { query } = args; if (!query || typeof query !== 'string') { throw new Error('Query parameter is required and must be a string'); } const result = await thinkingEngine.executeWithRetry( 'search', () => thinkingEngine.executeSearch(query) ); return { content: [ { type: 'text', text: result } ] }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { console.error(`Tool execution error: ${error.message}`); throw error; } }); // Start the server async function main() { try { console.error('πŸ”„ Starting Pollinations Think MCP Server v2.0.0...'); // Wait for thinking engine to be fully initialized console.error('⏳ Initializing thinking engine...'); await thinkingEngine.ensureReady(); // Connect the server transport const transport = new StdioServerTransport(); await server.connect(transport); console.error('πŸš€ Pollinations Think MCP Server v2.0.0 started successfully'); console.error('🧠 Available tools: think, continue_thinking, search'); console.error('πŸ€– Models: openai-reasoning, searchgpt'); console.error('βœ… Server is ready to accept requests'); } catch (error) { console.error('πŸ’₯ Server initialization failed:', error.message); throw error; } } // Handle graceful shutdown process.on('SIGTERM', () => { console.error('πŸ›‘ Received SIGTERM, shutting down gracefully...'); process.exit(0); }); process.on('SIGINT', () => { console.error('πŸ›‘ Received SIGINT, shutting down gracefully...'); process.exit(0); }); // Start the server main().catch((error) => { console.error('πŸ’₯ Server startup error:', error); process.exit(1); });

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/palolxx/pollinations-think-mcp'

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