mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-02-19 11:54:58 -05:00
* fix(sequential-thinking): convert to modern TypeScript SDK APIs Convert the sequential-thinking server to use the modern McpServer API instead of the low-level Server API. Key changes: - Replace Server with McpServer from @modelcontextprotocol/sdk/server/mcp.js - Use registerTool() method instead of manual request handlers - Use Zod schemas directly in inputSchema/outputSchema - Add structuredContent to tool responses - Fix type literals to use 'as const' assertions The modern API provides: - Less boilerplate code - Better type safety with Zod - More declarative tool registration - Cleaner, more maintainable code * fix: exclude test files from TypeScript build Add exclude for test files and vitest.config.ts to tsconfig * refactor: remove redundant validation now handled by Zod Zod schema already validates all required fields and types. Removed validateThoughtData() method and kept only business logic validation (adjusting totalThoughts if needed). * fix(sequentialthinking): add Zod validation to processThought method The modern API migration removed manual validation from processThought(), but tests call this method directly, bypassing the Zod validation in the tool registration layer. This commit adds Zod validation directly in the processThought() method to ensure validation works both when called via MCP and when called directly (e.g., in tests). Also improves error message formatting to match the expected error messages in the tests. * refactor: simplify by removing redundant validation Since processThought() is only called through the tool registration in production, validation always happens via Zod schemas at that layer. Removed redundant validation logic from processThought() and updated tests to reflect this architectural decision. Changes: - Remove Zod validation from processThought() method - Accept ThoughtData type instead of unknown - Remove 10 validation tests that are now handled at tool registration - Add comment explaining validation approach
100 lines
2.9 KiB
TypeScript
100 lines
2.9 KiB
TypeScript
import chalk from 'chalk';
|
|
|
|
export interface ThoughtData {
|
|
thought: string;
|
|
thoughtNumber: number;
|
|
totalThoughts: number;
|
|
isRevision?: boolean;
|
|
revisesThought?: number;
|
|
branchFromThought?: number;
|
|
branchId?: string;
|
|
needsMoreThoughts?: boolean;
|
|
nextThoughtNeeded: boolean;
|
|
}
|
|
|
|
export class SequentialThinkingServer {
|
|
private thoughtHistory: ThoughtData[] = [];
|
|
private branches: Record<string, ThoughtData[]> = {};
|
|
private disableThoughtLogging: boolean;
|
|
|
|
constructor() {
|
|
this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
|
|
}
|
|
|
|
private formatThought(thoughtData: ThoughtData): string {
|
|
const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId } = thoughtData;
|
|
|
|
let prefix = '';
|
|
let context = '';
|
|
|
|
if (isRevision) {
|
|
prefix = chalk.yellow('🔄 Revision');
|
|
context = ` (revising thought ${revisesThought})`;
|
|
} else if (branchFromThought) {
|
|
prefix = chalk.green('🌿 Branch');
|
|
context = ` (from thought ${branchFromThought}, ID: ${branchId})`;
|
|
} else {
|
|
prefix = chalk.blue('💭 Thought');
|
|
context = '';
|
|
}
|
|
|
|
const header = `${prefix} ${thoughtNumber}/${totalThoughts}${context}`;
|
|
const border = '─'.repeat(Math.max(header.length, thought.length) + 4);
|
|
|
|
return `
|
|
┌${border}┐
|
|
│ ${header} │
|
|
├${border}┤
|
|
│ ${thought.padEnd(border.length - 2)} │
|
|
└${border}┘`;
|
|
}
|
|
|
|
public processThought(input: ThoughtData): { content: Array<{ type: "text"; text: string }>; isError?: boolean } {
|
|
try {
|
|
// Validation happens at the tool registration layer via Zod
|
|
// Adjust totalThoughts if thoughtNumber exceeds it
|
|
if (input.thoughtNumber > input.totalThoughts) {
|
|
input.totalThoughts = input.thoughtNumber;
|
|
}
|
|
|
|
this.thoughtHistory.push(input);
|
|
|
|
if (input.branchFromThought && input.branchId) {
|
|
if (!this.branches[input.branchId]) {
|
|
this.branches[input.branchId] = [];
|
|
}
|
|
this.branches[input.branchId].push(input);
|
|
}
|
|
|
|
if (!this.disableThoughtLogging) {
|
|
const formattedThought = this.formatThought(input);
|
|
console.error(formattedThought);
|
|
}
|
|
|
|
return {
|
|
content: [{
|
|
type: "text" as const,
|
|
text: JSON.stringify({
|
|
thoughtNumber: input.thoughtNumber,
|
|
totalThoughts: input.totalThoughts,
|
|
nextThoughtNeeded: input.nextThoughtNeeded,
|
|
branches: Object.keys(this.branches),
|
|
thoughtHistoryLength: this.thoughtHistory.length
|
|
}, null, 2)
|
|
}]
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
content: [{
|
|
type: "text" as const,
|
|
text: JSON.stringify({
|
|
error: error instanceof Error ? error.message : String(error),
|
|
status: 'failed'
|
|
}, null, 2)
|
|
}],
|
|
isError: true
|
|
};
|
|
}
|
|
}
|
|
}
|