Skip to main content
Glama

Code Reasoning MCP Server

server.ts22 kB
#!/usr/bin/env node /** * @fileoverview Code Reasoning MCP Server Implementation. * * This server provides a tool for reflective problem-solving in software development, * allowing decomposition of tasks into sequential, revisable, and branchable thoughts. * It adheres to the Model Context Protocol (MCP) using SDK version 1.18.1 and is designed * to integrate seamlessly with Claude Desktop or similar MCP-compliant clients. * * ## Key Features * - Processes "thoughts" in structured JSON with sequential numbering * - Supports advanced reasoning patterns through branching and revision semantics * - Branching: Explore alternative approaches from any existing thought * - Revision: Correct or update earlier thoughts when new insights emerge * - Implements MCP capabilities for tools, resources, and prompts * - Relies on the standard StdioServerTransport provided by the MCP SDK * - Provides detailed validation and error handling with helpful guidance * - Logs thought evolution to stderr for debugging and visibility * * ## Usage in Claude Desktop * - In your Claude Desktop settings, add a "tool" definition referencing this server * - Ensure the tool name is "code-reasoning" * - Configure Claude to use this tool for complex reasoning and problem-solving tasks * - Upon connecting, Claude can call the tool with arguments matching the * `ThoughtData` interface defined in this file * * ## MCP Protocol Communication * - IMPORTANT: Local MCP servers must never log to stdout (standard output) * - All logging must be directed to stderr using console.error() instead of console.log() * - The stdout channel is reserved exclusively for JSON-RPC protocol messages * - Using console.log() or console.info() will cause client-side parsing errors * * ## Example Thought Data * ```json * { * "thought": "Start investigating the root cause of bug #1234", * "thought_number": 1, * "total_thoughts": 5, * "next_thought_needed": true * } * ``` * * @version 0.7.0 * @mcp-sdk-version 1.18.1 */ import process from 'node:process'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { type CallToolResult, CompleteRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, type LoggingLevel, } from '@modelcontextprotocol/sdk/types.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z, ZodError } from 'zod'; import { PromptManager } from './prompts/manager.js'; import { CONFIG_DIR } from './utils/config.js'; /* -------------------------------------------------------------------------- */ /* CONFIGURATION */ /* -------------------------------------------------------------------------- */ // Compile-time enum -> const enum would be erased, but we keep values for logs. export enum LogLevel { ERROR = 0, WARN = 1, INFO = 2, DEBUG = 3, } const MAX_THOUGHT_LENGTH = 20000; const MAX_THOUGHTS = 20; interface CodeReasoningConfig { debug: boolean; promptsEnabled: boolean; } const DEFAULT_CONFIG: Readonly<CodeReasoningConfig> = Object.freeze({ debug: false, promptsEnabled: true, }); const createConfig = (overrides: Partial<CodeReasoningConfig> = {}): CodeReasoningConfig => ({ ...DEFAULT_CONFIG, ...overrides, }); /* -------------------------------------------------------------------------- */ /* DATA SCHEMAS */ /* -------------------------------------------------------------------------- */ const createStrictThoughtShape = () => ({ thought: z .string() .trim() .min(1, 'Thought cannot be empty.') .max(MAX_THOUGHT_LENGTH, `Thought exceeds ${MAX_THOUGHT_LENGTH} chars.`), thought_number: z.number().int().positive(), total_thoughts: z.number().int().positive(), next_thought_needed: z.boolean(), is_revision: z.boolean().optional(), revises_thought: z.number().int().positive().optional(), branch_from_thought: z.number().int().positive().optional(), branch_id: z.string().trim().min(1).optional(), needs_more_thoughts: z.boolean().optional(), }); const createLooseThoughtShape = () => ({ thought: z.string().trim(), thought_number: z.number().int(), total_thoughts: z.number().int(), next_thought_needed: z.boolean(), is_revision: z.boolean().optional(), revises_thought: z.number().int().optional(), branch_from_thought: z.number().int().optional(), branch_id: z.string().trim().optional(), needs_more_thoughts: z.boolean().optional(), }); const ThoughtDataInputShape = createLooseThoughtShape(); const StrictThoughtSchema = z.object(createStrictThoughtShape()); export type ThoughtData = z.infer<typeof StrictThoughtSchema>; export type ValidatedThoughtData = ThoughtData; type ParsedThoughtData = z.infer<z.ZodObject<typeof ThoughtDataInputShape>>; /* -------------------------------------------------------------------------- */ /* TOOL DEF */ /* -------------------------------------------------------------------------- */ const TOOL_NAME = 'code-reasoning' as const; const createCodeReasoningToolDefinition = (serverVersion: string) => ({ title: 'Code Reasoning', description: `🧠 A detailed tool for dynamic and reflective problem-solving through sequential thinking. This tool helps you analyze problems through a flexible thinking process that can adapt and evolve. Each thought can build on, question, or revise previous insights as understanding deepens. 📋 KEY PARAMETERS: - thought: Your current reasoning step (required) - thought_number: Current number in sequence (required) - total_thoughts: Estimated final count (required, can adjust as needed) - next_thought_needed: Set to FALSE ONLY when completely done (required) - branch_from_thought + branch_id: When exploring alternative approaches (🌿) - is_revision + revises_thought: When correcting earlier thinking (🔄) ✅ CRITICAL CHECKLIST (review every 3 thoughts): 1. Need to explore alternatives? → Use BRANCH (🌿) with branch_from_thought + branch_id 2. Need to correct earlier thinking? → Use REVISION (🔄) with is_revision + revises_thought 3. Scope changed? → Adjust total_thoughts up or down as needed 4. Only set next_thought_needed = false when you have a complete, verified solution 💡 BEST PRACTICES: - Start with an initial estimate of total_thoughts, but adjust as you go - Don't hesitate to revise earlier conclusions when new insights emerge - Use branching to explore multiple approaches to the same problem - Express uncertainty when present - Ignore information that is irrelevant to the current step - End with a clear, validated conclusion before setting next_thought_needed = false ✍️ End each thought by asking: "What am I missing or need to reconsider?"`, annotations: { readOnlyHint: true, openWorldHint: false, }, _meta: { schema_version: '1.0.0', server_version: serverVersion, categories: ['reasoning', 'analysis'], recommended_prompt: 'code-reasoning/default', }, }); /* -------------------------------------------------------------------------- */ /* SERVER IMPLEMENTATION */ /* -------------------------------------------------------------------------- */ type ThoughtTracker = { add: (thought: ValidatedThoughtData) => void; ensureBranchIsValid: (branchFromThought?: number) => void; branches: () => string[]; count: () => number; }; interface ServerLogger { debug: (message: string, details?: unknown) => void; info: (message: string, details?: unknown) => void; notice: (message: string, details?: unknown) => void; warn: (message: string, details?: unknown) => void; error: (message: string, details?: unknown) => void; enableRemoteLogging: () => void; } const createServerLogger = (srv: McpServer, debug: boolean): ServerLogger => { let remoteLoggingEnabled = false; const emit = (level: LoggingLevel, message: string, details?: unknown) => { const data = details === undefined ? { message } : { message, details }; if (debug || level !== 'debug') { const prefix = `[${level}]`; if (message.startsWith('\n')) { console.error(message); } else if (details !== undefined) { console.error(`${prefix} ${message}`, details); } else { console.error(`${prefix} ${message}`); } } if (remoteLoggingEnabled) { void srv.sendLoggingMessage({ level, logger: 'code-reasoning', data }).catch(err => { if (debug) { console.error('[error] Failed to send logging notification', err); } }); } }; return { debug: (message, details) => emit('debug', message, details), info: (message, details) => emit('info', message, details), notice: (message, details) => emit('notice', message, details), warn: (message, details) => emit('warning', message, details), error: (message, details) => emit('error', message, details), enableRemoteLogging: () => { remoteLoggingEnabled = true; }, }; }; const createThoughtTracker = (): ThoughtTracker => { const thoughtHistory: ValidatedThoughtData[] = []; const branches = new Map<string, ValidatedThoughtData[]>(); return { add: thought => { thoughtHistory.push(thought); if (thought.branch_id) { const branchThoughts = branches.get(thought.branch_id) ?? []; branchThoughts.push(thought); branches.set(thought.branch_id, branchThoughts); } }, ensureBranchIsValid: branchFromThought => { if (branchFromThought && branchFromThought > thoughtHistory.length) { throw new Error(`Invalid branch_from_thought ${branchFromThought}.`); } }, branches: () => Array.from(branches.keys()), count: () => thoughtHistory.length, }; }; const formatThought = (t: ValidatedThoughtData): string => { const { thought_number, total_thoughts, thought, is_revision, revises_thought, branch_id, branch_from_thought, } = t; const header = is_revision ? `🔄 Revision ${thought_number}/${total_thoughts} (of ${revises_thought})` : branch_id ? `🌿 Branch ${thought_number}/${total_thoughts} (from ${branch_from_thought}, id:${branch_id})` : `💭 Thought ${thought_number}/${total_thoughts}`; const body = thought .split('\n') .map(l => ` ${l}`) .join('\n'); return `\n${header}\n---\n${body}\n---`; }; const getExampleThought = (errorMsg: string): Partial<ThoughtData> => { if (errorMsg.includes('branch')) { return { thought: 'Exploring alternative: Consider algorithm X.', thought_number: 3, total_thoughts: 7, next_thought_needed: true, branch_from_thought: 2, branch_id: 'alternative-algo-x', }; } if (errorMsg.includes('revis')) { return { thought: 'Revisiting earlier point: Assumption Y was flawed.', thought_number: 4, total_thoughts: 6, next_thought_needed: true, is_revision: true, revises_thought: 2, }; } if (errorMsg.includes('length') || errorMsg.includes('Thought cannot be empty')) { return { thought: 'Breaking down the thought into smaller parts...', thought_number: 2, total_thoughts: 5, next_thought_needed: true, }; } return { thought: 'Initial exploration of the problem.', thought_number: 1, total_thoughts: 5, next_thought_needed: true, }; }; const buildSuccess = (t: ValidatedThoughtData, tracker: ThoughtTracker): CallToolResult => { const payload = { status: 'processed', thought_number: t.thought_number, total_thoughts: t.total_thoughts, next_thought_needed: t.next_thought_needed, branches: tracker.branches(), thought_history_length: tracker.count(), } as const; return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }], isError: false }; }; const buildError = (error: Error, debug: boolean, logger: ServerLogger): CallToolResult => { let errorMessage = error.message; let guidance = 'Check the tool description and schema for correct usage.'; const example = getExampleThought(errorMessage); if (error instanceof ZodError) { if (debug) { logger.debug('Zod validation errors', error.errors); } errorMessage = `Validation Error: ${error.errors .map(e => `${e.path.join('.')}: ${e.message}`) .join(', ')}`; const firstPath = error.errors[0]?.path.join('.'); if (firstPath?.includes('thought') && !firstPath.includes('number')) { guidance = `The 'thought' field is empty or invalid. Must be a non-empty string below ${MAX_THOUGHT_LENGTH} characters.`; } else if (firstPath?.includes('thought_number')) { guidance = 'Ensure thought_number is a positive integer and increments correctly.'; } else if (firstPath?.includes('branch')) { guidance = 'When branching, provide both "branch_from_thought" (number) and "branch_id" (string), and do not combine with revision.'; } else if (firstPath?.includes('revision')) { guidance = 'When revising, set is_revision=true and provide revises_thought (positive number). Do not combine with branching.'; } } else if (errorMessage.includes('length')) { guidance = `The thought is too long. Keep it under ${MAX_THOUGHT_LENGTH} characters.`; } else if (errorMessage.includes('Max thought_number exceeded')) { guidance = `The maximum thought limit (${MAX_THOUGHTS}) was reached.`; } const payload = { status: 'failed', error: errorMessage, guidance, example, }; return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }], isError: true }; }; const enforceCrossFieldRules = (data: ThoughtData): ValidatedThoughtData => { if (data.is_revision) { if (typeof data.revises_thought !== 'number' || data.branch_id || data.branch_from_thought) { throw new Error('If is_revision=true, provide revises_thought and omit branch_* fields.'); } } else if (data.revises_thought !== undefined) { throw new Error('revises_thought only allowed when is_revision=true.'); } const hasBranchFields = data.branch_id !== undefined || data.branch_from_thought !== undefined; if (hasBranchFields) { if ( data.branch_id === undefined || data.branch_from_thought === undefined || data.is_revision ) { throw new Error('branch_id and branch_from_thought required together and not with revision.'); } } return data; }; const createThoughtProcessor = (cfg: Readonly<CodeReasoningConfig>, logger: ServerLogger) => { const tracker = createThoughtTracker(); logger.info('Code-Reasoning logic ready', { config: cfg }); return async (input: ParsedThoughtData): Promise<CallToolResult> => { const t0 = performance.now(); try { const parsed = StrictThoughtSchema.parse(input); const data = enforceCrossFieldRules(parsed); if (data.thought_number > MAX_THOUGHTS) { throw new Error(`Max thought_number exceeded (${MAX_THOUGHTS}).`); } tracker.ensureBranchIsValid(data.branch_from_thought); tracker.add(data); logger.info(formatThought(data)); logger.debug('Thought metrics', { num: data.thought_number, elapsedMs: +(performance.now() - t0).toFixed(1), }); return buildSuccess(data, tracker); } catch (err) { const e = err as Error; logger.error('Thought processing failed', { error: e.message, elapsedMs: +(performance.now() - t0).toFixed(1), }); return buildError(e, cfg.debug, logger); } }; }; /* -------------------------------------------------------------------------- */ /* BOOTSTRAP */ /* -------------------------------------------------------------------------- */ export async function runServer(debugFlag = false): Promise<void> { const config = createConfig(debugFlag ? { debug: true } : undefined); const serverMeta = { name: 'code-reasoning-server', version: '0.7.0' } as const; const capabilityOptions: Record<string, unknown> = { logging: {}, completions: {}, }; if (config.promptsEnabled) { capabilityOptions.prompts = {}; } const mcp = new McpServer(serverMeta, { capabilities: capabilityOptions }); const logger = createServerLogger(mcp, config.debug); const processThought = createThoughtProcessor(config, logger); logger.info('Server initialized', { version: serverMeta.version, promptsEnabled: config.promptsEnabled, }); // Register tool with MCP helper APIs const toolDefinition = createCodeReasoningToolDefinition(serverMeta.version); mcp.registerTool( TOOL_NAME, { title: toolDefinition.title, description: toolDefinition.description, annotations: toolDefinition.annotations, _meta: toolDefinition._meta, inputSchema: ThoughtDataInputShape, }, async args => processThought(args) ); // Initialize prompt manager if enabled let promptManager: PromptManager | undefined; if (config.promptsEnabled) { promptManager = new PromptManager(CONFIG_DIR); logger.info('Prompts capability enabled'); // Add prompt handlers mcp.server.setRequestHandler(ListPromptsRequestSchema, async () => { const prompts = promptManager?.getAllPrompts() || []; logger.debug('Returning prompts', { total: prompts.length }); return { prompts }; }); mcp.server.setRequestHandler(GetPromptRequestSchema, async req => { try { if (!promptManager) { throw new Error('Prompt manager not initialized'); } const promptName = req.params.name; const args = req.params.arguments || {}; logger.debug('Getting prompt', { promptName, args }); // Get the prompt result const result = promptManager.applyPrompt(promptName, args); // Return the result in the format expected by MCP return { messages: result.messages, _meta: { prompt_name: promptName, applied_arguments: Object.keys(args), server_version: serverMeta.version, }, }; } catch (err) { const e = err as Error; logger.error('Prompt error', { message: e.message }); return { isError: true, content: [{ type: 'text', text: e.message }], }; } }); // Add handler for completion/complete requests mcp.server.setRequestHandler(CompleteRequestSchema, async req => { try { if (!promptManager) { throw new Error('Prompt manager not initialized'); } // Check if this is a prompt reference if (req.params.ref.type !== 'ref/prompt') { return { completion: { values: [], }, }; } const promptName = req.params.ref.name; const argName = req.params.argument.name; logger.debug('Completing prompt argument', { promptName, argument: argName }); // Get stored values for this prompt using the public method const storedValues = promptManager.getStoredValues(promptName); // Return the stored value for this argument if available if (storedValues[argName]) { return { completion: { values: [storedValues[argName]], }, }; } // Return empty array if no stored value return { completion: { values: [], }, }; } catch (err) { const e = err as Error; logger.error('Completion error', { message: e.message }); return { completion: { values: [], }, }; } }); } else { // Keep the empty handlers if prompts disabled mcp.server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [] })); // Add empty handler for completion requests as well when prompts are disabled mcp.server.setRequestHandler(CompleteRequestSchema, async () => ({ completion: { values: [], }, })); } const transport = new StdioServerTransport(); await mcp.connect(transport); logger.enableRemoteLogging(); logger.notice('🚀 Code-Reasoning MCP Server ready.'); const shutdown = async (signal_name: string, exit_code = 0) => { logger.info('Shutdown signal received', { signal: signal_name, exit_code }); try { await mcp.close(); } catch (close_error) { const error = close_error as Error; logger.error('Error closing MCP server', { message: error.message }); } try { await transport.close(); } catch (close_error) { const error = close_error as Error; logger.error('Error closing transport', { message: error.message }); } process.exit(exit_code); }; ['SIGINT', 'SIGTERM'].forEach(signal_name => { process.on(signal_name, () => { void shutdown(signal_name); }); }); process.on('uncaughtException', uncaught_error => { const error = uncaught_error as Error; logger.error('💥 uncaught exception', { message: error.message, stack: error.stack }); void shutdown('uncaughtException', 1); }); process.on('unhandledRejection', rejection_reason => { logger.error('💥 unhandledPromiseRejection', { reason: rejection_reason }); void shutdown('unhandledRejection', 1); }); } // Self-execute when run directly ------------------------------------------------ if (import.meta.url === `file://${process.argv[1]}`) { runServer(process.argv.includes('--debug')).catch(err => { console.error('FATAL: failed to start', err); process.exit(1); }); }

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/mettamatt/code-reasoning'

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