175 lines
6.9 KiB
JavaScript
175 lines
6.9 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
import * as http from "http";
|
||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||
import { z } from "zod";
|
||
import { SequentialThinkingServer } from "./lib.js";
|
||
|
||
const server = new McpServer({
|
||
name: "sequential-thinking-server",
|
||
version: "0.2.0",
|
||
});
|
||
|
||
const thinkingServer = new SequentialThinkingServer();
|
||
|
||
server.registerTool(
|
||
"sequentialthinking",
|
||
{
|
||
title: "Sequential Thinking",
|
||
description: `A detailed tool for dynamic and reflective problem-solving through thoughts.
|
||
This tool helps 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.
|
||
|
||
When to use this tool:
|
||
- Breaking down complex problems into steps
|
||
- Planning and design with room for revision
|
||
- Analysis that might need course correction
|
||
- Problems where the full scope might not be clear initially
|
||
- Problems that require a multi-step solution
|
||
- Tasks that need to maintain context over multiple steps
|
||
- Situations where irrelevant information needs to be filtered out
|
||
|
||
Key features:
|
||
- You can adjust total_thoughts up or down as you progress
|
||
- You can question or revise previous thoughts
|
||
- You can add more thoughts even after reaching what seemed like the end
|
||
- You can express uncertainty and explore alternative approaches
|
||
- Not every thought needs to build linearly - you can branch or backtrack
|
||
- Generates a solution hypothesis
|
||
- Verifies the hypothesis based on the Chain of Thought steps
|
||
- Repeats the process until satisfied
|
||
- Provides a correct answer
|
||
|
||
Parameters explained:
|
||
- thought: Your current thinking step, which can include:
|
||
* Regular analytical steps
|
||
* Revisions of previous thoughts
|
||
* Questions about previous decisions
|
||
* Realizations about needing more analysis
|
||
* Changes in approach
|
||
* Hypothesis generation
|
||
* Hypothesis verification
|
||
- nextThoughtNeeded: True if you need more thinking, even if at what seemed like the end
|
||
- thoughtNumber: Current number in sequence (can go beyond initial total if needed)
|
||
- totalThoughts: Current estimate of thoughts needed (can be adjusted up/down)
|
||
- isRevision: A boolean indicating if this thought revises previous thinking
|
||
- revisesThought: If is_revision is true, which thought number is being reconsidered
|
||
- branchFromThought: If branching, which thought number is the branching point
|
||
- branchId: Identifier for the current branch (if any)
|
||
- needsMoreThoughts: If reaching end but realizing more thoughts needed
|
||
|
||
You should:
|
||
1. Start with an initial estimate of needed thoughts, but be ready to adjust
|
||
2. Feel free to question or revise previous thoughts
|
||
3. Don't hesitate to add more thoughts if needed, even at the "end"
|
||
4. Express uncertainty when present
|
||
5. Mark thoughts that revise previous thinking or branch into new paths
|
||
6. Ignore information that is irrelevant to the current step
|
||
7. Generate a solution hypothesis when appropriate
|
||
8. Verify the hypothesis based on the Chain of Thought steps
|
||
9. Repeat the process until satisfied with the solution
|
||
10. Provide a single, ideally correct answer as the final output
|
||
11. Only set nextThoughtNeeded to false when truly done and a satisfactory answer is reached`,
|
||
inputSchema: {
|
||
thought: z.string().describe("Your current thinking step"),
|
||
nextThoughtNeeded: z.boolean().describe("Whether another thought step is needed"),
|
||
thoughtNumber: z.number().int().min(1).describe("Current thought number (numeric value, e.g., 1, 2, 3)"),
|
||
totalThoughts: z.number().int().min(1).describe("Estimated total thoughts needed (numeric value, e.g., 5, 10)"),
|
||
isRevision: z.boolean().optional().describe("Whether this revises previous thinking"),
|
||
revisesThought: z.number().int().min(1).optional().describe("Which thought is being reconsidered"),
|
||
branchFromThought: z.number().int().min(1).optional().describe("Branching point thought number"),
|
||
branchId: z.string().optional().describe("Branch identifier"),
|
||
needsMoreThoughts: z.boolean().optional().describe("If more thoughts are needed")
|
||
},
|
||
outputSchema: {
|
||
thoughtNumber: z.number(),
|
||
totalThoughts: z.number(),
|
||
nextThoughtNeeded: z.boolean(),
|
||
branches: z.array(z.string()),
|
||
thoughtHistoryLength: z.number()
|
||
},
|
||
},
|
||
async (args) => {
|
||
const result = thinkingServer.processThought(args);
|
||
|
||
if (result.isError) {
|
||
return result;
|
||
}
|
||
|
||
// Parse the JSON response to get structured content
|
||
const parsedContent = JSON.parse(result.content[0].text);
|
||
|
||
return {
|
||
content: result.content,
|
||
structuredContent: parsedContent
|
||
};
|
||
}
|
||
);
|
||
|
||
const sseTransportsBySessionId = new Map<string, SSEServerTransport>();
|
||
|
||
function runServer() {
|
||
const port = Number(process.env.MCP_PORT ?? process.env.SSE_PORT ?? 3000);
|
||
|
||
const httpServer = http.createServer(async (req, res) => {
|
||
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
||
const pathname = url.pathname;
|
||
|
||
if (req.method === "GET" && (pathname === "/sse" || pathname === "/")) {
|
||
try {
|
||
const transport = new SSEServerTransport("/messages", res);
|
||
sseTransportsBySessionId.set(transport.sessionId, transport);
|
||
transport.onclose = () => {
|
||
sseTransportsBySessionId.delete(transport.sessionId);
|
||
};
|
||
const heartbeatInterval = setInterval(() => {
|
||
try {
|
||
if (!res.writableEnded) {
|
||
res.write(': heartbeat\n\n');
|
||
} else {
|
||
clearInterval(heartbeatInterval);
|
||
}
|
||
} catch {
|
||
clearInterval(heartbeatInterval);
|
||
}
|
||
}, 15_000);
|
||
res.on('close', () => clearInterval(heartbeatInterval));
|
||
await server.connect(transport);
|
||
console.error("Sequential Thinking MCP Server: new SSE client connected");
|
||
} catch (error) {
|
||
console.error("SSE connection error:", error);
|
||
if (!res.headersSent) {
|
||
res.writeHead(500).end("Internal server error");
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (req.method === "POST" && pathname === "/messages") {
|
||
const sessionId = url.searchParams.get("sessionId");
|
||
if (!sessionId) {
|
||
res.writeHead(400).end("Missing sessionId query parameter");
|
||
return;
|
||
}
|
||
const transport = sseTransportsBySessionId.get(sessionId);
|
||
if (!transport) {
|
||
res.writeHead(404).end("Unknown session");
|
||
return;
|
||
}
|
||
await transport.handlePostMessage(req, res);
|
||
return;
|
||
}
|
||
|
||
res.writeHead(404).end("Not found");
|
||
});
|
||
|
||
httpServer.listen(port, () => {
|
||
console.error(`Sequential Thinking MCP Server running on SSE at http://localhost:${port}`);
|
||
console.error(" GET /sse – open SSE stream (then POST to /messages?sessionId=...)");
|
||
console.error(" POST /messages?sessionId=<id> – send MCP messages");
|
||
});
|
||
}
|
||
|
||
runServer();
|