UNPKG

mcp-sequentialthinking-tools

Version:
210 lines (209 loc) • 8.5 kB
#!/usr/bin/env node // adapted from https://github.com/modelcontextprotocol/servers/blob/main/src/sequentialthinking/index.ts // for use with mcp tools import { McpServer } from 'tmcp'; import { ValibotJsonSchemaAdapter } from '@tmcp/adapter-valibot'; import { StdioTransport } from '@tmcp/transport-stdio'; import chalk from 'chalk'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { SequentialThinkingSchema, SEQUENTIAL_THINKING_TOOL } from './schema.js'; // Get version from package.json const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const package_json = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8')); const { name, version } = package_json; // Create MCP server with tmcp const adapter = new ValibotJsonSchemaAdapter(); const server = new McpServer({ name, version, description: 'MCP server for Sequential Thinking Tools', }, { adapter, capabilities: { tools: { listChanged: true }, }, }); class ToolAwareSequentialThinkingServer { getAvailableTools() { return Array.from(this.available_tools.values()); } constructor(options = {}) { this.thought_history = []; this.branches = {}; this.available_tools = new Map(); this.maxHistorySize = options.maxHistorySize || 1000; // Always include the sequential thinking tool const tools = [ SEQUENTIAL_THINKING_TOOL, ...(options.available_tools || []), ]; // Initialize with provided tools tools.forEach((tool) => { if (this.available_tools.has(tool.name)) { console.error(`Warning: Duplicate tool name '${tool.name}' - using first occurrence`); return; } this.available_tools.set(tool.name, tool); }); console.error('Available tools:', Array.from(this.available_tools.keys())); } clearHistory() { this.thought_history = []; this.branches = {}; console.error('History cleared'); } addTool(tool) { if (this.available_tools.has(tool.name)) { console.error(`Warning: Tool '${tool.name}' already exists`); return; } this.available_tools.set(tool.name, tool); console.error(`Added tool: ${tool.name}`); } discoverTools() { // In a real implementation, this would scan the environment // for available MCP tools and add them to available_tools console.error('Tool discovery not implemented - manually add tools via addTool()'); } formatRecommendation(step) { const tools = step.recommended_tools .map((tool) => { const alternatives = tool.alternatives?.length ? ` (alternatives: ${tool.alternatives.join(', ')})` : ''; const inputs = tool.suggested_inputs ? `\n Suggested inputs: ${JSON.stringify(tool.suggested_inputs)}` : ''; return ` - ${tool.tool_name} (priority: ${tool.priority})${alternatives} Rationale: ${tool.rationale}${inputs}`; }) .join('\n'); return `Step: ${step.step_description} Recommended Tools: ${tools} Expected Outcome: ${step.expected_outcome}${step.next_step_conditions ? `\nConditions for next step:\n - ${step.next_step_conditions.join('\n - ')}` : ''}`; } formatThought(thoughtData) { const { thought_number, total_thoughts, thought, is_revision, revises_thought, branch_from_thought, branch_id, current_step, } = thoughtData; let prefix = ''; let context = ''; if (is_revision) { prefix = chalk.yellow('šŸ”„ Revision'); context = ` (revising thought ${revises_thought})`; } else if (branch_from_thought) { prefix = chalk.green('🌿 Branch'); context = ` (from thought ${branch_from_thought}, ID: ${branch_id})`; } else { prefix = chalk.blue('šŸ’­ Thought'); context = ''; } const header = `${prefix} ${thought_number}/${total_thoughts}${context}`; let content = thought; // Add recommendation information if present if (current_step) { content = `${thought}\n\nRecommendation:\n${this.formatRecommendation(current_step)}`; } const border = '─'.repeat(Math.max(header.length, content.length) + 4); return ` ā”Œ${border}┐ │ ${header} │ ā”œ${border}┤ │ ${content.padEnd(border.length - 2)} │ ā””${border}ā”˜`; } async processThought(input) { try { // Input is already validated by tmcp with Valibot const validatedInput = input; if (validatedInput.thought_number > validatedInput.total_thoughts) { validatedInput.total_thoughts = validatedInput.thought_number; } // Store the current step in thought history if (validatedInput.current_step) { if (!validatedInput.previous_steps) { validatedInput.previous_steps = []; } validatedInput.previous_steps.push(validatedInput.current_step); } this.thought_history.push(validatedInput); // Prevent memory leaks by limiting history size if (this.thought_history.length > this.maxHistorySize) { this.thought_history = this.thought_history.slice(-this.maxHistorySize); console.error(`History trimmed to ${this.maxHistorySize} items`); } if (validatedInput.branch_from_thought && validatedInput.branch_id) { if (!this.branches[validatedInput.branch_id]) { this.branches[validatedInput.branch_id] = []; } this.branches[validatedInput.branch_id].push(validatedInput); } const formattedThought = this.formatThought(validatedInput); console.error(formattedThought); return { content: [ { type: 'text', text: JSON.stringify({ thought_number: validatedInput.thought_number, total_thoughts: validatedInput.total_thoughts, next_thought_needed: validatedInput.next_thought_needed, branches: Object.keys(this.branches), thought_history_length: this.thought_history.length, available_mcp_tools: validatedInput.available_mcp_tools, current_step: validatedInput.current_step, previous_steps: validatedInput.previous_steps, remaining_steps: validatedInput.remaining_steps, }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: error instanceof Error ? error.message : String(error), status: 'failed', }, null, 2), }, ], isError: true, }; } } } // Read configuration from environment variables or command line args const maxHistorySize = parseInt(process.env.MAX_HISTORY_SIZE || '1000'); const thinkingServer = new ToolAwareSequentialThinkingServer({ available_tools: [], // TODO: Add tool discovery mechanism maxHistorySize, }); // Register the sequential thinking tool server.tool({ name: 'sequentialthinking_tools', description: SEQUENTIAL_THINKING_TOOL.description, schema: SequentialThinkingSchema, }, async (input) => { return thinkingServer.processThought(input); }); async function main() { const transport = new StdioTransport(server); transport.listen(); console.error('Sequential Thinking MCP Server running on stdio'); } main().catch((error) => { console.error('Fatal error running server:', error); process.exit(1); });