Chain of Draft (CoD) MCP Server
by stat-guy
#!/usr/bin/env node
/**
* Chain of Draft (CoD) MCP Server in pure JavaScript
* Implements the MCP server for Chain of Draft reasoning
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { Anthropic } from "@anthropic-ai/sdk";
import dotenv from "dotenv";
// Load environment variables
dotenv.config();
// Initialize the Anthropic client
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || "your_api_key_here",
});
// Initialize database connection for analytics
// This is a simplified in-memory version
const analyticsDb = {
records: [],
addRecord: function(record) {
this.records.push({
...record,
timestamp: new Date()
});
return this.records.length;
},
getPerformanceByDomain: function(domain) {
let filtered = domain
? this.records.filter(r => r.domain === domain)
: this.records;
// Group by domain and approach
const grouped = {};
for (const record of filtered) {
const key = `${record.domain}-${record.approach}`;
if (!grouped[key]) {
grouped[key] = {
domain: record.domain,
approach: record.approach,
total_tokens: 0,
total_time: 0,
count: 0,
accuracy: null
};
}
grouped[key].total_tokens += record.tokens_used;
grouped[key].total_time += record.execution_time_ms;
grouped[key].count += 1;
}
// Calculate averages
return Object.values(grouped).map(g => ({
domain: g.domain,
approach: g.approach,
avg_tokens: g.total_tokens / g.count,
avg_time_ms: g.total_time / g.count,
accuracy: g.accuracy,
count: g.count
}));
},
getTokenReductionStats: function() {
// Group by domain
const domains = {};
for (const record of this.records) {
if (!domains[record.domain]) {
domains[record.domain] = {
cod_tokens: [],
cot_tokens: []
};
}
if (record.approach === 'CoD') {
domains[record.domain].cod_tokens.push(record.tokens_used);
} else if (record.approach === 'CoT') {
domains[record.domain].cot_tokens.push(record.tokens_used);
}
}
// Calculate reduction stats
return Object.entries(domains).map(([domain, data]) => {
const cod_avg = data.cod_tokens.length
? data.cod_tokens.reduce((a, b) => a + b, 0) / data.cod_tokens.length
: 0;
const cot_avg = data.cot_tokens.length
? data.cot_tokens.reduce((a, b) => a + b, 0) / data.cot_tokens.length
: 0;
let reduction = 0;
if (cot_avg > 0 && cod_avg > 0) {
reduction = 100 * (1 - (cod_avg / cot_avg));
}
return {
domain,
cod_avg_tokens: cod_avg,
cot_avg_tokens: cot_avg,
reduction_percentage: reduction
};
});
}
};
// Complexity estimation logic
const complexityEstimator = {
domainBaseLimits: {
math: 6,
logic: 5,
common_sense: 4,
physics: 7,
chemistry: 6,
biology: 5,
code: 8,
puzzle: 5,
general: 5
},
complexityIndicators: {
math: ['integral', 'derivative', 'equation', 'theorem', 'calculus', 'polynomial', 'algorithm'],
logic: ['if-then', 'premise', 'fallacy', 'syllogism', 'deduction', 'induction'],
physics: ['velocity', 'acceleration', 'quantum', 'momentum', 'thermodynamics'],
code: ['function', 'algorithm', 'recursive', 'complexity', 'optimization', 'edge case']
},
analyzeProblem: function(problem, domain) {
const wordCount = problem.split(/\s+/).filter(Boolean).length;
const sentences = problem.split(/[.!?]+/).filter(Boolean);
const sentenceCount = sentences.length;
const wordsPerSentence = sentenceCount > 0 ? wordCount / sentenceCount : 0;
// Count indicators
const indicators = this.complexityIndicators[domain] || this.complexityIndicators.general || [];
const lowerProblem = problem.toLowerCase();
const foundIndicators = indicators.filter(i => lowerProblem.includes(i.toLowerCase()));
// Count questions
const questionCount = (problem.match(/\?/g) || []).length;
// Estimate complexity
let complexity = this.domainBaseLimits[domain] || this.domainBaseLimits.general;
// Adjust for length
if (wordCount > 100) complexity += 2;
else if (wordCount > 50) complexity += 1;
// Adjust for sentences
if (wordsPerSentence > 20) complexity += 1;
// Adjust for indicators
complexity += Math.min(3, foundIndicators.length);
// Adjust for questions
complexity += Math.min(2, questionCount);
return {
word_count: wordCount,
sentence_count: sentenceCount,
words_per_sentence: wordsPerSentence,
indicator_count: foundIndicators.length,
found_indicators: foundIndicators,
question_count: questionCount,
estimated_complexity: complexity
};
}
};
// Format enforcement
const formatEnforcer = {
enforceWordLimit: function(reasoning, maxWordsPerStep) {
if (!maxWordsPerStep) return reasoning;
// Split into steps
const stepPattern = /(\d+\.\s*|Step\s+\d+:|\n-\s+|\n\*\s+|•\s+|^\s*-\s+|^\s*\*\s+)/;
let steps = reasoning.split(stepPattern).filter(Boolean);
// Process steps
const processed = [];
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
// Check if this is a step marker
if (step.match(stepPattern)) {
processed.push(step);
continue;
}
// Enforce word limit on this step
const words = step.split(/\s+/).filter(Boolean);
if (words.length <= maxWordsPerStep) {
processed.push(step);
} else {
// Truncate to word limit
processed.push(words.slice(0, maxWordsPerStep).join(' '));
}
}
return processed.join('');
},
analyzeAdherence: function(reasoning, maxWordsPerStep) {
// Split into steps
const stepPattern = /(\d+\.\s*|Step\s+\d+:|\n-\s+|\n\*\s+|•\s+|^\s*-\s+|^\s*\*\s+)/;
let steps = reasoning.split(stepPattern).filter(Boolean);
let totalWords = 0;
let stepCount = 0;
let overLimitSteps = 0;
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
// Skip step markers
if (step.match(stepPattern)) continue;
stepCount++;
const wordCount = step.split(/\s+/).filter(Boolean).length;
totalWords += wordCount;
if (wordCount > maxWordsPerStep) {
overLimitSteps++;
}
}
return {
step_count: stepCount,
total_words: totalWords,
avg_words_per_step: stepCount > 0 ? totalWords / stepCount : 0,
over_limit_steps: overLimitSteps,
adherence_percentage: stepCount > 0 ? (1 - (overLimitSteps / stepCount)) * 100 : 100
};
}
};
// Reasoning approach selector
const reasoningSelector = {
defaultPreferences: {
math: {
complexity_threshold: 7,
accuracy_threshold: 0.85
},
code: {
complexity_threshold: 8,
accuracy_threshold: 0.9
},
general: {
complexity_threshold: 6,
accuracy_threshold: 0.8
}
},
selectApproach: function(domain, complexity, performanceStats) {
// Default preferences for domain
const prefs = this.defaultPreferences[domain] || this.defaultPreferences.general;
// By default, use CoD for simpler problems
if (complexity < prefs.complexity_threshold) {
return 'CoD';
}
// For complex problems, check past performance if available
if (performanceStats && performanceStats.length > 0) {
const domainStats = performanceStats.filter(s => s.domain === domain);
// Check if CoD has good enough accuracy
const codStats = domainStats.find(s => s.approach === 'CoD');
if (codStats && codStats.accuracy && codStats.accuracy >= prefs.accuracy_threshold) {
return 'CoD';
}
// Otherwise, use CoT for complex problems
return 'CoT';
}
// Use CoT for complex problems by default
return complexity >= prefs.complexity_threshold ? 'CoT' : 'CoD';
}
};
// Prompt creation
function createCodPrompt(problem, domain, examples = [], wordLimit = 5) {
return `
You will solve this ${domain} problem using the Chain of Draft technique, which uses very concise reasoning steps.
For each step, use at most ${wordLimit} words. Be extremely concise but clear.
PROBLEM:
${problem}
To solve this, create short reasoning steps, with each step using at most ${wordLimit} words.
After your reasoning, state your final answer clearly.
`.trim();
}
function createCotPrompt(problem, domain, examples = []) {
return `
You will solve this ${domain} problem using detailed step-by-step reasoning.
PROBLEM:
${problem}
Think through this problem step-by-step with clear reasoning.
After your reasoning, state your final answer clearly.
`.trim();
}
// Chain of Draft client implementation
const chainOfDraftClient = {
async solveWithReasoning(params) {
const {
problem,
domain = 'general',
max_words_per_step = null,
approach = null,
enforce_format = true,
adaptive_word_limit = true
} = params;
const startTime = Date.now();
// Analyze problem complexity
const analysis = complexityEstimator.analyzeProblem(problem, domain);
const complexity = analysis.estimated_complexity;
// Determine word limit
let wordLimit = max_words_per_step;
if (!wordLimit && adaptive_word_limit) {
wordLimit = complexity;
} else if (!wordLimit) {
// Default based on domain
wordLimit = complexityEstimator.domainBaseLimits[domain] || 5;
}
// Determine approach (CoD or CoT)
const performanceStats = analyticsDb.getPerformanceByDomain(domain);
const selectedApproach = approach ||
reasoningSelector.selectApproach(domain, complexity, performanceStats);
// Create prompt based on approach
const prompt = selectedApproach === 'CoD'
? createCodPrompt(problem, domain, [], wordLimit)
: createCotPrompt(problem, domain, []);
// Call Claude
const response = await anthropic.messages.create({
model: 'claude-3-sonnet-20240229',
max_tokens: 1000,
messages: [
{ role: 'user', content: prompt }
]
});
// Extract reasoning and answer
const fullText = response.content[0].text;
// Extract final answer (assuming it comes after the reasoning, often starts with "Answer:" or similar)
let reasoningSteps = fullText;
let finalAnswer = '';
// Common patterns for final answer sections
const answerPatterns = [
/(?:Final Answer|Answer|Therefore):?\s*(.*?)$/is,
/(?:In conclusion|To conclude|Thus|Hence|So),\s*(.*?)$/is,
/(?:The answer is|The result is|The solution is)\s*(.*?)$/is
];
// Try to extract the final answer with each pattern
for (const pattern of answerPatterns) {
const match = fullText.match(pattern);
if (match && match[1]) {
finalAnswer = match[1].trim();
reasoningSteps = fullText.substring(0, fullText.indexOf(match[0])).trim();
break;
}
}
// If no pattern matched, just use the last sentence
if (!finalAnswer) {
const sentences = fullText.split(/[.!?]+\s+/);
if (sentences.length > 1) {
finalAnswer = sentences.pop().trim();
reasoningSteps = sentences.join('. ') + '.';
}
}
// Apply format enforcement if needed
if (enforce_format && selectedApproach === 'CoD') {
reasoningSteps = formatEnforcer.enforceWordLimit(reasoningSteps, wordLimit);
}
// Calculate execution time
const executionTime = Date.now() - startTime;
// Estimate token count (rough approximation)
const tokenCount = Math.ceil(fullText.length / 4);
// Record analytics
analyticsDb.addRecord({
problem_id: problem.substring(0, 20),
problem_text: problem,
domain,
approach: selectedApproach,
word_limit: wordLimit,
tokens_used: tokenCount,
execution_time_ms: executionTime,
reasoning_steps: reasoningSteps,
answer: finalAnswer
});
// Return result
return {
approach: selectedApproach,
reasoning_steps: reasoningSteps,
final_answer: finalAnswer,
token_count: tokenCount,
word_limit: wordLimit,
complexity: complexity,
execution_time_ms: executionTime
};
}
};
// Define the Chain of Draft tool
const CHAIN_OF_DRAFT_TOOL = {
name: "chain_of_draft_solve",
description: "Solve a reasoning problem using Chain of Draft approach",
inputSchema: {
type: "object",
properties: {
problem: {
type: "string",
description: "The problem to solve"
},
domain: {
type: "string",
description: "Domain for context (math, logic, code, common-sense, etc.)"
},
max_words_per_step: {
type: "number",
description: "Maximum words per reasoning step"
},
approach: {
type: "string",
description: "Force 'CoD' or 'CoT' approach"
},
enforce_format: {
type: "boolean",
description: "Whether to enforce the word limit"
},
adaptive_word_limit: {
type: "boolean",
description: "Adjust word limits based on complexity"
}
},
required: ["problem"]
}
};
const MATH_TOOL = {
name: "math_solve",
description: "Solve a math problem using Chain of Draft reasoning",
inputSchema: {
type: "object",
properties: {
problem: {
type: "string",
description: "The math problem to solve"
},
approach: {
type: "string",
description: "Force 'CoD' or 'CoT' approach"
},
max_words_per_step: {
type: "number",
description: "Maximum words per reasoning step"
}
},
required: ["problem"]
}
};
const CODE_TOOL = {
name: "code_solve",
description: "Solve a coding problem using Chain of Draft reasoning",
inputSchema: {
type: "object",
properties: {
problem: {
type: "string",
description: "The coding problem to solve"
},
approach: {
type: "string",
description: "Force 'CoD' or 'CoT' approach"
},
max_words_per_step: {
type: "number",
description: "Maximum words per reasoning step"
}
},
required: ["problem"]
}
};
const LOGIC_TOOL = {
name: "logic_solve",
description: "Solve a logic problem using Chain of Draft reasoning",
inputSchema: {
type: "object",
properties: {
problem: {
type: "string",
description: "The logic problem to solve"
},
approach: {
type: "string",
description: "Force 'CoD' or 'CoT' approach"
},
max_words_per_step: {
type: "number",
description: "Maximum words per reasoning step"
}
},
required: ["problem"]
}
};
const PERFORMANCE_TOOL = {
name: "get_performance_stats",
description: "Get performance statistics for CoD vs CoT approaches",
inputSchema: {
type: "object",
properties: {
domain: {
type: "string",
description: "Filter for specific domain"
}
}
}
};
const TOKEN_TOOL = {
name: "get_token_reduction",
description: "Get token reduction statistics for CoD vs CoT",
inputSchema: {
type: "object",
properties: {}
}
};
const COMPLEXITY_TOOL = {
name: "analyze_problem_complexity",
description: "Analyze the complexity of a problem",
inputSchema: {
type: "object",
properties: {
problem: {
type: "string",
description: "The problem to analyze"
},
domain: {
type: "string",
description: "Problem domain"
}
},
required: ["problem"]
}
};
// Initialize the MCP server
const server = new Server({
name: "chain-of-draft",
version: "1.0.0"
}, {
capabilities: {
tools: {},
},
});
// Expose available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
CHAIN_OF_DRAFT_TOOL,
MATH_TOOL,
CODE_TOOL,
LOGIC_TOOL,
PERFORMANCE_TOOL,
TOKEN_TOOL,
COMPLEXITY_TOOL
],
}));
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
// Chain of Draft solve
if (name === "chain_of_draft_solve") {
const result = await chainOfDraftClient.solveWithReasoning(args);
const formattedResponse =
`Chain of ${result.approach} reasoning (${result.word_limit} word limit):\n\n` +
`${result.reasoning_steps}\n\n` +
`Final answer: ${result.final_answer}\n\n` +
`Stats: ${result.token_count} tokens, ${result.execution_time_ms.toFixed(0)}ms, ` +
`complexity score: ${result.complexity}`;
return {
content: [{
type: "text",
text: formattedResponse
}]
};
}
// Math solver
if (name === "math_solve") {
const result = await chainOfDraftClient.solveWithReasoning({
...args,
domain: "math"
});
const formattedResponse =
`Chain of ${result.approach} reasoning (${result.word_limit} word limit):\n\n` +
`${result.reasoning_steps}\n\n` +
`Final answer: ${result.final_answer}\n\n` +
`Stats: ${result.token_count} tokens, ${result.execution_time_ms.toFixed(0)}ms, ` +
`complexity score: ${result.complexity}`;
return {
content: [{
type: "text",
text: formattedResponse
}]
};
}
// Code solver
if (name === "code_solve") {
const result = await chainOfDraftClient.solveWithReasoning({
...args,
domain: "code"
});
const formattedResponse =
`Chain of ${result.approach} reasoning (${result.word_limit} word limit):\n\n` +
`${result.reasoning_steps}\n\n` +
`Final answer: ${result.final_answer}\n\n` +
`Stats: ${result.token_count} tokens, ${result.execution_time_ms.toFixed(0)}ms, ` +
`complexity score: ${result.complexity}`;
return {
content: [{
type: "text",
text: formattedResponse
}]
};
}
// Logic solver
if (name === "logic_solve") {
const result = await chainOfDraftClient.solveWithReasoning({
...args,
domain: "logic"
});
const formattedResponse =
`Chain of ${result.approach} reasoning (${result.word_limit} word limit):\n\n` +
`${result.reasoning_steps}\n\n` +
`Final answer: ${result.final_answer}\n\n` +
`Stats: ${result.token_count} tokens, ${result.execution_time_ms.toFixed(0)}ms, ` +
`complexity score: ${result.complexity}`;
return {
content: [{
type: "text",
text: formattedResponse
}]
};
}
// Performance stats
if (name === "get_performance_stats") {
const stats = analyticsDb.getPerformanceByDomain(args.domain);
let result = "Performance Comparison (CoD vs CoT):\n\n";
if (!stats || stats.length === 0) {
result = "No performance data available yet.";
} else {
for (const stat of stats) {
result += `Domain: ${stat.domain}\n`;
result += `Approach: ${stat.approach}\n`;
result += `Average tokens: ${stat.avg_tokens.toFixed(1)}\n`;
result += `Average time: ${stat.avg_time_ms.toFixed(1)}ms\n`;
if (stat.accuracy !== null) {
result += `Accuracy: ${(stat.accuracy * 100).toFixed(1)}%\n`;
}
result += `Sample size: ${stat.count}\n\n`;
}
}
return {
content: [{
type: "text",
text: result
}]
};
}
// Token reduction
if (name === "get_token_reduction") {
const stats = analyticsDb.getTokenReductionStats();
let result = "Token Reduction Analysis:\n\n";
if (!stats || stats.length === 0) {
result = "No reduction data available yet.";
} else {
for (const stat of stats) {
result += `Domain: ${stat.domain}\n`;
result += `CoD avg tokens: ${stat.cod_avg_tokens.toFixed(1)}\n`;
result += `CoT avg tokens: ${stat.cot_avg_tokens.toFixed(1)}\n`;
result += `Reduction: ${stat.reduction_percentage.toFixed(1)}%\n\n`;
}
}
return {
content: [{
type: "text",
text: result
}]
};
}
// Complexity analysis
if (name === "analyze_problem_complexity") {
const analysis = complexityEstimator.analyzeProblem(args.problem, args.domain || "general");
let result = `Complexity Analysis for ${args.domain || "general"} problem:\n\n`;
result += `Word count: ${analysis.word_count}\n`;
result += `Sentence count: ${analysis.sentence_count}\n`;
result += `Words per sentence: ${analysis.words_per_sentence.toFixed(1)}\n`;
result += `Complexity indicators found: ${analysis.indicator_count}\n`;
if (analysis.found_indicators && analysis.found_indicators.length > 0) {
result += `Indicators: ${analysis.found_indicators.join(", ")}\n`;
}
result += `Question count: ${analysis.question_count}\n`;
result += `\nEstimated complexity score: ${analysis.estimated_complexity}\n`;
result += `Recommended word limit per step: ${analysis.estimated_complexity}\n`;
return {
content: [{
type: "text",
text: result
}]
};
}
// Handle unknown tool
return {
content: [{
type: "text",
text: `Unknown tool: ${name}`
}],
isError: true
};
} catch (error) {
console.error("Error executing tool:", error);
return {
content: [{
type: "text",
text: `Error executing tool ${name}: ${error.message}`
}],
isError: true
};
}
});
// Start the server
async function runServer() {
const transport = new StdioServerTransport();
console.error("Chain of Draft MCP Server starting...");
try {
// Connect to the transport
await server.connect(transport);
console.error("Server connected to transport");
} catch (error) {
console.error("Error starting server:", error);
process.exit(1);
}
}
// Run the server
runServer().catch(error => {
console.error("Fatal error:", error);
process.exit(1);
});