mcp-sequentialthinking-tools
Version:
MCP server for Sequential Thinking Tools
210 lines (209 loc) ⢠8.5 kB
JavaScript
// 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);
});