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);
});