#
tokens: 47566/50000 34/35 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/brendancopley/mcp-chain-of-draft-prompt-tool?lines=true&page={x} to view the full context.

# Directory Structure

```
├── __init__.py
├── .env.example
├── .gitignore
├── .npmrc
├── claude_desktop_config.example.json
├── commit.sh
├── config.json
├── Dockerfile
├── docs
│   ├── CLAUDE.md
│   └── mcp-documentation.md
├── nx.json
├── package-lock.json
├── package.json
├── project.json
├── promptsConfig.json
├── push.sh
├── README.md
├── RELEASE_NOTES.md
├── requirements.txt
├── sea-config.json
├── smithery.yaml
├── src
│   ├── index.ts
│   ├── python
│   │   ├── analytics.py
│   │   ├── client.py
│   │   ├── complexity.py
│   │   ├── examples.py
│   │   ├── format.py
│   │   ├── reasoning.py
│   │   └── server.py
│   ├── server.ts
│   ├── test-query.ts
│   ├── types.ts
│   └── utils
│       └── logger.ts
├── system_template.md
├── test.py
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------

```
1 | registry="https://registry.npmjs.org/"
2 | @mcp-chain-of-draft-prompt-tool:registry="https://registry.npmjs.org/"
3 | 
4 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Environment variables and configuration
 2 | .env
 3 | claude_desktop_config.json
 4 | 
 5 | # Python artifacts
 6 | __pycache__/
 7 | *.py[cod]
 8 | *$py.class
 9 | *.so
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | 
27 | # Virtual environment
28 | venv/
29 | .venv/
30 | ENV/
31 | 
32 | # Database files
33 | *.db
34 | *.sqlite
35 | *.sqlite3
36 | 
37 | # Logs
38 | *.log
39 | 
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 | node_modules/*
44 | dist/*
45 | 
46 | # Binary builds
47 | binaries/
48 | *.exe
49 | 
50 | # Nx specific
51 | .nx/
52 | .nxignore
53 | .nx-cache/
54 | 
55 | # VSCode specific
56 | .vscode/*
57 | !.vscode/settings.json
58 | !.vscode/tasks.json
59 | !.vscode/launch.json
60 | !.vscode/extensions.json
61 | *.code-workspace
62 | .history/
63 | 
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
 1 | # LLM Provider Configuration
 2 | # Choose which provider to use: 'anthropic', 'openai', 'mistral', or 'ollama'
 3 | LLM_PROVIDER=anthropic
 4 | 
 5 | # Default model to use (provider-specific)
 6 | # Anthropic: claude-3-7-sonnet-latest, claude-3-opus-20240229
 7 | # OpenAI: gpt-4-turbo-preview, gpt-4, gpt-3.5-turbo
 8 | # Mistral: mistral-tiny, mistral-small, mistral-medium
 9 | # Ollama: llama2, mistral, codellama
10 | LLM_MODEL=claude-3-7-sonnet-latest
11 | 
12 | # Anthropic Configuration
13 | ANTHROPIC_API_KEY=your_anthropic_api_key_here
14 | ANTHROPIC_BASE_URL=https://api.anthropic.com
15 | 
16 | # OpenAI Configuration
17 | OPENAI_API_KEY=your_openai_api_key_here
18 | OPENAI_BASE_URL=https://api.openai.com
19 | 
20 | # Mistral Configuration
21 | MISTRAL_API_KEY=your_mistral_api_key_here
22 | 
23 | # Ollama Configuration (local deployment)
24 | OLLAMA_BASE_URL=http://localhost:11434
25 | 
26 | # Chain of Draft Settings
27 | # These are optional and will use defaults if not set
28 | # MAX_WORDS_PER_STEP=5
29 | # ENFORCE_FORMAT=true
30 | # ADAPTIVE_WORD_LIMIT=true
31 | 
32 | # Database Settings
33 | COD_DB_URL=sqlite:///cod_analytics.db
34 | COD_EXAMPLES_DB=cod_examples.db
35 | 
36 | # Default Settings
37 | COD_DEFAULT_MODEL=claude-3-7-sonnet-latest
38 | COD_MAX_TOKENS=500
39 | COD_MAX_WORDS_PER_STEP=5
40 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/brendancopley-mcp-chain-of-draft-prompt-tool-badge.png)](https://mseep.ai/app/brendancopley-mcp-chain-of-draft-prompt-tool)
  2 | 
  3 | # MCP Chain of Draft (CoD) Prompt Tool
  4 | 
  5 | [![smithery badge](https://smithery.ai/badge/@brendancopley/mcp-chain-of-draft-prompt-tool)](https://smithery.ai/server/@brendancopley/mcp-chain-of-draft-prompt-tool)
  6 | [![version](https://img.shields.io/npm/v/mcp-chain-of-draft-prompt-tool.svg?style=flat-square)](https://npmjs.org/mcp-chain-of-draft-prompt-tool)
  7 | [![package size](https://packagephobia.com/badge?p=mcp-chain-of-draft-prompt-tool)](https://packagephobia.com/result?p=mcp-chain-of-draft-prompt-tool)
  8 | [![license](https://img.shields.io/npm/l/mcp-chain-of-draft-prompt-tool?color=%23007a1f&style=flat-square)](https://github.com/brendancopley/mcp-chain-of-draft-prompt-tool/blob/master/LICENSE)
  9 | [![stargazers](https://img.shields.io/github/stars/brendancopley/mcp-chain-of-draft-prompt-tool?style=social)](https://github.com/brendancopley/mcp-chain-of-draft-prompt-tool/stargazers)
 10 | [![number of forks](https://img.shields.io/github/forks/brendancopley/mcp-chain-of-draft-prompt-tool?style=social)](https://github.com/brendancopley/mcp-chain-of-draft-prompt-tool/fork)
 11 | [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/c7233b8a-33e4-492d-8840-e3bddcf48aa3)
 12 | 
 13 | ## Overview
 14 | 
 15 | The MCP Chain of Draft (CoD) Prompt Tool is a powerful Model Context Protocol tool that enhances LLM reasoning by transforming standard prompts into either Chain of Draft (CoD) or Chain of Thought (CoT) format. Here's how it works:
 16 | 
 17 | 1. **Input Transformation**: Your regular prompt is automatically transformed into a CoD/CoT format
 18 | 2. **LLM Processing**: The transformed prompt is passed to your chosen LLM (Claude, GPT, Ollama, or local models)
 19 | 3. **Enhanced Reasoning**: The LLM processes the request using structured reasoning steps
 20 | 4. **Result Transformation**: The response is transformed back into a clear, concise format
 21 | 
 22 | This approach significantly improves reasoning quality while reducing token usage and maintaining high accuracy.
 23 | 
 24 | ## BYOLLM Support
 25 | 
 26 | This tool supports a "Bring Your Own LLM" approach, allowing you to use any language model of your choice:
 27 | 
 28 | ### Supported LLM Integrations
 29 | - **Cloud Services**
 30 |   - Anthropic Claude
 31 |   - OpenAI GPT models
 32 |   - Mistral AI
 33 | - **Local Models**
 34 |   - Ollama (all models)
 35 |   - Local LLama variants
 36 |   - Any model supporting chat completion API
 37 | 
 38 | ### Configuring Your LLM
 39 | 
 40 | 1. **Cloud Services**
 41 |    ```bash
 42 |    # For Anthropic Claude
 43 |    export ANTHROPIC_API_KEY=your_key_here
 44 |    
 45 |    # For OpenAI
 46 |    export OPENAI_API_KEY=your_key_here
 47 |    
 48 |    # For Mistral AI
 49 |    export MISTRAL_API_KEY=your_key_here
 50 |    ```
 51 | 
 52 | 2. **Local Models with Ollama**
 53 |    ```bash
 54 |    # First install Ollama
 55 |    curl https://ollama.ai/install.sh | sh
 56 |    
 57 |    # Pull your preferred model
 58 |    ollama pull llama2
 59 |    # or
 60 |    ollama pull mistral
 61 |    # or any other model
 62 |    
 63 |    # Configure the tool to use Ollama
 64 |    export MCP_LLM_PROVIDER=ollama
 65 |    export MCP_OLLAMA_MODEL=llama2  # or your chosen model
 66 |    ```
 67 | 
 68 | 3. **Custom Local Models**
 69 |    ```bash
 70 |    # Point to your local model API
 71 |    export MCP_LLM_PROVIDER=custom
 72 |    export MCP_CUSTOM_LLM_ENDPOINT=http://localhost:your_port
 73 |    ```
 74 | 
 75 | ## Credits
 76 | 
 77 | This project implements the Chain of Draft (CoD) reasoning approach as a Model Context Protocol (MCP) prompt tool for Claude. The core Chain of Draft implementation is based on the work by [stat-guy](https://github.com/stat-guy/chain-of-draft). We extend our gratitude for their pioneering work in developing this efficient reasoning approach.
 78 | 
 79 | Original Repository: [https://github.com/stat-guy/chain-of-draft](https://github.com/stat-guy/chain-of-draft)
 80 | 
 81 | ## Key Benefits
 82 | 
 83 | - **Efficiency**: Significantly reduced token usage (as little as 7.6% of standard CoT)
 84 | - **Speed**: Faster responses due to shorter generation time
 85 | - **Cost Savings**: Lower API costs for LLM calls
 86 | - **Maintained Accuracy**: Similar or even improved accuracy compared to CoT
 87 | - **Flexibility**: Applicable across various reasoning tasks and domains
 88 | 
 89 | ## Features
 90 | 
 91 | 1. **Core Chain of Draft Implementation**
 92 |    - Concise reasoning steps (typically 5 words or less)
 93 |    - Format enforcement
 94 |    - Answer extraction
 95 | 
 96 | 2. **Performance Analytics**
 97 |    - Token usage tracking
 98 |    - Solution accuracy monitoring
 99 |    - Execution time measurement
100 |    - Domain-specific performance metrics
101 | 
102 | 3. **Adaptive Word Limits**
103 |    - Automatic complexity estimation
104 |    - Dynamic adjustment of word limits
105 |    - Domain-specific calibration
106 | 
107 | 4. **Comprehensive Example Database**
108 |    - CoT to CoD transformation 
109 |    - Domain-specific examples (math, code, biology, physics, chemistry, puzzle)
110 |    - Example retrieval based on problem similarity
111 | 
112 | 5. **Format Enforcement**
113 |    - Post-processing to ensure adherence to word limits
114 |    - Step structure preservation
115 |    - Adherence analytics
116 | 
117 | 6. **Hybrid Reasoning Approaches**
118 |    - Automatic selection between CoD and CoT
119 |    - Domain-specific optimization
120 |    - Historical performance-based selection
121 | 
122 | 7. **OpenAI API Compatibility**
123 |    - Drop-in replacement for standard OpenAI clients
124 |    - Support for both completions and chat interfaces
125 |    - Easy integration into existing workflows
126 | 
127 | ## Setup and Installation
128 | 
129 | ### Prerequisites
130 | - Python 3.10+ (for Python implementation)
131 | - Node.js 22+ (for JavaScript implementation)
132 | - Nx (for building Single Executable Applications)
133 | 
134 | ### Python Installation
135 | 
136 | 1. Clone the repository
137 | 2. Install dependencies:
138 |    ```bash
139 |    pip install -r requirements.txt
140 |    ```
141 | 3. Configure API keys in `.env` file:
142 |    ```
143 |    ANTHROPIC_API_KEY=your_api_key_here
144 |    ```
145 | 4. Run the server:
146 |    ```bash
147 |    python server.py
148 |    ```
149 | 
150 | ### JavaScript/TypeScript Installation
151 | 
152 | 1. Clone the repository
153 | 2. Install dependencies:
154 |    ```bash
155 |    npm install
156 |    ```
157 | 3. Configure API keys in `.env` file:
158 |    ```
159 |    ANTHROPIC_API_KEY=your_api_key_here
160 |    ```
161 | 4. Build and run the server:
162 |    ```bash
163 |    # Build TypeScript files using Nx
164 |    npm run nx build
165 | 
166 |    # Start the server
167 |    npm start
168 | 
169 |    # For development with auto-reload:
170 |    npm run dev
171 |    ```
172 | 
173 | Available scripts:
174 | - `npm run nx build`: Compiles TypeScript to JavaScript using Nx build system
175 | - `npm run build:sea`: Creates Single Executable Applications for all platforms
176 | - `npm start`: Runs the compiled server from `dist`
177 | - `npm test`: Runs the test query against the server
178 | - `npm run dev`: Runs the TypeScript server directly using ts-node (useful for development)
179 | 
180 | The project uses Nx as its build system, providing:
181 | - Efficient caching and incremental builds
182 | - Cross-platform build support
183 | - Integrated SEA generation
184 | - Dependency graph visualization
185 | - Consistent build process across environments
186 | 
187 | ## Single Executable Applications (SEA)
188 | 
189 | This project supports building Single Executable Applications (SEA) using Node.js 22+ and the [@getlarge/nx-node-sea](https://github.com/getlarge/nx-node-sea) plugin. This allows you to create standalone executables that don't require Node.js to be installed on the target system.
190 | 
191 | ### Building SEA Executables
192 | 
193 | The project includes several scripts for building SEA executables:
194 | 
195 | ```bash
196 | # Build for all platforms
197 | npm run build:sea
198 | 
199 | # Build for specific platforms
200 | npm run build:macos   # macOS
201 | npm run build:linux   # Linux
202 | npm run build:windows # Windows
203 | ```
204 | 
205 | ### SEA Build Configuration
206 | 
207 | The project uses Nx for managing the build process. The SEA configuration is handled through the nx-node-sea plugin, which provides a streamlined way to create Node.js single executable applications.
208 | 
209 | Key features of the SEA build process:
210 | - Cross-platform support (macOS, Linux, Windows)
211 | - Automatic dependency bundling
212 | - Optimized binary size
213 | - No runtime dependencies required
214 | 
215 | ### Using SEA Executables
216 | 
217 | Once built, the SEA executables can be found in the `dist` directory. These executables:
218 | - Are completely standalone
219 | - Don't require Node.js installation
220 | - Can be distributed and run directly
221 | - Maintain all functionality of the original application
222 | 
223 | For Claude Desktop integration with SEA executables, update your configuration to use the executable path:
224 | 
225 | ```json
226 | {
227 |     "mcpServers": {
228 |         "chain-of-draft-prompt-tool": {
229 |             "command": "/path/to/mcp-chain-of-draft-prompt-tool",
230 |             "env": {
231 |                 "ANTHROPIC_API_KEY": "your_api_key_here"
232 |             }
233 |         }
234 |     }
235 | }
236 | ```
237 | 
238 | ## Claude Desktop Integration
239 | 
240 | To integrate with Claude Desktop:
241 | 
242 | 1. Install Claude Desktop from [claude.ai/download](https://claude.ai/download)
243 | 2. Create or edit the Claude Desktop config file:
244 |    ```
245 |    ~/Library/Application Support/Claude/claude_desktop_config.json
246 |    ```
247 | 3. Add the tool configuration (Python version):
248 |    ```json
249 |    {
250 |        "mcpServers": {
251 |            "chain-of-draft-prompt-tool": {
252 |                "command": "python3",
253 |                "args": ["/absolute/path/to/cod/server.py"],
254 |                "env": {
255 |                    "ANTHROPIC_API_KEY": "your_api_key_here"
256 |                }
257 |            }
258 |        }
259 |    }
260 |    ```
261 |    
262 |    Or for the JavaScript version:
263 |    ```json
264 |    {
265 |        "mcpServers": {
266 |            "chain-of-draft-prompt-tool": {
267 |                "command": "node",
268 |                "args": ["/absolute/path/to/cod/index.js"],
269 |                "env": {
270 |                    "ANTHROPIC_API_KEY": "your_api_key_here"
271 |                }
272 |            }
273 |        }
274 |    }
275 |    ```
276 | 4. Restart Claude Desktop
277 | 
278 | You can also use the Claude CLI to add the tool:
279 | 
280 | ```bash
281 | # For Python implementation
282 | claude mcp add chain-of-draft-prompt-tool -e ANTHROPIC_API_KEY="your_api_key_here" "python3 /absolute/path/to/cod/server.py"
283 | 
284 | # For JavaScript implementation
285 | claude mcp add chain-of-draft-prompt-tool -e ANTHROPIC_API_KEY="your_api_key_here" "node /absolute/path/to/cod/index.js"
286 | ```
287 | 
288 | ## Using with Dive GUI
289 | 
290 | [Dive](https://github.com/OpenAgentPlatform/Dive) is an excellent open-source MCP Host Desktop Application that provides a user-friendly GUI for interacting with MCP tools like this one. It supports multiple LLMs including ChatGPT, Anthropic Claude, Ollama, and other OpenAI-compatible models.
291 | 
292 | ### Integrating with Dive
293 | 
294 | 1. Download and install Dive from their [releases page](https://github.com/OpenAgentPlatform/Dive/releases)
295 | 
296 | 2. Configure the Chain of Draft tool in Dive's MCP settings:
297 | 
298 | ```json
299 | {
300 |   "mcpServers": {
301 |     "chain-of-draft-prompt-tool": {
302 |       "command": "/path/to/mcp-chain-of-draft-prompt-tool",
303 |       "enabled": true,
304 |       "env": {
305 |         "ANTHROPIC_API_KEY": "your_api_key_here"
306 |       }
307 |     }
308 |   }
309 | }
310 | ```
311 | 
312 | If you're using the non-SEA version:
313 | ```json
314 | {
315 |   "mcpServers": {
316 |     "chain-of-draft-prompt-tool": {
317 |       "command": "node",
318 |       "args": ["/path/to/dist/index.js"],
319 |       "enabled": true,
320 |       "env": {
321 |         "ANTHROPIC_API_KEY": "your_api_key_here"
322 |       }
323 |     }
324 |   }
325 | }
326 | ```
327 | 
328 | ### Key Benefits of Using Dive
329 | 
330 | - 🌐 Universal LLM Support with multiple API key management
331 | - 💻 Cross-platform availability (Windows, MacOS, Linux)
332 | - 🔄 Seamless MCP integration in both stdio and SSE modes
333 | - 🌍 Multi-language interface
334 | - 💡 Custom instructions and system prompts
335 | - 🔄 Automatic updates
336 | 
337 | Using Dive provides a convenient way to interact with the Chain of Draft tool through a modern, feature-rich interface while maintaining all the benefits of the MCP protocol.
338 | 
339 | ## Testing with MCP Inspector
340 | 
341 | The project includes integration with the MCP Inspector tool, which provides a visual interface for testing and debugging MCP tools. This is especially useful during development or when you want to inspect the tool's behavior.
342 | 
343 | ### Running the Inspector
344 | 
345 | You can start the MCP Inspector using the provided npm script:
346 | 
347 | ```bash
348 | # Start the MCP Inspector with the tool
349 | npm run test-inspector
350 | 
351 | # Or run it manually
352 | npx @modelcontextprotocol/inspector -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY -- node dist/index.js
353 | ```
354 | 
355 | This will:
356 | 1. Start the MCP server in the background
357 | 2. Launch the MCP Inspector interface in your default browser
358 | 3. Connect to the running server for testing
359 | 
360 | ### Using the Inspector Interface
361 | 
362 | The MCP Inspector provides:
363 | - 🔍 Real-time visualization of tool calls and responses
364 | - 📝 Interactive testing of MCP functions
365 | - 🔄 Request/response history
366 | - 🐛 Debug information for each interaction
367 | - 📊 Performance metrics and timing data
368 | 
369 | This makes it an invaluable tool for:
370 | - Development and debugging
371 | - Understanding tool behavior
372 | - Testing different inputs and scenarios
373 | - Verifying MCP compliance
374 | - Performance optimization
375 | 
376 | The Inspector will be available at `http://localhost:5173` by default.
377 | 
378 | ## Available Tools
379 | 
380 | The Chain of Draft server provides the following tools:
381 | 
382 | | Tool | Description |
383 | |------|-------------|
384 | | `chain_of_draft_solve` | Solve a problem using Chain of Draft reasoning |
385 | | `math_solve` | Solve a math problem with CoD |
386 | | `code_solve` | Solve a coding problem with CoD |
387 | | `logic_solve` | Solve a logic problem with CoD |
388 | | `get_performance_stats` | Get performance stats for CoD vs CoT |
389 | | `get_token_reduction` | Get token reduction statistics |
390 | | `analyze_problem_complexity` | Analyze problem complexity |
391 | 
392 | ## Developer Usage
393 | 
394 | ### Python Client
395 | 
396 | If you want to use the Chain of Draft client directly in your Python code:
397 | 
398 | ```python
399 | from client import ChainOfDraftClient
400 | 
401 | # Create client with specific LLM provider
402 | cod_client = ChainOfDraftClient(
403 |     llm_provider="ollama",  # or "anthropic", "openai", "mistral", "custom"
404 |     model_name="llama2"     # specify your model
405 | )
406 | 
407 | # Use directly
408 | result = await cod_client.solve_with_reasoning(
409 |     problem="Solve: 247 + 394 = ?",
410 |     domain="math"
411 | )
412 | 
413 | print(f"Answer: {result['final_answer']}")
414 | print(f"Reasoning: {result['reasoning_steps']}")
415 | print(f"Tokens used: {result['token_count']}")
416 | ```
417 | 
418 | ### JavaScript/TypeScript Client
419 | 
420 | For TypeScript/Node.js applications:
421 | 
422 | ```typescript
423 | import { ChainOfDraftClient } from './lib/chain-of-draft-client';
424 | 
425 | // Create client with your preferred LLM
426 | const client = new ChainOfDraftClient({
427 |   provider: 'ollama',           // or 'anthropic', 'openai', 'mistral', 'custom'
428 |   model: 'llama2',             // your chosen model
429 |   endpoint: 'http://localhost:11434'  // for custom endpoints
430 | });
431 | 
432 | // Use the client
433 | async function solveMathProblem() {
434 |   const result = await client.solveWithReasoning({
435 |     problem: "Solve: 247 + 394 = ?",
436 |     domain: "math",
437 |     max_words_per_step: 5
438 |   });
439 |   
440 |   console.log(`Answer: ${result.final_answer}`);
441 |   console.log(`Reasoning: ${result.reasoning_steps}`);
442 |   console.log(`Tokens used: ${result.token_count}`);
443 | }
444 | 
445 | solveMathProblem();
446 | ```
447 | 
448 | ## Implementation Details
449 | 
450 | The server is available in both Python and JavaScript implementations, both consisting of several integrated components:
451 | 
452 | ### Python Implementation
453 | 
454 | 1. **AnalyticsService**: Tracks performance metrics across different problem domains and reasoning approaches
455 | 2. **ComplexityEstimator**: Analyzes problems to determine appropriate word limits
456 | 3. **ExampleDatabase**: Manages and retrieves examples, transforming CoT examples to CoD format
457 | 4. **FormatEnforcer**: Ensures reasoning steps adhere to word limits
458 | 5. **ReasoningSelector**: Intelligently chooses between CoD and CoT based on problem characteristics
459 | 
460 | ### JavaScript Implementation
461 | 
462 | 1. **analyticsDb**: In-memory database for tracking performance metrics
463 | 2. **complexityEstimator**: Analyzes problems to determine complexity and appropriate word limits
464 | 3. **formatEnforcer**: Ensures reasoning steps adhere to word limits 
465 | 4. **reasoningSelector**: Automatically chooses between CoD and CoT based on problem characteristics and historical performance
466 | 
467 | Both implementations follow the same core principles and provide identical MCP tools, making them interchangeable for most use cases.
468 | 
469 | ## License
470 | 
471 | This project is open-source and available under the MIT license.
472 | 
```

--------------------------------------------------------------------------------
/docs/CLAUDE.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Chain of Draft MCP Server
 2 | 
 3 | ## Overview
 4 | - This repository implements a Chain of Draft (CoD) reasoning server
 5 | - CoD is a method for efficient reasoning with LLMs using minimal word steps
 6 | 
 7 | ## Dependencies
 8 | - Required packages are in `requirements.txt`
 9 | - Install with `pip install -r requirements.txt`
10 | - Requires an Anthropic API key in `.env` file
11 | 
12 | ## Running the Server
13 | - Start the server with `python server.py`
14 | - Alternatively, configure in Claude Desktop config
15 | 
16 | ## Testing
17 | - Run tests with `python test.py`
18 | 
19 | ## Claude Desktop Integration
20 | - Add to `~/Library/Application Support/Claude/claude_desktop_config.json`
21 | - Point to the absolute path of server.py
22 | - Include your Anthropic API key in the environment
23 | - Restart Claude Desktop after configuration
24 | 
25 | ## Available Tools
26 | - `chain_of_draft_solve`: General problem solver with CoD
27 | - `math_solve`: Specifically for math problems
28 | - `code_solve`: For coding problems
29 | - `logic_solve`: For logic problems
30 | - `get_performance_stats`: View statistics on CoD vs CoT
31 | - `get_token_reduction`: Analyze token usage reduction
32 | - `analyze_problem_complexity`: Evaluate problem difficulty
33 | 
34 | ## Pushing to GitHub
35 | - Use `./push.sh` to push changes to GitHub
36 | - Use `./commit.sh` to commit changes
```

--------------------------------------------------------------------------------
/push.sh:
--------------------------------------------------------------------------------

```bash
1 | #!/bin/bash
2 | git push -u origin main
3 | 
```

--------------------------------------------------------------------------------
/commit.sh:
--------------------------------------------------------------------------------

```bash
1 | #!/bin/bash
2 | git commit -m "Initial commit: Chain of Draft MCP server implementation"
3 | 
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
 1 | mcp>=1.2.0
 2 | anthropic>=0.15.0
 3 | openai>=1.12.0
 4 | mistralai>=0.0.12
 5 | sqlalchemy>=2.0.0
 6 | python-dotenv>=1.0.0
 7 | uuid>=1.30
 8 | aiohttp>=3.8.0
 9 | uvicorn>=0.23.0
10 | pydantic>=2.0.0
11 | fastapi>=0.100.0
12 | typing-extensions>=4.5.0
13 | asyncio>=3.4.3
14 | 
```

--------------------------------------------------------------------------------
/claude_desktop_config.example.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "mcpServers": {
 3 |         "mcp-chain-of-draft-prompt-tool": {
 4 |             "command": "python",
 5 |             "args": ["-m", "server.server"],
 6 |             "env": {
 7 |                 "ANTHROPIC_API_KEY": "your_anthropic_api_key_here"
 8 |             }
 9 |         }
10 |     }
11 | }
12 | 
```

--------------------------------------------------------------------------------
/sea-config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "main": "dist/index.js",
 3 |   "output": "binaries/mcp-chain-of-draft-prompt-tool",
 4 |   "assets": [
 5 |     "dist/**/*",
 6 |     "config.json",
 7 |     "promptsConfig.json",
 8 |     "system_template.md"
 9 |   ],
10 |   "env": {
11 |     "NODE_ENV": "production"
12 |   },
13 |   "platforms": ["darwin", "linux", "win32"],
14 |   "architectures": {
15 |     "darwin": ["x64", "arm64"],
16 |     "linux": ["x64", "arm64"],
17 |     "win32": ["x64"]
18 |   }
19 | } 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "ES2020",
 5 |     "moduleResolution": "node",
 6 |     "esModuleInterop": true,
 7 |     "strict": true,
 8 |     "outDir": "dist/",
 9 |     "rootDir": "src/",
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "resolveJsonModule": true
13 |   },
14 |   "include": ["src/**/*.ts", "src/index.ts", "src/server.tssrc/test-query.tsquery.ts", "src/types.ts"],
15 |   "exclude": ["node_modules", "dist"]
16 | } 
```

--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Chain of Draft (CoD) MCP Server.
 3 | 
 4 | This package implements the Chain of Draft reasoning approach
 5 | as described in the paper "Chain of Draft: Thinking Faster by Writing Less".
 6 | """
 7 | 
 8 | from .analytics import AnalyticsService
 9 | from .complexity import ComplexityEstimator
10 | from .examples import ExampleDatabase
11 | from .format import FormatEnforcer
12 | from .reasoning import ReasoningSelector, create_cod_prompt, create_cot_prompt
13 | from .client import ChainOfDraftClient
14 | 
15 | __version__ = "0.1.0"
16 | 
```

--------------------------------------------------------------------------------
/project.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "mcp-chain-of-draft-prompt-tool",
 3 |   "$schema": "node_modules/nx/schemas/project-schema.json",
 4 |   "sourceRoot": "src",
 5 |   "projectType": "application",
 6 |   "plugins": [
 7 |     {
 8 |       "plugin": "@getlarge/nx-node-sea",
 9 |       "options": {
10 |         "seaTargetName": "sea-build",
11 |         "buildTarget": "build"
12 |       }
13 |     }
14 |   ],
15 |   "targets": {
16 |     "build": {
17 |       "executor": "@nx/js:tsc",
18 |       "outputs": ["{options.outputPath}"],
19 |       "options": {
20 |         "outputPath": "dist",
21 |         "main": "src/index.ts",
22 |         "tsConfig": "tsconfig.json",
23 |         "assets": ["package.json"]
24 |       }
25 |     }
26 |   }
27 | } 
```

--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "extends": "nx/presets/npm.json",
 3 |   "$schema": "./node_modules/nx/schemas/nx-schema.json",
 4 |   "namedInputs": {
 5 |     "default": ["{projectRoot}/**/*"],
 6 |     "production": ["default"]
 7 |   },
 8 |   "plugins": [
 9 |     {
10 |       "plugin": "@getlarge/nx-node-sea",
11 |       "options": {
12 |         "seaTargetName": "sea-build",
13 |         "buildTarget": "build"
14 |       }
15 |     }
16 |   ],
17 |   "tasksRunnerOptions": {
18 |     "default": {
19 |       "runner": "nx/tasks-runners/default",
20 |       "options": {
21 |         "cacheableOperations": ["build", "test"]
22 |       }
23 |     }
24 |   },
25 |   "targetDefaults": {
26 |     "build": {
27 |       "dependsOn": ["^build"],
28 |       "inputs": [],
29 |       "options": {
30 |         "outputPath": "dist",
31 |         "main": "src/index.ts",
32 |         "tsConfig": "tsconfig.json",
33 |         "rootDir": "src",
34 |         "outputFileName": "index.js"
35 |       }
36 |     }
37 |   }
38 | } 
```

--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "server": {
 3 |     "name": "MCP Chain of Draft (CoD) Prompt Tool",
 4 |     "version": "1.0.0",
 5 |     "port": 3000,
 6 |     "description": "A server for Chain of Draft reasoning and analysis"
 7 |   },
 8 |   "prompts": {
 9 |     "file": "promptsConfig.json",
10 |     "registrationMode": "name",
11 |     "categories": ["chain-of-draft", "analytics"]
12 |   },
13 |   "transports": {
14 |     "default": "stdio",
15 |     "sse": { 
16 |       "enabled": false 
17 |     },
18 |     "stdio": { 
19 |       "enabled": true,
20 |       "debug": true
21 |     }
22 |   },
23 |   "logging": {
24 |     "directory": "./logs",
25 |     "level": "info",
26 |     "format": "json",
27 |     "transport": true
28 |   },
29 |   "analytics": {
30 |     "databases": {
31 |       "examples": "cod_examples.db",
32 |       "analytics": "cod_analytics.db"
33 |     }
34 |   },
35 |   "features": {
36 |     "adaptiveWordLimit": true,
37 |     "formatEnforcement": true,
38 |     "complexityAnalysis": true,
39 |     "performanceTracking": true
40 |   }
41 | } 
```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import chalk from 'chalk';
 2 | 
 3 | export const logger = {
 4 |   info: (message: string, ...args: any[]) => {
 5 |     console.log(chalk.blue('ℹ'), chalk.blue(message), ...args);
 6 |   },
 7 |   
 8 |   success: (message: string, ...args: any[]) => {
 9 |     console.log(chalk.green('✓'), chalk.green(message), ...args);
10 |   },
11 |   
12 |   warning: (message: string, ...args: any[]) => {
13 |     console.warn(chalk.yellow('⚠'), chalk.yellow(message), ...args);
14 |   },
15 |   
16 |   error: (message: string, ...args: any[]) => {
17 |     console.error(chalk.red('✖'), chalk.red(message), ...args);
18 |   },
19 |   
20 |   debug: (message: string, ...args: any[]) => {
21 |     console.log(chalk.gray('🔍'), chalk.gray(message), ...args);
22 |   },
23 |   
24 |   highlight: (message: string, ...args: any[]) => {
25 |     console.log(chalk.cyan('→'), chalk.cyan(message), ...args);
26 |   },
27 | 
28 |   devLog: (message: string, ...args: any[]) => {
29 |     if (process.env.NODE_ENV === 'development') {
30 |       console.log(chalk.gray('[DEV]'), chalk.gray(message), ...args);
31 |     }
32 |   },
33 | 
34 |   // Special formatting for Chain of Draft outputs
35 |   codOutput: {
36 |     header: (text: string) => console.log(chalk.bold.magenta('\n=== ' + text + ' ===\n')),
37 |     problem: (text: string) => console.log(chalk.yellow('Problem: ') + text),
38 |     steps: (steps: string) => console.log(chalk.cyan('Reasoning Steps:\n') + steps),
39 |     answer: (text: string) => console.log(chalk.green('\nFinal Answer: ') + text),
40 |     stats: (stats: Record<string, any>) => {
41 |       console.log(chalk.blue('\nStats:'));
42 |       Object.entries(stats).forEach(([key, value]) => {
43 |         console.log(chalk.blue(`- ${key}: `) + chalk.white(value));
44 |       });
45 |     }
46 |   }
47 | }; 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Build stage
 2 | FROM node:22.12-alpine AS builder
 3 | 
 4 | # Install only necessary build dependencies
 5 | RUN apk add --no-cache \
 6 |     python3 \
 7 |     py3-pip \
 8 |     py3-virtualenv
 9 | 
10 | WORKDIR /app
11 | 
12 | # Copy dependency files
13 | COPY package*.json requirements.txt ./
14 | 
15 | # Setup .env file (use .env if it exists, otherwise use .env.example)
16 | COPY .env* ./
17 | RUN if [ ! -f .env ] && [ -f .env.example ]; then cp .env.example .env; fi
18 | 
19 | # Install Node.js dependencies with caching
20 | RUN --mount=type=cache,target=/root/.npm \
21 |     npm ci --ignore-scripts --omit-dev
22 | 
23 | # Setup Python environment and install dependencies
24 | RUN python3 -m venv /app/venv
25 | ENV PATH="/app/venv/bin:$PATH"
26 | RUN --mount=type=cache,target=/root/.cache/pip \
27 |     pip3 install --no-cache-dir -r requirements.txt
28 | 
29 | # Set build-time environment variables
30 | ENV NODE_ENV=production
31 | 
32 | # Copy source and build
33 | COPY . .
34 | RUN npm run build
35 | 
36 | # Release stage
37 | FROM node:22-alpine AS release
38 | 
39 | # Install runtime dependencies
40 | RUN apk add --no-cache \
41 |     python3 \
42 |     py3-pip
43 | 
44 | WORKDIR /app
45 | 
46 | # Copy built artifacts and dependencies
47 | COPY --from=builder /app/dist /app/dist
48 | COPY --from=builder /app/package*.json /app/
49 | COPY --from=builder /app/requirements.txt /app/
50 | COPY --from=builder /app/venv /app/venv
51 | COPY --from=builder /app/.env /app/.env
52 | 
53 | # Set runtime environment variables
54 | ENV PATH="/app/venv/bin:$PATH" \
55 |     NODE_ENV=production
56 | 
57 | # Install production dependencies
58 | RUN npm ci --omit=dev
59 | 
60 | # Set executable permissions
61 | RUN chmod +x /app/dist/index.js
62 | 
63 | # Set production environment
64 | ENV NODE_ENV=production
65 | 
66 | # Set the user to non-root for security
67 | USER node
68 | 
69 | # Create startup script
70 | ENTRYPOINT ["node", "/app/dist/index.js"]
```

--------------------------------------------------------------------------------
/src/test-query.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import fetch from 'node-fetch';
 2 | import { logger } from './utils/logger.js';
 3 | 
 4 | interface ChainOfDraftResponse {
 5 |   reasoning_steps: string;
 6 |   final_answer: string;
 7 |   approach: string;
 8 |   stats: {
 9 |     word_limit: number;
10 |     token_count: number;
11 |     execution_time_ms: number;
12 |     complexity: number;
13 |   };
14 | }
15 | 
16 | function isChainOfDraftResponse(data: unknown): data is ChainOfDraftResponse {
17 |   return (
18 |     typeof data === 'object' &&
19 |     data !== null &&
20 |     'reasoning_steps' in data &&
21 |     'final_answer' in data &&
22 |     'approach' in data &&
23 |     'stats' in data
24 |   );
25 | }
26 | 
27 | async function testChainOfDraft() {
28 |   try {
29 |     const problem = "What is the sum of the first 10 prime numbers?";
30 |     const response = await fetch('http://localhost:3000/solve', {
31 |       method: 'POST',
32 |       headers: {
33 |         'Content-Type': 'application/json',
34 |       },
35 |       body: JSON.stringify({
36 |         problem,
37 |         domain: 'math',
38 |         approach: 'CoD'
39 |       })
40 |     });
41 | 
42 |     const rawData = await response.json();
43 |     
44 |     if (!isChainOfDraftResponse(rawData)) {
45 |       throw new Error('Invalid response format from server');
46 |     }
47 |     
48 |     const data: ChainOfDraftResponse = rawData;
49 |     logger.codOutput.header('Chain of Draft Demo');
50 |     logger.codOutput.problem(problem);
51 |     logger.codOutput.steps(data.reasoning_steps);
52 |     logger.codOutput.answer(data.final_answer);
53 |     logger.codOutput.stats({
54 |       'Approach': data.approach,
55 |       'Word limit': data.stats.word_limit,
56 |       'Tokens used': data.stats.token_count,
57 |       'Execution time': `${data.stats.execution_time_ms}ms`,
58 |       'Complexity score': data.stats.complexity
59 |     });
60 | 
61 |   } catch (error) {
62 |     logger.error('Error testing Chain of Draft:', error);
63 |   }
64 | }
65 | 
66 | testChainOfDraft(); 
```

--------------------------------------------------------------------------------
/system_template.md:
--------------------------------------------------------------------------------

```markdown
 1 | TEMPLATE """{{- if .Messages }}
 2 | {{- if or .System .Tools }}<start_of_turn>user
 3 | {{- if .System}}
 4 | {{ .System }}
 5 | {{- end }}
 6 | {{- if .Tools }}
 7 | # Function Calling Instructions
 8 | 
 9 | You are provided with a set of functions to help complete tasks. Here are your instructions for using them:
10 | 
11 | 1. FUNCTION DEFINITIONS
12 | The available functions are defined within <tools></tools> XML tags below:
13 | 
14 | <tools>
15 | {{- range $.Tools }}
16 | {"type": "function", "function": {{ .Function }}}
17 | {{- end }}
18 | </tools>
19 | 
20 | 2. FUNCTION CALLING FORMAT
21 | When calling a function:
22 | - Use ```tool_use``` code blocks
23 | - Include both function name and arguments as a JSON object
24 | - Follow this exact format:
25 | 
26 | ```tool_use
27 | {"name": "<function-name>", "arguments": <args-json-object>}
28 | ```
29 | 
30 | 3. GUIDELINES
31 | - Analyze the task carefully before choosing functions
32 | - Only call functions that are necessary
33 | - Validate all required arguments before calling
34 | - Handle function responses appropriately
35 | - Maintain context between function calls
36 | - Chain multiple functions if needed to complete the task
37 | 
38 | 4. ERROR HANDLING
39 | - Validate argument types and formats
40 | - Check for missing required arguments
41 | - Handle function response errors gracefully
42 | - Retry with different approaches if needed
43 | 
44 | 5. RESPONSE FORMAT
45 | - Always provide clear explanations of your actions
46 | - Format responses in a clear, structured way
47 | - Include relevant context from function responses
48 | - End with clear next steps or conclusions
49 | 
50 | {{- end }}<end_of_turn>
51 | {{ end }}
52 | {{- range $i, $_ := .Messages }}
53 | {{- $last := eq (len (slice $.Messages $i)) 1 -}}
54 | {{- if eq .Role "user" }}<start_of_turn>user
55 | {{ .Content }}<end_of_turn>
56 | {{ else if eq .Role "assistant" }}<start_of_turn>model
57 | {{ if .Content }}{{ .Content }}
58 | {{- else if .ToolCalls }}```tool_use
59 | {{ range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments}}}
60 | {{ end }}```
61 | {{- end }}{{ if not $last }}<end_of_turn>
62 | {{ end }}
63 | {{- else if eq .Role "tool" }}<start_of_turn>user
64 | <tool_response>
65 | {{ .Content }}
66 | </tool_response><end_of_turn>
67 | {{ end }}
68 | {{- if and (ne .Role "assistant") $last }}<start_of_turn>model
69 | {{ end }}
70 | {{- end }}
71 | {{- else }}
72 | {{- if .System }}<start_of_turn>user
73 | {{ .System }}<end_of_turn>
74 | {{ end }}{{ if .Prompt }}<start_of_turn>user
75 | {{ .Prompt }}<end_of_turn>
76 | {{ end }}<start_of_turn>model
77 | {{ end }}{{ .Response }}{{ if .Response }}<end_of_turn>{{ end }}"""
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "mcp-chain-of-draft-prompt-tool",
 3 |   "version": "1.1.1",
 4 |   "description": "MCP Chain of Draft (CoD) Prompt Tool - A Model Context Protocol tool for efficient reasoning",
 5 |   "type": "module",
 6 |   "engines": {
 7 |     "node": ">=22"
 8 |   },
 9 |   "bin": {
10 |     "mcp-chain-of-draft-prompt-tool": "dist/index.js"
11 |   },
12 |   "files": [
13 |     "dist",
14 |     "README.md",
15 |     "LICENSE"
16 |   ],
17 |   "scripts": {
18 |     "build": "tsc && shx chmod +x dist/*.js",
19 |     "build:docker": "docker build -t mcp-chain-of-draft-prompt-tool .",
20 |     "build:sea": "nx build && nx run-many --target=sea --all",
21 |     "build:macos": "nx build && nx run-many --target=sea --all --args=\"--platform=darwin\"",
22 |     "build:linux": "nx build && nx run-many --target=sea --all --args=\"--platform=linux\"",
23 |     "build:windows": "nx build && nx run-many --target=sea --all --args=\"--platform=win32\"",
24 |     "watch": "tsc --watch",
25 |     "start": "node dist/index.js",
26 |     "dev": "ts-node-esm dist/index.ts",
27 |     "test": "node dist/test-query.js",
28 |     "inspector": "npx @modelcontextprotocol/inspector -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY -- node dist/index.js",
29 |     "open-browser": "sleep 2 && open http://localhost:5173 || xdg-open http://localhost:5173 || start http://localhost:5173",
30 |     "test-inspector": "npm run start & sleep 2 && trap 'kill $!' SIGINT SIGTERM; (npm run inspector & npm run open-browser) || kill $!"
31 |   },
32 |   "keywords": [
33 |     "mcp",
34 |     "chain-of-draft",
35 |     "claude",
36 |     "reasoning",
37 |     "prompt-tool"
38 |   ],
39 |   "author": "brendancopley",
40 |   "license": "MIT",
41 |   "repository": {
42 |     "type": "git",
43 |     "url": "git+https://github.com/brendancopley/mcp-chain-of-draft-prompt-tool.git"
44 |   },
45 |   "bugs": {
46 |     "url": "https://github.com/brendancopley/mcp-chain-of-draft-prompt-tool/issues"
47 |   },
48 |   "homepage": "https://github.com/brendancopley/mcp-chain-of-draft-prompt-tool#readme",
49 |   "dependencies": {
50 |     "@anthropic-ai/sdk": "^0.39.0",
51 |     "@mistralai/mistralai": "^1.5.2",
52 |     "@modelcontextprotocol/sdk": "1.7.0",
53 |     "chalk": "5",
54 |     "dotenv": "^16.4.5",
55 |     "node-fetch": "^3.3.2",
56 |     "ollama": "^0.5.14",
57 |     "openai": "^4.28.0"
58 |   },
59 |   "devDependencies": {
60 |     "@getlarge/nx-node-sea": "^0.2.0",
61 |     "@nx/devkit": "^20.6.2",
62 |     "@nx/js": "^20.6.2",
63 |     "@nx/workspace": "^20.6.2",
64 |     "@types/node": "^22",
65 |     "@types/node-fetch": "^2.6.11",
66 |     "nx": "^20.6.2",
67 |     "shx": "^0.4.0",
68 |     "ts-node": "^10.9.2",
69 |     "typescript": "^5.3.3"
70 |   },
71 |   "packageManager": "[email protected]+sha1.ac34549e6aa8e7ead463a7390f61a6610"
72 | }
73 | 
```

--------------------------------------------------------------------------------
/RELEASE_NOTES.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Release Notes - v1.1.1
 2 | 
 3 | ## MCP Chain of Draft Prompt Tool
 4 | 
 5 | We're excited to announce version 1.1.1 of the MCP Chain of Draft Prompt Tool! This release introduces BYOLLM support and improves documentation clarity.
 6 | 
 7 | ### What's New
 8 | 
 9 | - **BYOLLM Support**: Added support for multiple LLM providers:
10 |   - Cloud services (Claude, GPT, Mistral)
11 |   - Local models via Ollama
12 |   - Custom local LLM endpoints
13 | - **Pre-built Binaries**: Added standalone executables for all major platforms
14 | - Enhanced documentation to better explain the tool's core functionality
15 | - Improved README with clearer explanation of the transformation process
16 | - Updated Smithery.ai integration and badges
17 | 
18 | ### Core Functionality Highlight
19 | 
20 | The MCP Chain of Draft Prompt Tool serves as an intelligent prompt transformer that:
21 | 
22 | 1. Takes your standard prompts
23 | 2. Automatically transforms them into Chain of Draft (CoD) or Chain of Thought (CoT) format
24 | 3. Processes them through your chosen LLM (cloud or local)
25 | 4. Returns enhanced, structured responses
26 | 
27 | This process significantly improves reasoning quality while reducing token usage.
28 | 
29 | ### LLM Integration
30 | 
31 | The tool now supports multiple LLM providers:
32 | 
33 | #### Cloud Services
34 | - Anthropic Claude
35 | - OpenAI GPT models
36 | - Mistral AI
37 | 
38 | #### Local Models
39 | - Ollama integration (supporting all Ollama models)
40 | - Custom local LLM endpoints
41 | - Any model supporting chat completion API
42 | 
43 | ### Pre-built Binaries
44 | 
45 | We now provide standalone executables for all major platforms:
46 | 
47 | #### macOS
48 | - `mcp-chain-of-draft-prompt-tool-macos-arm64` (Apple Silicon)
49 | - `mcp-chain-of-draft-prompt-tool-macos-x64` (Intel)
50 | 
51 | #### Linux
52 | - `mcp-chain-of-draft-prompt-tool-linux-arm64` (ARM64)
53 | - `mcp-chain-of-draft-prompt-tool-linux-x64` (x64)
54 | 
55 | #### Windows
56 | - `mcp-chain-of-draft-prompt-tool-win-x64.exe`
57 | 
58 | ### Integration
59 | 
60 | The tool is now available on Smithery.ai:
61 | https://smithery.ai/server/@brendancopley/mcp-chain-of-draft-prompt-tool
62 | 
63 | ### Getting Started
64 | 
65 | To get started with the new version:
66 | 
67 | ```bash
68 | # Using npm package
69 | npm install [email protected]
70 | 
71 | # Or download the appropriate binary for your platform from the releases page
72 | ```
73 | 
74 | Configure your preferred LLM:
75 | 
76 | ```bash
77 | # For Ollama
78 | export MCP_LLM_PROVIDER=ollama
79 | export MCP_OLLAMA_MODEL=llama2
80 | 
81 | # For cloud services
82 | export ANTHROPIC_API_KEY=your_key_here
83 | # or
84 | export OPENAI_API_KEY=your_key_here
85 | # or
86 | export MISTRAL_API_KEY=your_key_here
87 | ```
88 | 
89 | ### Feedback
90 | 
91 | We welcome your feedback and contributions! Please visit our GitHub repository or Smithery.ai page to share your thoughts and experiences. 
```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Anthropic } from '@anthropic-ai/sdk';
  2 | 
  3 | export interface AnalyticsRecord {
  4 |   problem_id: string;
  5 |   problem_text: string;
  6 |   domain: string;
  7 |   approach: string;
  8 |   word_limit: number;
  9 |   tokens_used: number;
 10 |   execution_time_ms: number;
 11 |   reasoning_steps: string;
 12 |   answer: string;
 13 |   timestamp?: Date;
 14 | }
 15 | 
 16 | export interface PerformanceStats {
 17 |   domain: string;
 18 |   approach: string;
 19 |   avg_tokens: number;
 20 |   avg_time_ms: number;
 21 |   accuracy: number | null;
 22 |   count: number;
 23 | }
 24 | 
 25 | export interface TokenReductionStats {
 26 |   domain: string;
 27 |   cod_avg_tokens: number;
 28 |   cot_avg_tokens: number;
 29 |   reduction_percentage: number;
 30 | }
 31 | 
 32 | export interface ComplexityAnalysis {
 33 |   word_count: number;
 34 |   sentence_count: number;
 35 |   words_per_sentence: number;
 36 |   indicator_count: number;
 37 |   found_indicators: string[];
 38 |   question_count: number;
 39 |   estimated_complexity: number;
 40 | }
 41 | 
 42 | export interface DomainPreferences {
 43 |   complexity_threshold: number;
 44 |   accuracy_threshold: number;
 45 | }
 46 | 
 47 | export interface ChainOfDraftParams {
 48 |   problem: string;
 49 |   domain?: string;
 50 |   max_words_per_step?: number | null;
 51 |   approach?: string;
 52 |   enforce_format?: boolean;
 53 |   adaptive_word_limit?: boolean;
 54 | }
 55 | 
 56 | export interface ChainOfDraftResult {
 57 |   approach: string;
 58 |   reasoning_steps: string;
 59 |   final_answer: string;
 60 |   token_count: number;
 61 |   word_limit: number;
 62 |   complexity: number;
 63 |   execution_time_ms: number;
 64 | }
 65 | 
 66 | export interface AnalyticsDatabase {
 67 |   records: AnalyticsRecord[];
 68 |   addRecord(record: AnalyticsRecord): number;
 69 |   getPerformanceByDomain(domain?: string): PerformanceStats[];
 70 |   getTokenReductionStats(): TokenReductionStats[];
 71 | }
 72 | 
 73 | export interface ComplexityEstimator {
 74 |   domainBaseLimits: { [key: string]: number };
 75 |   complexityIndicators: { [key: string]: string[] };
 76 |   analyzeProblem(problem: string, domain: string): ComplexityAnalysis;
 77 | }
 78 | 
 79 | export interface FormatEnforcer {
 80 |   enforceWordLimit(reasoning: string, maxWordsPerStep: number | null): string;
 81 |   analyzeAdherence(reasoning: string, maxWordsPerStep: number): {
 82 |     step_count: number;
 83 |     total_words: number;
 84 |     avg_words_per_step: number;
 85 |     over_limit_steps: number;
 86 |     adherence_percentage: number;
 87 |   };
 88 | }
 89 | 
 90 | export interface ReasoningSelector {
 91 |   defaultPreferences: { [key: string]: DomainPreferences };
 92 |   selectApproach(domain: string, complexity: number, performanceStats: PerformanceStats[]): string;
 93 | }
 94 | 
 95 | export interface ChainOfDraftClient {
 96 |   solveWithReasoning(params: ChainOfDraftParams): Promise<ChainOfDraftResult>;
 97 | }
 98 | 
 99 | export interface ToolArguments {
100 |   problem?: string;
101 |   domain?: string;
102 |   max_words_per_step?: number;
103 |   approach?: string;
104 |   enforce_format?: boolean;
105 |   adaptive_word_limit?: boolean;
106 | } 
```

--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Test script for the Chain of Draft client.
 3 | """
 4 | 
 5 | import asyncio
 6 | import os
 7 | from dotenv import load_dotenv
 8 | # Use absolute import since this file might be run directly
 9 | from client import ChainOfDraftClient
10 | 
11 | # Load environment variables
12 | load_dotenv()
13 | 
14 | async def main():
15 |     """Run a simple test of the Chain of Draft client."""
16 |     # Create client
17 |     client = ChainOfDraftClient()
18 |     
19 |     # Test problems
20 |     test_problems = [
21 |         {
22 |             "problem": "Jason had 20 lollipops. He gave Denny some lollipops. Now Jason has 12 lollipops. How many lollipops did Jason give to Denny?",
23 |             "domain": "math"
24 |         },
25 |         {
26 |             "problem": "A coin is heads up. John flips the coin. Mary flips the coin. Paul flips the coin. Susan does not flip the coin. Is the coin still heads up?",
27 |             "domain": "logic"
28 |         },
29 |         {
30 |             "problem": "Write a function to find the nth Fibonacci number.",
31 |             "domain": "code"
32 |         }
33 |     ]
34 |     
35 |     # Run tests with both CoD and CoT
36 |     for approach in ["CoD", "CoT"]:
37 |         print(f"\n===== Testing with {approach} approach =====\n")
38 |         
39 |         for test in test_problems:
40 |             print(f"Problem ({test['domain']}): {test['problem']}")
41 |             
42 |             # Solve with specified approach
43 |             result = await client.solve_with_reasoning(
44 |                 problem=test['problem'],
45 |                 domain=test['domain'],
46 |                 approach=approach
47 |             )
48 |             
49 |             print(f"\nReasoning:\n{result['reasoning_steps']}")
50 |             print(f"\nAnswer: {result['final_answer']}")
51 |             print(f"Tokens: {result['token_count']}")
52 |             print(f"Word limit: {result['word_limit']}")
53 |             print(f"Complexity: {result['complexity']}")
54 |             print("\n" + "-" * 50 + "\n")
55 |     
56 |     # Get performance stats
57 |     print("\n===== Performance Statistics =====\n")
58 |     stats = await client.get_performance_stats()
59 |     
60 |     for stat in stats:
61 |         print(f"Domain: {stat['domain']}")
62 |         print(f"Approach: {stat['approach']}")
63 |         print(f"Average tokens: {stat['avg_tokens']}")
64 |         print(f"Average time: {stat['avg_time_ms']}")
65 |         
66 |         if stat['accuracy'] is not None:
67 |             print(f"Accuracy: {stat['accuracy'] * 100:.1f}%")
68 |             
69 |         print(f"Sample size: {stat['count']}")
70 |         print()
71 |     
72 |     # Get token reduction stats
73 |     reduction_stats = await client.get_token_reduction_stats()
74 |     
75 |     if reduction_stats:
76 |         print("\n===== Token Reduction =====\n")
77 |         
78 |         for stat in reduction_stats:
79 |             print(f"Domain: {stat['domain']}")
80 |             print(f"CoD avg tokens: {stat['cod_avg_tokens']:.1f}")
81 |             print(f"CoT avg tokens: {stat['cot_avg_tokens']:.1f}")
82 |             print(f"Reduction: {stat['reduction_percentage']:.1f}%")
83 |             print()
84 | 
85 | if __name__ == "__main__":
86 |     asyncio.run(main())
87 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
  1 | name: '@brendancopley/mcp-chain-of-draft-prompt-tool'
  2 | description: |-
  3 |   A TypeScript-based Model Context Protocol (MCP) server for Chain of Draft
  4 |   reasoning
  5 | version: "1.0.0"
  6 | build:
  7 |   dockerBuildPath: ./
  8 | startCommand:
  9 |   type: stdio
 10 |   configSchema:
 11 |     type: object
 12 |     title: MCP Server Configuration
 13 |     description: Configuration options for the MCP server
 14 |     properties:
 15 |       LLM_PROVIDER:
 16 |         type: string
 17 |         description: Choose which provider to use - 'anthropic', 'openai', 'mistral', or 'ollama'
 18 |         enum: ['anthropic', 'openai', 'mistral', 'ollama']
 19 |         default: 'anthropic'
 20 |       LLM_MODEL:
 21 |         type: string
 22 |         description: Default model to use (provider-specific)
 23 |         default: 'claude-3-7-sonnet-latest'
 24 |       ANTHROPIC_API_KEY:
 25 |         type: string
 26 |         description: API key for Anthropic Claude models
 27 |       ANTHROPIC_BASE_URL:
 28 |         type: string
 29 |         description: Base URL for Anthropic API
 30 |         default: 'https://api.anthropic.com'
 31 |       OPENAI_API_KEY:
 32 |         type: string
 33 |         description: API key for OpenAI models
 34 |       OPENAI_BASE_URL:
 35 |         type: string
 36 |         description: Base URL for OpenAI API
 37 |         default: 'https://api.openai.com'
 38 |       MISTRAL_API_KEY:
 39 |         type: string
 40 |         description: API key for Mistral AI models
 41 |       OLLAMA_BASE_URL:
 42 |         type: string
 43 |         description: Base URL for Ollama local deployment
 44 |         default: 'http://localhost:11434'
 45 |       COD_MAX_WORDS_PER_STEP:
 46 |         type: string
 47 |         description: Maximum words per step in Chain of Draft
 48 |         default: '5'
 49 |       ENFORCE_FORMAT:
 50 |         type: string
 51 |         description: Whether to enforce format in Chain of Draft
 52 |         default: 'true'
 53 |       ADAPTIVE_WORD_LIMIT:
 54 |         type: string
 55 |         description: Whether to use adaptive word limit in Chain of Draft
 56 |         default: 'true'
 57 |       COD_DB_URL:
 58 |         type: string
 59 |         description: URL for Chain of Draft analytics database
 60 |         default: 'sqlite:///cod_analytics.db'
 61 |       COD_EXAMPLES_DB:
 62 |         type: string
 63 |         description: Path to Chain of Draft examples database
 64 |         default: 'cod_examples.db'
 65 |       COD_DEFAULT_MODEL:
 66 |         type: string
 67 |         description: Default model for Chain of Draft
 68 |         default: 'claude-3-7-sonnet-latest'
 69 |       COD_MAX_TOKENS:
 70 |         type: string
 71 |         description: Maximum tokens for Chain of Draft
 72 |         default: '500'
 73 |     required: []
 74 |   commandFunction: |-
 75 |     (config) => ({
 76 |       command: 'node',
 77 |       args: ['dist/index.js'],
 78 |       env: {
 79 |         NODE_ENV: config.NODE_ENV || 'production',
 80 |         LLM_PROVIDER: config.LLM_PROVIDER || 'anthropic',
 81 |         LLM_MODEL: config.LLM_MODEL || 'claude-3-7-sonnet-latest',
 82 |         ANTHROPIC_API_KEY: config.ANTHROPIC_API_KEY,
 83 |         OPENAI_API_KEY: config.OPENAI_API_KEY,
 84 |         MISTRAL_API_KEY: config.MISTRAL_API_KEY,
 85 |         ANTHROPIC_BASE_URL: config.ANTHROPIC_BASE_URL || 'https://api.anthropic.com',
 86 |         OPENAI_BASE_URL: config.OPENAI_BASE_URL || 'https://api.openai.com',
 87 |         OLLAMA_BASE_URL: config.OLLAMA_BASE_URL || 'http://localhost:11434',
 88 |         COD_MAX_WORDS_PER_STEP: config.COD_MAX_WORDS_PER_STEP || '5',
 89 |         ENFORCE_FORMAT: config.ENFORCE_FORMAT || 'true',
 90 |         ADAPTIVE_WORD_LIMIT: config.ADAPTIVE_WORD_LIMIT || 'true',
 91 |         COD_DB_URL: config.COD_DB_URL || 'sqlite:///cod_analytics.db',
 92 |         COD_EXAMPLES_DB: config.COD_EXAMPLES_DB || 'cod_examples.db',
 93 |         COD_DEFAULT_MODEL: config.COD_DEFAULT_MODEL || 'claude-3-7-sonnet-latest',
 94 |         COD_MAX_TOKENS: config.COD_MAX_TOKENS || '500'
 95 |       }
 96 |     })
 97 | 
 98 | clients:
 99 |   - claude
100 |   - cursor
101 |   - windsurf
102 |   - cline
103 |   - typescript
104 | 
```

--------------------------------------------------------------------------------
/promptsConfig.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "categories": [
  3 |     {
  4 |       "id": "chain-of-draft",
  5 |       "name": "Chain of Draft",
  6 |       "description": "Prompts related to Chain of Draft reasoning approach"
  7 |     },
  8 |     {
  9 |       "id": "analytics",
 10 |       "name": "Analytics",
 11 |       "description": "Tools for analyzing performance and complexity"
 12 |     }
 13 |   ],
 14 |   "prompts": [
 15 |     {
 16 |       "id": "chain_of_draft_solve",
 17 |       "name": "Chain of Draft Solve",
 18 |       "category": "chain-of-draft",
 19 |       "description": "Solve a reasoning problem using Chain of Draft approach",
 20 |       "arguments": [
 21 |         {
 22 |           "name": "problem",
 23 |           "description": "The problem to solve",
 24 |           "required": true
 25 |         },
 26 |         {
 27 |           "name": "domain",
 28 |           "description": "Domain for context (math, logic, code, common-sense, etc.)",
 29 |           "required": false
 30 |         },
 31 |         {
 32 |           "name": "max_words_per_step",
 33 |           "description": "Maximum words per reasoning step (default: adaptive)",
 34 |           "required": false
 35 |         },
 36 |         {
 37 |           "name": "approach",
 38 |           "description": "Force 'CoD' or 'CoT' approach (default: auto-select)",
 39 |           "required": false
 40 |         },
 41 |         {
 42 |           "name": "enforce_format",
 43 |           "description": "Whether to enforce the word limit (default: True)",
 44 |           "required": false
 45 |         },
 46 |         {
 47 |           "name": "adaptive_word_limit",
 48 |           "description": "Adjust word limits based on complexity (default: True)",
 49 |           "required": false
 50 |         }
 51 |       ]
 52 |     },
 53 |     {
 54 |       "id": "math_solve",
 55 |       "name": "Math Problem Solve",
 56 |       "category": "chain-of-draft",
 57 |       "description": "Solve a math problem using Chain of Draft reasoning",
 58 |       "arguments": [
 59 |         {
 60 |           "name": "problem",
 61 |           "description": "The math problem to solve",
 62 |           "required": true
 63 |         },
 64 |         {
 65 |           "name": "approach",
 66 |           "description": "Force 'CoD' or 'CoT' approach (default: auto-select)",
 67 |           "required": false
 68 |         },
 69 |         {
 70 |           "name": "max_words_per_step",
 71 |           "description": "Maximum words per step (default: adaptive)",
 72 |           "required": false
 73 |         }
 74 |       ]
 75 |     },
 76 |     {
 77 |       "id": "code_solve",
 78 |       "name": "Code Problem Solve",
 79 |       "category": "chain-of-draft",
 80 |       "description": "Solve a coding problem using Chain of Draft reasoning",
 81 |       "arguments": [
 82 |         {
 83 |           "name": "problem",
 84 |           "description": "The coding problem to solve",
 85 |           "required": true
 86 |         },
 87 |         {
 88 |           "name": "approach",
 89 |           "description": "Force 'CoD' or 'CoT' approach (default: auto-select)",
 90 |           "required": false
 91 |         },
 92 |         {
 93 |           "name": "max_words_per_step",
 94 |           "description": "Maximum words per step (default: adaptive)",
 95 |           "required": false
 96 |         }
 97 |       ]
 98 |     },
 99 |     {
100 |       "id": "logic_solve",
101 |       "name": "Logic Problem Solve",
102 |       "category": "chain-of-draft",
103 |       "description": "Solve a logic problem using Chain of Draft reasoning",
104 |       "arguments": [
105 |         {
106 |           "name": "problem",
107 |           "description": "The logic problem to solve",
108 |           "required": true
109 |         },
110 |         {
111 |           "name": "approach",
112 |           "description": "Force 'CoD' or 'CoT' approach (default: auto-select)",
113 |           "required": false
114 |         },
115 |         {
116 |           "name": "max_words_per_step",
117 |           "description": "Maximum words per step (default: adaptive)",
118 |           "required": false
119 |         }
120 |       ]
121 |     },
122 |     {
123 |       "id": "get_performance_stats",
124 |       "name": "Get Performance Stats",
125 |       "category": "analytics",
126 |       "description": "Get performance statistics for CoD vs CoT approaches",
127 |       "arguments": [
128 |         {
129 |           "name": "domain",
130 |           "description": "Filter for specific domain (optional)",
131 |           "required": false
132 |         }
133 |       ]
134 |     },
135 |     {
136 |       "id": "get_token_reduction",
137 |       "name": "Get Token Reduction",
138 |       "category": "analytics",
139 |       "description": "Get token reduction statistics for CoD vs CoT",
140 |       "arguments": []
141 |     },
142 |     {
143 |       "id": "analyze_problem_complexity",
144 |       "name": "Analyze Problem Complexity",
145 |       "category": "analytics",
146 |       "description": "Analyze the complexity of a problem",
147 |       "arguments": [
148 |         {
149 |           "name": "problem",
150 |           "description": "The problem to analyze",
151 |           "required": true
152 |         },
153 |         {
154 |           "name": "domain",
155 |           "description": "Problem domain",
156 |           "required": false
157 |         }
158 |       ]
159 |     }
160 |   ],
161 |   "imports": []
162 | } 
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Node.js wrapper for the Chain of Draft Python server
  5 |  * This provides better compatibility with Claude Desktop
  6 |  */
  7 | 
  8 | import { spawn } from 'child_process';
  9 | import path from 'path';
 10 | import fs from 'fs';
 11 | import { fileURLToPath } from 'url';
 12 | import { logger } from './utils/logger.js';
 13 | import dotenv from 'dotenv';
 14 | 
 15 | // Load environment variables
 16 | dotenv.config();
 17 | 
 18 | 
 19 | // Get current file directory in ESM
 20 | const __filename = fileURLToPath(import.meta.url);
 21 | const __dirname = path.dirname(__filename);
 22 | 
 23 | // Path to the Python server script
 24 | const serverPath = path.join(__dirname, 'server.py');
 25 | 
 26 | logger.devLog('Server initialization', {
 27 |   serverPath,
 28 |   nodeEnv: process.env.NODE_ENV,
 29 |   cwd: process.cwd(),
 30 |   pythonPath: process.env.PYTHON_PATH || 'python3'
 31 | });
 32 | 
 33 | // Error if server.py doesn't exist
 34 | if (!fs.existsSync(serverPath)) {
 35 |   logger.error(`Server file not found at ${serverPath}`);
 36 |   process.exit(1);
 37 | }
 38 | 
 39 | // Launch the Python process with debugging
 40 | logger.debug(`Starting Python process: python3 ${serverPath}`);
 41 | 
 42 | const pythonProcess = spawn('python3', [serverPath], {
 43 |   env: {
 44 |     ...process.env,
 45 |     PYTHONUNBUFFERED: '1', // Ensure Python output isn't buffered
 46 |     DEBUG: process.env.NODE_ENV === 'development' ? '1' : '0'
 47 |   },
 48 |   stdio: ['pipe', 'pipe', 'pipe']  // Explicitly define stdio
 49 | });
 50 | 
 51 | logger.devLog('Python process spawned', {
 52 |   pid: pythonProcess.pid,
 53 |   spawnargs: pythonProcess.spawnargs,
 54 |   env: {
 55 |     PYTHONUNBUFFERED: process.env.PYTHONUNBUFFERED,
 56 |     DEBUG: process.env.DEBUG
 57 |   }
 58 | });
 59 | 
 60 | // Keep the process alive by sending a dummy input occasionally
 61 | const keepAlive = setInterval(() => {
 62 |   // Just checking if process is still running
 63 |   if (pythonProcess.killed) {
 64 |     clearInterval(keepAlive);
 65 |     logger.error("Python process was killed, exiting");
 66 |     logger.devLog('Python process killed', {
 67 |       pid: pythonProcess.pid,
 68 |       exitCode: pythonProcess.exitCode,
 69 |       signalCode: pythonProcess.signalCode
 70 |     });
 71 |     process.exit(1);
 72 |   }
 73 | }, 5000);
 74 | 
 75 | // Pass stdin to the Python process
 76 | process.stdin.pipe(pythonProcess.stdin);
 77 | 
 78 | // Pipe Python's stdout to our stdout
 79 | pythonProcess.stdout.pipe(process.stdout);
 80 | 
 81 | // Log stderr but don't pipe it to avoid protocol errors
 82 | pythonProcess.stderr.on('data', (data: Buffer) => {
 83 |   process.stderr.write(data);
 84 |   if (process.env.NODE_ENV === 'development') {
 85 |     logger.devLog('Python stderr', data.toString());
 86 |   }
 87 | });
 88 | 
 89 | // Handle process termination
 90 | pythonProcess.on('close', (code: number | null) => {
 91 |   if (code === null) {
 92 |     logger.error('Python process was killed, exiting');
 93 |     logger.devLog('Python process terminated', {
 94 |       pid: pythonProcess.pid,
 95 |       exitCode: pythonProcess.exitCode,
 96 |       signalCode: pythonProcess.signalCode
 97 |     });
 98 |   } else {
 99 |     logger.error(`Chain of Draft server exited with code ${code}`);
100 |     logger.devLog('Python process exited', {
101 |       pid: pythonProcess.pid,
102 |       code,
103 |       signalCode: pythonProcess.signalCode
104 |     });
105 |   }
106 |   process.exit(code || 1);
107 | });
108 | 
109 | // Forward termination signals
110 | process.on('SIGINT', () => {
111 |   logger.devLog('Received SIGINT signal');
112 |   pythonProcess.kill('SIGINT');
113 | });
114 | 
115 | process.on('SIGTERM', () => {
116 |   logger.devLog('Received SIGTERM signal');
117 |   pythonProcess.kill('SIGTERM');
118 | });
119 | 
120 | export async function startServer() {
121 |   const serverPath = path.join(process.cwd(), 'server.py');
122 | 
123 |   logger.devLog('Starting server', {
124 |     serverPath,
125 |     nodeEnv: process.env.NODE_ENV,
126 |     development: process.env.NODE_ENV === 'development'
127 |   });
128 | 
129 |   if (!fs.existsSync(serverPath)) {
130 |     logger.error(`Server file not found at ${serverPath}`);
131 |     process.exit(1);
132 |   }
133 | 
134 |   logger.debug(`Starting Python process: python3 ${serverPath}`);
135 | 
136 |   const pythonProcess = spawn('python3', [serverPath], {
137 |     stdio: ['pipe', 'pipe', 'pipe'],
138 |     env: {
139 |       ...process.env,
140 |       PYTHONUNBUFFERED: '1',
141 |       DEBUG: process.env.NODE_ENV === 'development' ? '1' : '0'
142 |     }
143 |   });
144 | 
145 |   logger.devLog('Server process spawned', {
146 |     pid: pythonProcess.pid,
147 |     spawnargs: pythonProcess.spawnargs
148 |   });
149 | 
150 |   pythonProcess.stdout.on('data', (data) => {
151 |     process.stdout.write(data);
152 |     if (process.env.NODE_ENV === 'development') {
153 |       logger.devLog('Python stdout', data.toString());
154 |     }
155 |   });
156 | 
157 |   pythonProcess.stderr.on('data', (data) => {
158 |     process.stderr.write(data);
159 |     if (process.env.NODE_ENV === 'development') {
160 |       logger.devLog('Python stderr', data.toString());
161 |     }
162 |   });
163 | 
164 |   pythonProcess.on('close', (code) => {
165 |     if (code === null) {
166 |       logger.error('Python process was killed, exiting');
167 |       logger.devLog('Server process killed', {
168 |         pid: pythonProcess.pid,
169 |         exitCode: pythonProcess.exitCode
170 |       });
171 |     } else {
172 |       logger.error(`Chain of Draft server exited with code ${code}`);
173 |       logger.devLog('Server process exited', {
174 |         pid: pythonProcess.pid,
175 |         code
176 |       });
177 |     }
178 |     process.exit(code || 1);
179 |   });
180 | 
181 |   return pythonProcess;
182 | }
183 | 
```

--------------------------------------------------------------------------------
/src/python/format.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Format enforcement module for the Chain of Draft MCP server.
  3 | Ensures reasoning steps adhere to the word limit.
  4 | """
  5 | 
  6 | import re
  7 | 
  8 | class FormatEnforcer:
  9 |     """
 10 |     Enforces the Chain of Draft format by ensuring reasoning steps
 11 |     adhere to the specified word limit.
 12 |     """
 13 |     
 14 |     def __init__(self):
 15 |         """Initialize with patterns for detecting reasoning steps."""
 16 |         # Patterns for identifying different step formats
 17 |         self.step_pattern = re.compile(
 18 |             r'(\d+\.\s*|Step\s+\d+:|\n-\s+|\n\*\s+|•\s+|^\s*-\s+|^\s*\*\s+)',
 19 |             re.MULTILINE
 20 |         )
 21 |     
 22 |     def enforce_word_limit(self, reasoning, max_words_per_step):
 23 |         """
 24 |         Enforce word limit per reasoning step.
 25 |         
 26 |         Args:
 27 |             reasoning: The reasoning text to process
 28 |             max_words_per_step: Maximum number of words allowed per step
 29 |             
 30 |         Returns:
 31 |             Processed reasoning with enforced word limits
 32 |         """
 33 |         # Split into steps
 34 |         steps = self._split_into_steps(reasoning)
 35 |         
 36 |         # Process each step
 37 |         enforced_steps = []
 38 |         for step in steps:
 39 |             enforced_step = self._enforce_step(step, max_words_per_step)
 40 |             enforced_steps.append(enforced_step)
 41 |         
 42 |         # Combine back
 43 |         return "\n".join(enforced_steps)
 44 |     
 45 |     def _split_into_steps(self, reasoning):
 46 |         """Split reasoning text into individual steps."""
 47 |         # Try to detect step formatting
 48 |         if self.step_pattern.search(reasoning):
 49 |             # Extract steps with their markers
 50 |             parts = []
 51 |             current_part = ""
 52 |             lines = reasoning.split('\n')
 53 |             
 54 |             for line in lines:
 55 |                 # If this is a new step, store the previous part and start a new one
 56 |                 if self.step_pattern.match(line) or re.match(r'^\s*\d+\.', line):
 57 |                     if current_part:
 58 |                         parts.append(current_part)
 59 |                     current_part = line
 60 |                 else:
 61 |                     # Otherwise add to current part
 62 |                     if current_part:
 63 |                         current_part += "\n" + line
 64 |                     else:
 65 |                         current_part = line
 66 |             
 67 |             # Add the last part
 68 |             if current_part:
 69 |                 parts.append(current_part)
 70 |                 
 71 |             return parts if parts else [reasoning]
 72 |         else:
 73 |             # If no clear step markers, split by lines
 74 |             return [line.strip() for line in reasoning.split('\n') if line.strip()]
 75 |     
 76 |     def _enforce_step(self, step, max_words):
 77 |         """Enforce word limit on a single reasoning step."""
 78 |         words = step.split()
 79 |         
 80 |         # If already within limit, return as is
 81 |         if len(words) <= max_words:
 82 |             return step
 83 |         
 84 |         # Extract step number/marker if present
 85 |         match = re.match(r'^(\d+\.\s*|Step\s+\d+:|\s*-\s+|\s*\*\s+|•\s+)', step)
 86 |         marker = match.group(0) if match else ""
 87 |         content = step[len(marker):].strip() if marker else step
 88 |         
 89 |         # Truncate content words
 90 |         content_words = content.split()
 91 |         truncated = " ".join(content_words[:max_words])
 92 |         
 93 |         # Reassemble with marker
 94 |         return f"{marker}{truncated}"
 95 |     
 96 |     def analyze_adherence(self, reasoning, max_words_per_step):
 97 |         """
 98 |         Analyze how well the reasoning adheres to word limits.
 99 |         
100 |         Args:
101 |             reasoning: The reasoning text to analyze
102 |             max_words_per_step: Maximum number of words allowed per step
103 |             
104 |         Returns:
105 |             Dictionary with adherence metrics
106 |         """
107 |         steps = self._split_into_steps(reasoning)
108 |         
109 |         # Count words in each step, excluding markers
110 |         step_counts = []
111 |         for step in steps:
112 |             # Extract step number/marker if present
113 |             match = re.match(r'^(\d+\.\s*|Step\s+\d+:|\s*-\s+|\s*\*\s+|•\s+)', step)
114 |             marker = match.group(0) if match else ""
115 |             content = step[len(marker):].strip() if marker else step
116 |             
117 |             # Count words in content
118 |             step_counts.append(len(content.split()))
119 |         
120 |         # Calculate adherence metrics
121 |         adherence = {
122 |             "total_steps": len(steps),
123 |             "steps_within_limit": sum(1 for count in step_counts if count <= max_words_per_step),
124 |             "average_words_per_step": sum(step_counts) / len(steps) if steps else 0,
125 |             "max_words_in_any_step": max(step_counts) if steps else 0,
126 |             "adherence_rate": sum(1 for count in step_counts if count <= max_words_per_step) / len(steps) if steps else 1.0,
127 |             "step_counts": step_counts
128 |         }
129 |         
130 |         return adherence
131 |     
132 |     def format_to_numbered_steps(self, reasoning):
133 |         """Format reasoning into consistently numbered steps."""
134 |         steps = self._split_into_steps(reasoning)
135 |         
136 |         # Convert to numbered steps
137 |         numbered_steps = []
138 |         for i, step in enumerate(steps, 1):
139 |             # Remove any existing markers
140 |             match = re.match(r'^(\d+\.\s*|Step\s+\d+:|\s*-\s+|\s*\*\s+|•\s+)', step)
141 |             if match:
142 |                 content = step[len(match.group(0)):].strip()
143 |             else:
144 |                 content = step.strip()
145 |                 
146 |             # Add numbered step format
147 |             numbered_steps.append(f"{i}. {content}")
148 |         
149 |         return "\n".join(numbered_steps)
150 | 
```

--------------------------------------------------------------------------------
/src/python/complexity.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Complexity estimation module for the Chain of Draft MCP server.
  3 | Analyzes problems to determine appropriate word limits.
  4 | """
  5 | 
  6 | class ComplexityEstimator:
  7 |     """
  8 |     Estimates the complexity of a problem to determine appropriate word limits
  9 |     for Chain of Draft reasoning steps.
 10 |     """
 11 |     
 12 |     def __init__(self):
 13 |         """Initialize with domain-specific complexity rules."""
 14 |         # Base complexity rules - default word limits per domain
 15 |         self.domain_base_limits = {
 16 |             "math": 6,
 17 |             "logic": 5,
 18 |             "common_sense": 4,
 19 |             "physics": 7,
 20 |             "chemistry": 6,
 21 |             "biology": 5,
 22 |             "code": 8,
 23 |             "puzzle": 5,
 24 |             "general": 5
 25 |         }
 26 |         
 27 |         # Keywords that might indicate complexity
 28 |         self.complexity_indicators = {
 29 |             "math": [
 30 |                 "integral", "derivative", "equation", "proof", "theorem", "calculus",
 31 |                 "matrix", "vector", "linear algebra", "probability", "statistics",
 32 |                 "geometric series", "differential", "polynomial", "factorial"
 33 |             ],
 34 |             "logic": [
 35 |                 "if and only if", "necessary condition", "sufficient", "contradiction",
 36 |                 "syllogism", "premise", "fallacy", "converse", "counterexample",
 37 |                 "logical equivalence", "negation", "disjunction", "conjunction"
 38 |             ],
 39 |             "code": [
 40 |                 "recursion", "algorithm", "complexity", "optimization", "function",
 41 |                 "class", "object", "inheritance", "polymorphism", "data structure",
 42 |                 "binary tree", "hash table", "graph", "dynamic programming"
 43 |             ],
 44 |             "physics": [
 45 |                 "quantum", "relativity", "momentum", "force", "acceleration",
 46 |                 "energy", "thermodynamics", "electric field", "magnetic field",
 47 |                 "potential", "entropy", "wavelength", "frequency"
 48 |             ],
 49 |             "chemistry": [
 50 |                 "reaction", "molecule", "compound", "element", "equilibrium",
 51 |                 "acid", "base", "oxidation", "reduction", "catalyst", "isomer"
 52 |             ],
 53 |             "biology": [
 54 |                 "gene", "protein", "enzyme", "cell", "tissue", "organ", "system",
 55 |                 "metabolism", "photosynthesis", "respiration", "homeostasis"
 56 |             ],
 57 |             "puzzle": [
 58 |                 "constraint", "sequence", "pattern", "rules", "probability",
 59 |                 "combination", "permutation", "optimal", "strategy"
 60 |             ]
 61 |         }
 62 |     
 63 |     async def estimate_complexity(self, problem, domain="general"):
 64 |         """
 65 |         Estimate the complexity of a problem based on its characteristics.
 66 |         Returns a recommended word limit per step.
 67 |         """
 68 |         # Start with base word limit for domain
 69 |         base_limit = self.domain_base_limits.get(domain.lower(), 5)
 70 |         
 71 |         # Analyze problem length - longer problems often need more detailed reasoning
 72 |         length_factor = min(len(problem.split()) / 50, 2)  # Cap at doubling
 73 |         
 74 |         # Check for complexity indicators
 75 |         indicator_count = 0
 76 |         indicators = self.complexity_indicators.get(domain.lower(), [])
 77 |         for indicator in indicators:
 78 |             if indicator.lower() in problem.lower():
 79 |                 indicator_count += 1
 80 |         
 81 |         indicator_factor = min(1 + (indicator_count * 0.2), 1.8)  # Cap at 80% increase
 82 |         
 83 |         # Count the number of question marks - might indicate multi-part problems
 84 |         question_factor = 1 + (problem.count("?") * 0.2)
 85 |         
 86 |         # Simple complexity analysis based on sentence structure
 87 |         sentences = [s for s in problem.split(".") if s.strip()]
 88 |         words_per_sentence = len(problem.split()) / max(len(sentences), 1)
 89 |         sentence_complexity_factor = min(words_per_sentence / 15, 1.5)  # Complex sentences
 90 |         
 91 |         # Special domain-specific adjustments
 92 |         domain_factor = 1.0
 93 |         if domain.lower() == "math" and any(term in problem.lower() for term in ["prove", "proof", "theorem"]):
 94 |             domain_factor = 1.3  # Proofs need more explanation
 95 |         elif domain.lower() == "code" and any(term in problem.lower() for term in ["implement", "function", "algorithm"]):
 96 |             domain_factor = 1.2  # Code implementations need more detail
 97 |         
 98 |         # Combine factors - take the maximum impact factor
 99 |         impact_factor = max(length_factor, indicator_factor, question_factor, 
100 |                            sentence_complexity_factor, domain_factor)
101 |         
102 |         # Apply the impact factor to the base limit
103 |         adjusted_limit = round(base_limit * impact_factor)
104 |         
105 |         # Cap at reasonable bounds
106 |         return max(3, min(adjusted_limit, 10))
107 |     
108 |     def analyze_problem(self, problem, domain="general"):
109 |         """
110 |         Provide a detailed analysis of problem complexity factors.
111 |         Useful for debugging and understanding complexity estimates.
112 |         """
113 |         base_limit = self.domain_base_limits.get(domain.lower(), 5)
114 |         
115 |         # Word count analysis
116 |         word_count = len(problem.split())
117 |         length_factor = min(word_count / 50, 2)
118 |         
119 |         # Indicator analysis
120 |         indicators = self.complexity_indicators.get(domain.lower(), [])
121 |         found_indicators = [ind for ind in indicators if ind.lower() in problem.lower()]
122 |         indicator_count = len(found_indicators)
123 |         indicator_factor = min(1 + (indicator_count * 0.2), 1.8)
124 |         
125 |         # Question mark analysis
126 |         question_count = problem.count("?")
127 |         question_factor = 1 + (question_count * 0.2)
128 |         
129 |         # Sentence complexity
130 |         sentences = [s for s in problem.split(".") if s.strip()]
131 |         words_per_sentence = word_count / max(len(sentences), 1)
132 |         sentence_complexity_factor = min(words_per_sentence / 15, 1.5)
133 |         
134 |         return {
135 |             "domain": domain,
136 |             "base_limit": base_limit,
137 |             "word_count": word_count,
138 |             "length_factor": length_factor,
139 |             "indicator_count": indicator_count,
140 |             "found_indicators": found_indicators,
141 |             "indicator_factor": indicator_factor,
142 |             "question_count": question_count,
143 |             "question_factor": question_factor,
144 |             "sentence_count": len(sentences),
145 |             "words_per_sentence": words_per_sentence,
146 |             "sentence_complexity_factor": sentence_complexity_factor,
147 |             "estimated_complexity": max(3, min(round(base_limit * max(length_factor, indicator_factor, question_factor, sentence_complexity_factor)), 10))
148 |         }
149 | 
```

--------------------------------------------------------------------------------
/src/python/reasoning.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Reasoning selector module for the Chain of Draft MCP server.
  3 | Handles choosing between CoD and CoT approaches.
  4 | """
  5 | 
  6 | from complexity import ComplexityEstimator
  7 | 
  8 | class ReasoningSelector:
  9 |     """
 10 |     Selects the most appropriate reasoning approach (CoD or CoT)
 11 |     based on problem characteristics and historical performance.
 12 |     """
 13 |     
 14 |     def __init__(self, analytics_service):
 15 |         """Initialize with analytics service for performance data."""
 16 |         self.analytics = analytics_service
 17 |         
 18 |         # Preferences for when to use which approach
 19 |         self.default_preferences = {
 20 |             # Format: domain -> criteria
 21 |             "math": {
 22 |                 "complexity_threshold": 7,  # Use CoT for problems with complexity above this
 23 |                 "accuracy_threshold": 0.85  # Use CoT if CoD accuracy falls below this
 24 |             },
 25 |             "code": {
 26 |                 "complexity_threshold": 8,
 27 |                 "accuracy_threshold": 0.9
 28 |             },
 29 |             "physics": {
 30 |                 "complexity_threshold": 7,
 31 |                 "accuracy_threshold": 0.85
 32 |             },
 33 |             "chemistry": {
 34 |                 "complexity_threshold": 7,
 35 |                 "accuracy_threshold": 0.85
 36 |             },
 37 |             "biology": {
 38 |                 "complexity_threshold": 6,
 39 |                 "accuracy_threshold": 0.85
 40 |             },
 41 |             "logic": {
 42 |                 "complexity_threshold": 6,
 43 |                 "accuracy_threshold": 0.9
 44 |             },
 45 |             "puzzle": {
 46 |                 "complexity_threshold": 7,
 47 |                 "accuracy_threshold": 0.85
 48 |             },
 49 |             # Default for any domain
 50 |             "default": {
 51 |                 "complexity_threshold": 6,
 52 |                 "accuracy_threshold": 0.8
 53 |             }
 54 |         }
 55 |     
 56 |     async def select_approach(self, problem, domain, complexity_score=None):
 57 |         """
 58 |         Select whether to use CoD or CoT for this problem.
 59 |         
 60 |         Args:
 61 |             problem: The problem text
 62 |             domain: Problem domain (math, code, logic, etc.)
 63 |             complexity_score: Pre-computed complexity score (optional)
 64 |             
 65 |         Returns:
 66 |             Tuple of (approach, reason) where approach is "CoD" or "CoT"
 67 |         """
 68 |         # Get domain-specific preferences
 69 |         prefs = self.default_preferences.get(domain.lower(), self.default_preferences["default"])
 70 |         
 71 |         # If complexity score not provided, estimate it
 72 |         if complexity_score is None:
 73 |             estimator = ComplexityEstimator()
 74 |             complexity_score = await estimator.estimate_complexity(problem, domain)
 75 |         
 76 |         # Check if complexity exceeds threshold
 77 |         if complexity_score > prefs["complexity_threshold"]:
 78 |             return "CoT", f"Problem complexity ({complexity_score}) exceeds threshold ({prefs['complexity_threshold']})"
 79 |         
 80 |         # Check historical accuracy for this domain with CoD
 81 |         domain_performance = await self.analytics.get_performance_by_domain(domain)
 82 |         cod_accuracy = next((p["accuracy"] for p in domain_performance 
 83 |                            if p["approach"] == "CoD"), None)
 84 |         
 85 |         if cod_accuracy is not None and cod_accuracy < prefs["accuracy_threshold"]:
 86 |             return "CoT", f"Historical accuracy with CoD ({cod_accuracy:.2f}) below threshold ({prefs['accuracy_threshold']})"
 87 |         
 88 |         # Default to CoD for efficiency
 89 |         return "CoD", "Default to Chain-of-Draft for efficiency"
 90 |     
 91 |     def update_preferences(self, domain, complexity_threshold=None, accuracy_threshold=None):
 92 |         """Update preferences for a specific domain."""
 93 |         if domain not in self.default_preferences:
 94 |             self.default_preferences[domain] = self.default_preferences["default"].copy()
 95 |             
 96 |         if complexity_threshold is not None:
 97 |             self.default_preferences[domain]["complexity_threshold"] = complexity_threshold
 98 |             
 99 |         if accuracy_threshold is not None:
100 |             self.default_preferences[domain]["accuracy_threshold"] = accuracy_threshold
101 |     
102 |     def get_preferences(self, domain=None):
103 |         """Get current preferences for all domains or a specific domain."""
104 |         if domain:
105 |             return self.default_preferences.get(domain, self.default_preferences["default"])
106 |         return self.default_preferences
107 | 
108 | 
109 | def create_cod_prompt(problem, domain, max_words_per_step, examples=None):
110 |     """
111 |     Create a Chain of Draft prompt for the LLM.
112 |     
113 |     Args:
114 |         problem: The problem to solve
115 |         domain: Domain for context (math, logic, common-sense, etc.)
116 |         max_words_per_step: Maximum words per reasoning step
117 |         examples: Optional list of few-shot examples
118 |         
119 |     Returns:
120 |         Dictionary with system and user prompts
121 |     """
122 |     system_prompt = f"""
123 |     You are an expert problem solver using Chain of Draft reasoning.
124 |     Think step by step, but only keep a minimum draft for each thinking step, 
125 |     with {max_words_per_step} words at most per step.
126 |     Return the answer at the end after '####'.
127 |     """
128 |     
129 |     # Add domain-specific context
130 |     if domain.lower() == "math":
131 |         system_prompt += "\nUse mathematical notation to keep steps concise."
132 |     elif domain.lower() == "code":
133 |         system_prompt += "\nUse pseudocode or short code snippets when appropriate."
134 |     elif domain.lower() == "physics":
135 |         system_prompt += "\nUse equations and physical quantities with units."
136 |         
137 |     # Add examples if provided
138 |     example_text = ""
139 |     if examples:
140 |         for example in examples:
141 |             example_text += f"\nProblem: {example['problem']}\nSolution:\n{example['reasoning']}\n####\n{example['answer']}\n"
142 |     
143 |     user_prompt = f"Problem: {problem}"
144 |     
145 |     return {
146 |         "system": system_prompt,
147 |         "user": example_text + "\n" + user_prompt if example_text else user_prompt
148 |     }
149 | 
150 | 
151 | def create_cot_prompt(problem, domain, examples=None):
152 |     """
153 |     Create a Chain of Thought prompt for the LLM.
154 |     
155 |     Args:
156 |         problem: The problem to solve
157 |         domain: Domain for context (math, logic, common-sense, etc.)
158 |         examples: Optional list of few-shot examples
159 |         
160 |     Returns:
161 |         Dictionary with system and user prompts
162 |     """
163 |     system_prompt = """
164 |     Think step by step to answer the following question.
165 |     Return the answer at the end of the response after a separator ####.
166 |     """
167 |     
168 |     # Add domain-specific context
169 |     if domain.lower() == "math":
170 |         system_prompt += "\nMake sure to show all mathematical operations clearly."
171 |     elif domain.lower() == "code":
172 |         system_prompt += "\nBe detailed about algorithms and implementation steps."
173 |     elif domain.lower() == "physics":
174 |         system_prompt += "\nExplain physical principles and equations in detail."
175 |         
176 |     # Add examples if provided
177 |     example_text = ""
178 |     if examples:
179 |         for example in examples:
180 |             example_text += f"\nProblem: {example['problem']}\nSolution:\n{example['reasoning']}\n####\n{example['answer']}\n"
181 |     
182 |     user_prompt = f"Problem: {problem}"
183 |     
184 |     return {
185 |         "system": system_prompt,
186 |         "user": example_text + "\n" + user_prompt if example_text else user_prompt
187 |     }
188 | 
```

--------------------------------------------------------------------------------
/src/python/analytics.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Analytics service for the Chain of Draft MCP server.
  3 | Tracks performance metrics for different reasoning approaches.
  4 | """
  5 | 
  6 | import datetime
  7 | from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, JSON, func
  8 | from sqlalchemy.ext.declarative import declarative_base
  9 | from sqlalchemy.orm import sessionmaker
 10 | import os
 11 | 
 12 | Base = declarative_base()
 13 | 
 14 | class InferenceRecord(Base):
 15 |     """Database model for tracking inference performance."""
 16 |     __tablename__ = 'inference_records'
 17 |     
 18 |     id = Column(Integer, primary_key=True)
 19 |     timestamp = Column(DateTime, default=datetime.datetime.utcnow)
 20 |     problem_id = Column(String)
 21 |     problem_text = Column(String)
 22 |     domain = Column(String)
 23 |     approach = Column(String)  # "CoD" or "CoT"
 24 |     word_limit = Column(Integer)
 25 |     tokens_used = Column(Integer)
 26 |     execution_time_ms = Column(Float)
 27 |     reasoning_steps = Column(String)
 28 |     answer = Column(String)
 29 |     expected_answer = Column(String, nullable=True)
 30 |     is_correct = Column(Integer, nullable=True)  # 1=correct, 0=incorrect, null=unknown
 31 |     meta_data = Column(JSON, nullable=True)  # Changed from metadata to meta_data to avoid SQLAlchemy reserved keyword
 32 | 
 33 | 
 34 | class AnalyticsService:
 35 |     """Service for tracking and analyzing inference performance."""
 36 |     
 37 |     def __init__(self, db_url=None):
 38 |         """Initialize the analytics service with a database connection."""
 39 |         if db_url is None:
 40 |             # Default to SQLite in the current directory
 41 |             db_url = os.environ.get("COD_DB_URL", "sqlite:///cod_analytics.db")
 42 |             
 43 |         self.engine = create_engine(db_url)
 44 |         Base.metadata.create_all(self.engine)
 45 |         self.Session = sessionmaker(bind=self.engine)
 46 |     
 47 |     async def record_inference(self, problem, domain, approach, word_limit, 
 48 |                               tokens_used, execution_time, reasoning, answer, 
 49 |                               expected_answer=None, metadata=None):
 50 |         """Record a new inference with performance metrics."""
 51 |         session = self.Session()
 52 |         try:
 53 |             # Simple hash function for problem ID
 54 |             problem_id = str(abs(hash(problem)) % (10 ** 10))
 55 |             
 56 |             record = InferenceRecord(
 57 |                 problem_id=problem_id,
 58 |                 problem_text=problem,
 59 |                 domain=domain,
 60 |                 approach=approach,
 61 |                 word_limit=word_limit,
 62 |                 tokens_used=tokens_used,
 63 |                 execution_time_ms=execution_time,
 64 |                 reasoning_steps=reasoning,
 65 |                 answer=answer,
 66 |                 expected_answer=expected_answer,
 67 |                 is_correct=self._check_correctness(answer, expected_answer) if expected_answer else None,
 68 |                 meta_data=metadata
 69 |             )
 70 |             session.add(record)
 71 |             session.commit()
 72 |             return record.id
 73 |         finally:
 74 |             session.close()
 75 |     
 76 |     def _check_correctness(self, answer, expected_answer):
 77 |         """Check if an answer is correct."""
 78 |         # Basic string comparison - could be improved with more sophisticated matching
 79 |         if not answer or not expected_answer:
 80 |             return None
 81 |             
 82 |         return 1 if answer.strip().lower() == expected_answer.strip().lower() else 0
 83 |     
 84 |     async def get_performance_by_domain(self, domain=None):
 85 |         """Get performance statistics by domain."""
 86 |         session = self.Session()
 87 |         try:
 88 |             query = session.query(
 89 |                 InferenceRecord.domain,
 90 |                 InferenceRecord.approach,
 91 |                 func.avg(InferenceRecord.tokens_used).label("avg_tokens"),
 92 |                 func.avg(InferenceRecord.execution_time_ms).label("avg_time"),
 93 |                 func.avg(InferenceRecord.is_correct).label("accuracy"),
 94 |                 func.count(InferenceRecord.id).label("count")
 95 |             ).group_by(InferenceRecord.domain, InferenceRecord.approach)
 96 |             
 97 |             if domain:
 98 |                 query = query.filter(InferenceRecord.domain == domain)
 99 |                 
100 |             results = query.all()
101 |             return [
102 |                 {
103 |                     "domain": r.domain,
104 |                     "approach": r.approach,
105 |                     "avg_tokens": r.avg_tokens,
106 |                     "avg_time_ms": r.avg_time,
107 |                     "accuracy": r.accuracy if r.accuracy is not None else None,
108 |                     "count": r.count
109 |                 }
110 |                 for r in results
111 |             ]
112 |         finally:
113 |             session.close()
114 |     
115 |     async def get_token_reduction_stats(self):
116 |         """Calculate token reduction statistics for CoD vs CoT."""
117 |         session = self.Session()
118 |         try:
119 |             domains = session.query(InferenceRecord.domain).distinct().all()
120 |             results = []
121 |             
122 |             for domain_row in domains:
123 |                 domain = domain_row[0]
124 |                 
125 |                 # Get average tokens for CoD and CoT approaches in this domain
126 |                 cod_avg = session.query(func.avg(InferenceRecord.tokens_used)).filter(
127 |                     InferenceRecord.domain == domain,
128 |                     InferenceRecord.approach == "CoD"
129 |                 ).scalar() or 0
130 |                 
131 |                 cot_avg = session.query(func.avg(InferenceRecord.tokens_used)).filter(
132 |                     InferenceRecord.domain == domain,
133 |                     InferenceRecord.approach == "CoT"
134 |                 ).scalar() or 0
135 |                 
136 |                 if cot_avg > 0:
137 |                     reduction_percentage = (1 - (cod_avg / cot_avg)) * 100
138 |                 else:
139 |                     reduction_percentage = 0
140 |                     
141 |                 results.append({
142 |                     "domain": domain,
143 |                     "cod_avg_tokens": cod_avg,
144 |                     "cot_avg_tokens": cot_avg,
145 |                     "reduction_percentage": reduction_percentage
146 |                 })
147 |                 
148 |             return results
149 |         finally:
150 |             session.close()
151 |             
152 |     async def get_accuracy_comparison(self):
153 |         """Compare accuracy between CoD and CoT approaches."""
154 |         session = self.Session()
155 |         try:
156 |             domains = session.query(InferenceRecord.domain).distinct().all()
157 |             results = []
158 |             
159 |             for domain_row in domains:
160 |                 domain = domain_row[0]
161 |                 
162 |                 # Get accuracy for CoD and CoT approaches in this domain
163 |                 cod_accuracy = session.query(func.avg(InferenceRecord.is_correct)).filter(
164 |                     InferenceRecord.domain == domain,
165 |                     InferenceRecord.approach == "CoD",
166 |                     InferenceRecord.is_correct.isnot(None)
167 |                 ).scalar()
168 |                 
169 |                 cot_accuracy = session.query(func.avg(InferenceRecord.is_correct)).filter(
170 |                     InferenceRecord.domain == domain,
171 |                     InferenceRecord.approach == "CoT",
172 |                     InferenceRecord.is_correct.isnot(None)
173 |                 ).scalar()
174 |                 
175 |                 results.append({
176 |                     "domain": domain,
177 |                     "cod_accuracy": cod_accuracy,
178 |                     "cot_accuracy": cot_accuracy,
179 |                     "accuracy_difference": (cod_accuracy - cot_accuracy) if cod_accuracy and cot_accuracy else None
180 |                 })
181 |                 
182 |             return results
183 |         finally:
184 |             session.close()
185 | 
```

--------------------------------------------------------------------------------
/src/python/server.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Main server module for the Chain of Draft MCP server.
  3 | Implements the MCP server with reasoning tools.
  4 | """
  5 | 
  6 | import os
  7 | import asyncio
  8 | import time
  9 | from dotenv import load_dotenv
 10 | 
 11 | # Import the FastMCP module for modern MCP API
 12 | from mcp.server.fastmcp import FastMCP
 13 | from mcp.server.stdio import stdio_server
 14 | 
 15 | # Import other modules
 16 | from analytics import AnalyticsService
 17 | from complexity import ComplexityEstimator
 18 | from examples import ExampleDatabase
 19 | from format import FormatEnforcer
 20 | from reasoning import ReasoningSelector, create_cod_prompt, create_cot_prompt
 21 | from client import ChainOfDraftClient
 22 | 
 23 | # Load environment variables
 24 | load_dotenv()
 25 | 
 26 | # Initialize services
 27 | analytics = AnalyticsService()
 28 | complexity_estimator = ComplexityEstimator()
 29 | example_db = ExampleDatabase()
 30 | format_enforcer = FormatEnforcer()
 31 | cod_client = ChainOfDraftClient()
 32 | 
 33 | # Initialize FastMCP server
 34 | app = FastMCP("mcp-chain-of-draft-prompt-tool")
 35 | 
 36 | @app.tool()
 37 | async def chain_of_draft_solve(
 38 |     problem: str,
 39 |     domain: str = "general",
 40 |     max_words_per_step: int = None,
 41 |     approach: str = None,
 42 |     enforce_format: bool = True,
 43 |     adaptive_word_limit: bool = True
 44 | ) -> str:
 45 |     """Solve a reasoning problem using Chain of Draft approach.
 46 |     
 47 |     Args:
 48 |         problem: The problem to solve
 49 |         domain: Domain for context (math, logic, code, common-sense, etc.)
 50 |         max_words_per_step: Maximum words per reasoning step (default: adaptive)
 51 |         approach: Force "CoD" or "CoT" approach (default: auto-select)
 52 |         enforce_format: Whether to enforce the word limit (default: True)
 53 |         adaptive_word_limit: Adjust word limits based on complexity (default: True)
 54 |     """
 55 |     # Track execution time
 56 |     start_time = time.time()
 57 |     
 58 |     # Process the request with the client
 59 |     result = await cod_client.solve_with_reasoning(
 60 |         problem=problem,
 61 |         domain=domain,
 62 |         max_words_per_step=max_words_per_step,
 63 |         approach=approach,
 64 |         enforce_format=enforce_format,
 65 |         adaptive_word_limit=adaptive_word_limit
 66 |     )
 67 |     
 68 |     # Calculate execution time
 69 |     execution_time = (time.time() - start_time) * 1000  # ms
 70 |     
 71 |     # Format the response
 72 |     formatted_response = (
 73 |         f"Chain of {result['approach']} reasoning ({result['word_limit']} word limit):\n\n"
 74 |         f"{result['reasoning_steps']}\n\n"
 75 |         f"Final answer: {result['final_answer']}\n\n"
 76 |         f"Stats: {result['token_count']} tokens, {execution_time:.0f}ms, "
 77 |         f"complexity score: {result['complexity']}"
 78 |     )
 79 |     
 80 |     return formatted_response
 81 | 
 82 | @app.tool()
 83 | async def math_solve(
 84 |     problem: str,
 85 |     approach: str = None,
 86 |     max_words_per_step: int = None
 87 | ) -> str:
 88 |     """Solve a math problem using Chain of Draft reasoning.
 89 |     
 90 |     Args:
 91 |         problem: The math problem to solve
 92 |         approach: Force "CoD" or "CoT" approach (default: auto-select)
 93 |         max_words_per_step: Maximum words per step (default: adaptive)
 94 |     """
 95 |     return await chain_of_draft_solve(
 96 |         problem=problem,
 97 |         domain="math",
 98 |         approach=approach,
 99 |         max_words_per_step=max_words_per_step
100 |     )
101 | 
102 | @app.tool()
103 | async def code_solve(
104 |     problem: str,
105 |     approach: str = None,
106 |     max_words_per_step: int = None
107 | ) -> str:
108 |     """Solve a coding problem using Chain of Draft reasoning.
109 |     
110 |     Args:
111 |         problem: The coding problem to solve
112 |         approach: Force "CoD" or "CoT" approach (default: auto-select)
113 |         max_words_per_step: Maximum words per step (default: adaptive)
114 |     """
115 |     return await chain_of_draft_solve(
116 |         problem=problem,
117 |         domain="code",
118 |         approach=approach,
119 |         max_words_per_step=max_words_per_step
120 |     )
121 | 
122 | @app.tool()
123 | async def logic_solve(
124 |     problem: str,
125 |     approach: str = None,
126 |     max_words_per_step: int = None
127 | ) -> str:
128 |     """Solve a logic problem using Chain of Draft reasoning.
129 |     
130 |     Args:
131 |         problem: The logic problem to solve
132 |         approach: Force "CoD" or "CoT" approach (default: auto-select)
133 |         max_words_per_step: Maximum words per step (default: adaptive)
134 |     """
135 |     return await chain_of_draft_solve(
136 |         problem=problem,
137 |         domain="logic",
138 |         approach=approach,
139 |         max_words_per_step=max_words_per_step
140 |     )
141 | 
142 | @app.tool()
143 | async def get_performance_stats(
144 |     domain: str = None
145 | ) -> str:
146 |     """Get performance statistics for CoD vs CoT approaches.
147 |     
148 |     Args:
149 |         domain: Filter for specific domain (optional)
150 |     """
151 |     stats = await analytics.get_performance_by_domain(domain)
152 |     
153 |     result = "Performance Comparison (CoD vs CoT):\n\n"
154 |     
155 |     if not stats:
156 |         return "No performance data available yet."
157 |     
158 |     for stat in stats:
159 |         result += f"Domain: {stat['domain']}\n"
160 |         result += f"Approach: {stat['approach']}\n"
161 |         result += f"Average tokens: {stat['avg_tokens']:.1f}\n"
162 |         result += f"Average time: {stat['avg_time_ms']:.1f}ms\n"
163 |         
164 |         if stat['accuracy'] is not None:
165 |             result += f"Accuracy: {stat['accuracy'] * 100:.1f}%\n"
166 |         
167 |         result += f"Sample size: {stat['count']}\n\n"
168 |     
169 |     return result
170 | 
171 | @app.tool()
172 | async def get_token_reduction() -> str:
173 |     """Get token reduction statistics for CoD vs CoT."""
174 |     stats = await analytics.get_token_reduction_stats()
175 |     
176 |     result = "Token Reduction Analysis:\n\n"
177 |     
178 |     if not stats:
179 |         return "No reduction data available yet."
180 |     
181 |     for stat in stats:
182 |         result += f"Domain: {stat['domain']}\n"
183 |         result += f"CoD avg tokens: {stat['cod_avg_tokens']:.1f}\n"
184 |         result += f"CoT avg tokens: {stat['cot_avg_tokens']:.1f}\n"
185 |         result += f"Reduction: {stat['reduction_percentage']:.1f}%\n\n"
186 |     
187 |     return result
188 | 
189 | @app.tool()
190 | async def analyze_problem_complexity(
191 |     problem: str,
192 |     domain: str = "general"
193 | ) -> str:
194 |     """Analyze the complexity of a problem.
195 |     
196 |     Args:
197 |         problem: The problem to analyze
198 |         domain: Problem domain
199 |     """
200 |     analysis = complexity_estimator.analyze_problem(problem, domain)
201 |     
202 |     result = f"Complexity Analysis for {domain} problem:\n\n"
203 |     result += f"Word count: {analysis['word_count']}\n"
204 |     result += f"Sentence count: {analysis['sentence_count']}\n"
205 |     result += f"Words per sentence: {analysis['words_per_sentence']:.1f}\n"
206 |     result += f"Complexity indicators found: {analysis['indicator_count']}\n"
207 |     
208 |     if analysis['found_indicators']:
209 |         result += f"Indicators: {', '.join(analysis['found_indicators'])}\n"
210 |     
211 |     result += f"Question count: {analysis['question_count']}\n"
212 |     result += f"\nEstimated complexity score: {analysis['estimated_complexity']}\n"
213 |     result += f"Recommended word limit per step: {analysis['estimated_complexity']}\n"
214 |     
215 |     return result
216 | 
217 | async def main():
218 |     """Main entry point for the MCP server."""
219 |     # Initialize example database
220 |     await example_db.get_examples("math")  # This will trigger example loading if needed
221 |     
222 |     # Print startup message
223 |     import sys
224 |     print("Chain of Draft MCP Server starting...", file=sys.stderr)
225 | 
226 | if __name__ == "__main__":
227 |     import sys
228 |     
229 |     # Print debug information
230 |     print("Python version:", sys.version, file=sys.stderr)
231 |     print("Running in directory:", os.getcwd(), file=sys.stderr)
232 |     print("Environment variables:", dict(os.environ), file=sys.stderr)
233 |     
234 |     try:
235 |         # Run the example initialization
236 |         asyncio.run(main())
237 |         
238 |         # Start the server with stdio transport
239 |         print("Starting FastMCP server with stdio transport...", file=sys.stderr)
240 |         # Cannot use additional options directly, use environment variables instead
241 |         os.environ['MCP_DEBUG'] = '1'
242 |         os.environ['MCP_LOG_TRANSPORT'] = '1'
243 |         app.run(transport="stdio")
244 |     except Exception as e:
245 |         print(f"Error in server startup: {e}", file=sys.stderr)
246 |         import traceback
247 |         traceback.print_exc(file=sys.stderr)
248 |         sys.exit(1)
249 | 
```

--------------------------------------------------------------------------------
/src/python/examples.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Example database management for the Chain of Draft MCP server.
  3 | Stores and retrieves example problems and solutions.
  4 | """
  5 | 
  6 | import os
  7 | from sqlalchemy import create_engine, Column, Integer, String, Text, JSON
  8 | from sqlalchemy.ext.declarative import declarative_base
  9 | from sqlalchemy.orm import sessionmaker
 10 | 
 11 | Base = declarative_base()
 12 | 
 13 | class Example(Base):
 14 |     """Database model for storing reasoning examples."""
 15 |     __tablename__ = 'examples'
 16 |     
 17 |     id = Column(Integer, primary_key=True)
 18 |     problem = Column(Text, nullable=False)
 19 |     reasoning = Column(Text, nullable=False)
 20 |     answer = Column(String, nullable=False)
 21 |     domain = Column(String, nullable=False)
 22 |     approach = Column(String, nullable=False)  # "CoD" or "CoT"
 23 |     meta_data = Column(JSON, nullable=True)  # Changed from metadata to avoid SQLAlchemy reserved keyword
 24 | 
 25 | 
 26 | class ExampleDatabase:
 27 |     """Manages a database of reasoning examples for few-shot prompting."""
 28 |     
 29 |     def __init__(self, db_path=None):
 30 |         """Initialize the example database with a connection."""
 31 |         if db_path is None:
 32 |             # Default to SQLite in the current directory
 33 |             db_path = os.environ.get("COD_EXAMPLES_DB", "cod_examples.db")
 34 |             
 35 |         self.engine = create_engine(f"sqlite:///{db_path}")
 36 |         Base.metadata.create_all(self.engine)
 37 |         self.Session = sessionmaker(bind=self.engine)
 38 |         
 39 |         # Initialize with some examples if empty
 40 |         self._ensure_examples_exist()
 41 |     
 42 |     def _ensure_examples_exist(self):
 43 |         """Check if examples exist and seed the database if empty."""
 44 |         session = self.Session()
 45 |         count = session.query(Example).count()
 46 |         session.close()
 47 |         
 48 |         if count == 0:
 49 |             self._load_initial_examples()
 50 |     
 51 |     def _load_initial_examples(self):
 52 |         """Load initial examples into the database."""
 53 |         # Add some basic examples across domains
 54 |         examples = [
 55 |             # Math example (CoT)
 56 |             {
 57 |                 "problem": "Jason had 20 lollipops. He gave Denny some lollipops. Now Jason has 12 lollipops. How many lollipops did Jason give to Denny?",
 58 |                 "reasoning": "Let's think through this step by step:\n1. Initially, Jason had 20 lollipops.\n2. After giving some to Denny, Jason now has 12 lollipops.\n3. To find out how many lollipops Jason gave to Denny, we need to calculate the difference between the initial number of lollipops and the remaining number.\n4. We can set up a simple subtraction problem: Initial number of lollipops - Remaining number of lollipops = Lollipops given to Denny\n5. Putting in the numbers: 20 - 12 = Lollipops given to Denny\n6. Solving the subtraction: 20 - 12 = 8",
 59 |                 "answer": "8 lollipops",
 60 |                 "domain": "math",
 61 |                 "approach": "CoT"
 62 |             },
 63 |             # Math example (CoD)
 64 |             {
 65 |                 "problem": "Jason had 20 lollipops. He gave Denny some lollipops. Now Jason has 12 lollipops. How many lollipops did Jason give to Denny?",
 66 |                 "reasoning": "Initial: 20 lollipops\nRemaining: 12 lollipops\nGave away: 20-12=8 lollipops",
 67 |                 "answer": "8 lollipops",
 68 |                 "domain": "math",
 69 |                 "approach": "CoD"
 70 |             },
 71 |             # Logic example (CoT)
 72 |             {
 73 |                 "problem": "A coin is heads up. John flips the coin. Mary flips the coin. Paul flips the coin. Susan does not flip the coin. Is the coin still heads up?",
 74 |                 "reasoning": "Let's track the state of the coin through each flip:\n1. Initially, the coin is heads up.\n2. John flips the coin, so it changes from heads to tails.\n3. Mary flips the coin, so it changes from tails to heads.\n4. Paul flips the coin, so it changes from heads to tails.\n5. Susan does not flip the coin, so it remains tails.\nTherefore, the coin is tails up, which means it is not still heads up.",
 75 |                 "answer": "No",
 76 |                 "domain": "logic",
 77 |                 "approach": "CoT"
 78 |             },
 79 |             # Logic example (CoD)
 80 |             {
 81 |                 "problem": "A coin is heads up. John flips the coin. Mary flips the coin. Paul flips the coin. Susan does not flip the coin. Is the coin still heads up?",
 82 |                 "reasoning": "H→J flips→T\nT→M flips→H\nH→P flips→T\nT→S no flip→T\nFinal: tails",
 83 |                 "answer": "No",
 84 |                 "domain": "logic",
 85 |                 "approach": "CoD"
 86 |             },
 87 |             # Physics example (CoT)
 88 |             {
 89 |                 "problem": "A car accelerates from 0 to 60 mph in 5 seconds. What is its acceleration in mph/s?",
 90 |                 "reasoning": "Let's solve this problem step by step:\n1. We know the initial velocity is 0 mph.\n2. The final velocity is 60 mph.\n3. The time taken is 5 seconds.\n4. Acceleration is the rate of change of velocity with respect to time.\n5. Using the formula: acceleration = (final velocity - initial velocity) / time\n6. Substituting the values: acceleration = (60 mph - 0 mph) / 5 seconds\n7. Simplifying: acceleration = 60 mph / 5 seconds = 12 mph/s",
 91 |                 "answer": "12 mph/s",
 92 |                 "domain": "physics",
 93 |                 "approach": "CoT"
 94 |             },
 95 |             # Physics example (CoD)
 96 |             {
 97 |                 "problem": "A car accelerates from 0 to 60 mph in 5 seconds. What is its acceleration in mph/s?",
 98 |                 "reasoning": "a = Δv/Δt\na = (60-0)/5\na = 12 mph/s",
 99 |                 "answer": "12 mph/s",
100 |                 "domain": "physics",
101 |                 "approach": "CoD"
102 |             }
103 |         ]
104 |         
105 |         # Add examples to database
106 |         session = self.Session()
107 |         try:
108 |             for example in examples:
109 |                 session.add(Example(**example))
110 |             session.commit()
111 |         finally:
112 |             session.close()
113 |     
114 |     async def get_examples(self, domain, approach="CoD", limit=3):
115 |         """Get examples for a specific domain and approach."""
116 |         session = self.Session()
117 |         try:
118 |             examples = session.query(Example).filter_by(
119 |                 domain=domain, approach=approach
120 |             ).limit(limit).all()
121 |             
122 |             return [
123 |                 {
124 |                     "problem": ex.problem,
125 |                     "reasoning": ex.reasoning,
126 |                     "answer": ex.answer,
127 |                     "domain": ex.domain,
128 |                     "approach": ex.approach
129 |                 }
130 |                 for ex in examples
131 |             ]
132 |         finally:
133 |             session.close()
134 |     
135 |     async def add_example(self, problem, reasoning, answer, domain, approach="CoD", metadata=None):
136 |         """Add a new example to the database."""
137 |         session = self.Session()
138 |         try:
139 |             example = Example(
140 |                 problem=problem,
141 |                 reasoning=reasoning,
142 |                 answer=answer,
143 |                 domain=domain,
144 |                 approach=approach,
145 |                 meta_data=metadata
146 |             )
147 |             session.add(example)
148 |             session.commit()
149 |             return example.id
150 |         finally:
151 |             session.close()
152 |     
153 |     async def transform_cot_to_cod(self, cot_example, max_words_per_step=5):
154 |         """Transform a CoT example into CoD format with word limit per step."""
155 |         # Extract steps from CoT
156 |         steps = self._extract_reasoning_steps(cot_example["reasoning"])
157 |         
158 |         # Transform each step to be more concise
159 |         cod_steps = []
160 |         for step in steps:
161 |             # This could use an LLM to summarize, or rules-based approach
162 |             cod_step = self._summarize_step(step, max_words_per_step)
163 |             cod_steps.append(cod_step)
164 |         
165 |         # Create CoD version
166 |         return {
167 |             "problem": cot_example["problem"],
168 |             "reasoning": "\n".join(cod_steps),
169 |             "answer": cot_example["answer"],
170 |             "domain": cot_example["domain"],
171 |             "approach": "CoD"
172 |         }
173 |     
174 |     def _extract_reasoning_steps(self, reasoning):
175 |         """Extract individual reasoning steps from CoT reasoning."""
176 |         # Simple approach: split by numbered steps or line breaks
177 |         if any(f"{i}." in reasoning for i in range(1, 10)):
178 |             # Numbered steps
179 |             import re
180 |             steps = re.split(r'\d+\.', reasoning)
181 |             return [s.strip() for s in steps if s.strip()]
182 |         else:
183 |             # Split by line
184 |             return [s.strip() for s in reasoning.split('\n') if s.strip()]
185 |     
186 |     def _summarize_step(self, step, max_words):
187 |         """Summarize a reasoning step to meet the word limit."""
188 |         # For now, just truncate - in production, would use an LLM to summarize
189 |         words = step.split()
190 |         if len(words) <= max_words:
191 |             return step
192 |         
193 |         # Simple heuristic: keep first few words focusing on calculation/operation
194 |         return " ".join(words[:max_words])
195 |     
196 |     async def get_example_count_by_domain(self):
197 |         """Get count of examples by domain and approach."""
198 |         session = self.Session()
199 |         try:
200 |             from sqlalchemy import func
201 |             results = session.query(
202 |                 Example.domain,
203 |                 Example.approach,
204 |                 func.count(Example.id).label("count")
205 |             ).group_by(Example.domain, Example.approach).all()
206 |             
207 |             return [
208 |                 {
209 |                     "domain": r[0],
210 |                     "approach": r[1],
211 |                     "count": r[2]
212 |                 }
213 |                 for r in results
214 |             ]
215 |         finally:
216 |             session.close()
217 | 
```

--------------------------------------------------------------------------------
/src/python/client.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | OpenAI-compatible client wrapper for the Chain of Draft MCP server.
  3 | Provides a drop-in replacement for OpenAI and Anthropic clients.
  4 | """
  5 | 
  6 | import os
  7 | import time
  8 | import uuid
  9 | import anthropic
 10 | import openai
 11 | from mistralai.client import MistralClient
 12 | from mistralai.models.chat_completion import ChatMessage
 13 | import requests
 14 | from dotenv import load_dotenv
 15 | 
 16 | from analytics import AnalyticsService
 17 | from complexity import ComplexityEstimator
 18 | from examples import ExampleDatabase
 19 | from format import FormatEnforcer
 20 | from reasoning import ReasoningSelector, create_cod_prompt, create_cot_prompt
 21 | 
 22 | # Load environment variables
 23 | load_dotenv()
 24 | 
 25 | class UnifiedLLMClient:
 26 |     """
 27 |     Unified client that supports multiple LLM providers (Anthropic, OpenAI, Mistral, Ollama).
 28 |     """
 29 |     
 30 |     def __init__(self):
 31 |         """Initialize the appropriate client based on environment variables."""
 32 |         self.provider = os.getenv("LLM_PROVIDER", "anthropic").lower()
 33 |         self.model = os.getenv("LLM_MODEL", "claude-3-7-sonnet-latest")
 34 |         
 35 |         # Initialize the appropriate client
 36 |         if self.provider == "anthropic":
 37 |             if not os.getenv("ANTHROPIC_API_KEY"):
 38 |                 raise ValueError("ANTHROPIC_API_KEY is required for Anthropic provider")
 39 |             self.client = anthropic.Anthropic(
 40 |                 api_key=os.getenv("ANTHROPIC_API_KEY"),
 41 |                 base_url=os.getenv("ANTHROPIC_BASE_URL")
 42 |             )
 43 |         
 44 |         elif self.provider == "openai":
 45 |             if not os.getenv("OPENAI_API_KEY"):
 46 |                 raise ValueError("OPENAI_API_KEY is required for OpenAI provider")
 47 |             self.client = openai.OpenAI(
 48 |                 api_key=os.getenv("OPENAI_API_KEY"),
 49 |                 base_url=os.getenv("OPENAI_BASE_URL")
 50 |             )
 51 |         
 52 |         elif self.provider == "mistral":
 53 |             if not os.getenv("MISTRAL_API_KEY"):
 54 |                 raise ValueError("MISTRAL_API_KEY is required for Mistral provider")
 55 |             self.client = MistralClient(
 56 |                 api_key=os.getenv("MISTRAL_API_KEY")
 57 |             )
 58 |         
 59 |         elif self.provider == "ollama":
 60 |             self.base_url = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
 61 |         
 62 |         else:
 63 |             raise ValueError(f"Unsupported LLM provider: {self.provider}")
 64 |     
 65 |     async def get_available_models(self):
 66 |         """Get list of available models from the current provider."""
 67 |         try:
 68 |             if self.provider == "anthropic":
 69 |                 response = await self.client.models.list()
 70 |                 return [model.id for model in response.data]
 71 |             
 72 |             elif self.provider == "openai":
 73 |                 response = await self.client.models.list()
 74 |                 return [model.id for model in response.data]
 75 |             
 76 |             elif self.provider == "mistral":
 77 |                 response = await self.client.list_models()
 78 |                 return [model.id for model in response.data] if response.data else []
 79 |             
 80 |             elif self.provider == "ollama":
 81 |                 response = requests.get(f"{self.base_url}/api/tags")
 82 |                 return [model["name"] for model in response.json()["models"]]
 83 |             
 84 |             return []
 85 |         
 86 |         except Exception as e:
 87 |             print(f"Error fetching models: {e}")
 88 |             return []
 89 |     
 90 |     async def chat(self, messages, model=None, max_tokens=None, temperature=None):
 91 |         """
 92 |         Send chat messages to the current provider.
 93 |         
 94 |         Args:
 95 |             messages: List of message dicts with 'role' and 'content'
 96 |             model: Optional model override
 97 |             max_tokens: Optional max tokens limit
 98 |             temperature: Optional temperature setting
 99 |             
100 |         Returns:
101 |             Dict with response content and token usage
102 |         """
103 |         model = model or self.model
104 |         
105 |         try:
106 |             if self.provider == "anthropic":
107 |                 response = await self.client.messages.create(
108 |                     model=model,
109 |                     messages=[{
110 |                         "role": msg["role"],
111 |                         "content": msg["content"]
112 |                     } for msg in messages],
113 |                     max_tokens=max_tokens,
114 |                     temperature=temperature
115 |                 )
116 |                 return {
117 |                     "content": response.content[0].text,
118 |                     "usage": {
119 |                         "input_tokens": response.usage.input_tokens,
120 |                         "output_tokens": response.usage.output_tokens
121 |                     }
122 |                 }
123 |             
124 |             elif self.provider == "openai":
125 |                 response = await self.client.chat.completions.create(
126 |                     model=model,
127 |                     messages=messages,
128 |                     max_tokens=max_tokens,
129 |                     temperature=temperature
130 |                 )
131 |                 return {
132 |                     "content": response.choices[0].message.content,
133 |                     "usage": {
134 |                         "input_tokens": response.usage.prompt_tokens,
135 |                         "output_tokens": response.usage.completion_tokens
136 |                     }
137 |                 }
138 |             
139 |             elif self.provider == "mistral":
140 |                 response = await self.client.chat(
141 |                     model=model,
142 |                     messages=[ChatMessage(
143 |                         role=msg["role"],
144 |                         content=msg["content"]
145 |                     ) for msg in messages],
146 |                     max_tokens=max_tokens,
147 |                     temperature=temperature
148 |                 )
149 |                 return {
150 |                     "content": response.choices[0].message.content,
151 |                     "usage": {
152 |                         "input_tokens": response.usage.prompt_tokens,
153 |                         "output_tokens": response.usage.completion_tokens
154 |                     }
155 |                 }
156 |             
157 |             elif self.provider == "ollama":
158 |                 response = requests.post(
159 |                     f"{self.base_url}/api/chat",
160 |                     json={
161 |                         "model": model,
162 |                         "messages": messages,
163 |                         "options": {
164 |                             "num_predict": max_tokens,
165 |                             "temperature": temperature
166 |                         }
167 |                     }
168 |                 )
169 |                 data = response.json()
170 |                 return {
171 |                     "content": data["message"]["content"],
172 |                     "usage": {
173 |                         "input_tokens": 0,  # Ollama doesn't provide token counts
174 |                         "output_tokens": 0
175 |                     }
176 |                 }
177 |             
178 |             raise ValueError(f"Unsupported provider: {self.provider}")
179 |         
180 |         except Exception as e:
181 |             print(f"Error in chat: {e}")
182 |             raise
183 | 
184 | class ChainOfDraftClient:
185 |     """
186 |     Drop-in replacement for OpenAI client that uses Chain of Draft reasoning.
187 |     Provides both OpenAI and Anthropic-compatible interfaces.
188 |     """
189 |     
190 |     def __init__(self, api_key=None, base_url=None, **kwargs):
191 |         """Initialize the client with optional API key and settings."""
192 |         # Initialize the unified LLM client
193 |         self.llm_client = UnifiedLLMClient()
194 |         
195 |         # Initialize services
196 |         self.analytics = AnalyticsService()
197 |         self.complexity_estimator = ComplexityEstimator()
198 |         self.example_db = ExampleDatabase()
199 |         self.format_enforcer = FormatEnforcer()
200 |         self.reasoning_selector = ReasoningSelector(self.analytics)
201 |         
202 |         # Default settings
203 |         self.default_settings = {
204 |             "max_words_per_step": 8,
205 |             "enforce_format": True,
206 |             "adaptive_word_limit": True,
207 |             "track_analytics": True,
208 |             "max_tokens": 200000
209 |         }
210 |         
211 |         # Update with any provided kwargs
212 |         self.settings = {**self.default_settings, **kwargs}
213 |     
214 |     # OpenAI-style completions
215 |     async def completions(self, model=None, prompt=None, **kwargs):
216 |         """
217 |         OpenAI-compatible completions interface.
218 |         
219 |         Args:
220 |             model: Model to use (default from settings)
221 |             prompt: The problem to solve
222 |             **kwargs: Additional parameters including domain
223 |             
224 |         Returns:
225 |             OpenAI-style completion response
226 |         """
227 |         if not prompt:
228 |             raise ValueError("Prompt is required")
229 |             
230 |         # Extract reasoning problem from prompt
231 |         problem = prompt
232 |         
233 |         # Determine domain from kwargs or infer
234 |         domain = kwargs.get("domain", "general")
235 |         
236 |         # Process reasoning request
237 |         result = await self.solve_with_reasoning(
238 |             problem, 
239 |             domain, 
240 |             model=model or self.settings["model"],
241 |             **kwargs
242 |         )
243 |         
244 |         # Format in OpenAI style response
245 |         return {
246 |             "id": f"cod-{uuid.uuid4()}",
247 |             "object": "completion",
248 |             "created": int(time.time()),
249 |             "model": model or self.settings["model"],
250 |             "choices": [{
251 |                 "text": result["final_answer"],
252 |                 "index": 0,
253 |                 "finish_reason": "stop"
254 |             }],
255 |             "usage": {
256 |                 "prompt_tokens": len(prompt.split()),
257 |                 "completion_tokens": result["token_count"],
258 |                 "total_tokens": len(prompt.split()) + result["token_count"]
259 |             },
260 |             # Add custom fields for CoD-specific data
261 |             "reasoning": result["reasoning_steps"],
262 |             "approach": result["approach"]
263 |         }
264 |     
265 |     # ChatCompletions-style method
266 |     async def chat(self, model=None, messages=None, **kwargs):
267 |         """
268 |         OpenAI-compatible chat completions interface.
269 |         
270 |         Args:
271 |             model: Model to use (default from settings)
272 |             messages: Chat history with the last user message as the problem
273 |             **kwargs: Additional parameters including domain
274 |             
275 |         Returns:
276 |             OpenAI-style chat completion response
277 |         """
278 |         if not messages:
279 |             raise ValueError("Messages are required")
280 |             
281 |         # Extract last user message as the problem
282 |         last_user_msg = next((m["content"] for m in reversed(messages) 
283 |                              if m["role"] == "user"), "")
284 |         
285 |         if not last_user_msg:
286 |             raise ValueError("No user message found in the provided messages")
287 |         
288 |         # Determine domain from kwargs or infer
289 |         domain = kwargs.get("domain", "general")
290 |         
291 |         # Process reasoning request
292 |         result = await self.solve_with_reasoning(
293 |             last_user_msg, 
294 |             domain, 
295 |             model=model or self.settings["model"],
296 |             **kwargs
297 |         )
298 |         
299 |         # Format in OpenAI style response
300 |         return {
301 |             "id": f"cod-{uuid.uuid4()}",
302 |             "object": "chat.completion",
303 |             "created": int(time.time()),
304 |             "model": model or self.settings["model"],
305 |             "choices": [{
306 |                 "index": 0,
307 |                 "message": {
308 |                     "role": "assistant",
309 |                     "content": f"{result['reasoning_steps']}\n\n####\n{result['final_answer']}"
310 |                 },
311 |                 "finish_reason": "stop"
312 |             }],
313 |             "usage": {
314 |                 "prompt_tokens": sum(len(m.get("content", "").split()) for m in messages),
315 |                 "completion_tokens": result["token_count"],
316 |                 "total_tokens": sum(len(m.get("content", "").split()) for m in messages) + result["token_count"]
317 |             }
318 |         }
319 |     
320 |     # Anthropic-style messages
321 |     async def messages(self, model=None, messages=None, **kwargs):
322 |         """
323 |         Anthropic-compatible messages interface.
324 |         
325 |         Args:
326 |             model: Model to use (default from settings)
327 |             messages: Chat history with the last user message as the problem
328 |             **kwargs: Additional parameters including domain
329 |             
330 |         Returns:
331 |             Anthropic-style message response
332 |         """
333 |         if not messages:
334 |             raise ValueError("Messages are required")
335 |             
336 |         # Extract last user message as the problem
337 |         last_user_msg = next((m["content"] for m in reversed(messages) 
338 |                              if m["role"] == "user"), "")
339 |         
340 |         if not last_user_msg:
341 |             raise ValueError("No user message found in the provided messages")
342 |         
343 |         # Determine domain from kwargs or infer
344 |         domain = kwargs.get("domain", "general")
345 |         
346 |         # Process reasoning request
347 |         result = await self.solve_with_reasoning(
348 |             last_user_msg, 
349 |             domain, 
350 |             model=model or self.settings["model"],
351 |             **kwargs
352 |         )
353 |         
354 |         # Format in Anthropic style response
355 |         return {
356 |             "id": f"msg_{uuid.uuid4()}",
357 |             "type": "message",
358 |             "role": "assistant",
359 |             "model": model or self.settings["model"],
360 |             "content": [
361 |                 {
362 |                     "type": "text",
363 |                     "text": f"{result['reasoning_steps']}\n\n####\n{result['final_answer']}"
364 |                 }
365 |             ],
366 |             "usage": {
367 |                 "input_tokens": sum(len(m.get("content", "").split()) for m in messages),
368 |                 "output_tokens": result["token_count"]
369 |             },
370 |             # Add custom fields
371 |             "reasoning_approach": result["approach"],
372 |             "word_limit": result["word_limit"]
373 |         }
374 |     
375 |     # Core reasoning implementation
376 |     async def solve_with_reasoning(self, problem, domain="general", **kwargs):
377 |         """
378 |         Solve a problem using the appropriate reasoning approach.
379 |         
380 |         Args:
381 |             problem: The problem text
382 |             domain: Problem domain (math, code, logic, etc.)
383 |             **kwargs: Additional parameters and settings
384 |             
385 |         Returns:
386 |             Dictionary with reasoning steps and answer
387 |         """
388 |         start_time = time.time()
389 |         
390 |         # Override settings with kwargs
391 |         local_settings = {**self.settings, **kwargs}
392 |         
393 |         # Determine complexity and select approach
394 |         complexity = await self.complexity_estimator.estimate_complexity(problem, domain)
395 |         
396 |         if local_settings.get("approach"):
397 |             # Manually specified approach
398 |             approach = local_settings["approach"]
399 |             approach_reason = "Manually specified"
400 |         else:
401 |             # Auto-select based on problem
402 |             approach, approach_reason = await self.reasoning_selector.select_approach(
403 |                 problem, domain, complexity
404 |             )
405 |         
406 |         # Determine word limit
407 |         if local_settings["adaptive_word_limit"] and approach == "CoD":
408 |             word_limit = complexity  # Use estimated complexity as word limit
409 |         else:
410 |             word_limit = local_settings["max_words_per_step"]
411 |         
412 |         # Get examples
413 |         examples = await self.example_db.get_examples(domain, approach)
414 |         
415 |         # Create prompt based on approach
416 |         if approach == "CoD":
417 |             prompt = create_cod_prompt(problem, domain, word_limit, examples)
418 |         else:
419 |             prompt = create_cot_prompt(problem, domain, examples)
420 |         
421 |         # Generate response from LLM
422 |         response = await self.llm_client.chat(
423 |             [{"role": "user", "content": prompt["user"]}],
424 |             model=local_settings.get("model", "claude-3-7-sonnet-latest"),
425 |             max_tokens=local_settings.get("max_tokens", 500),
426 |             temperature=local_settings.get("temperature", 0.7)
427 |         )
428 |         
429 |         # Extract reasoning and answer
430 |         full_response = response["content"]
431 |         parts = full_response.split("####")
432 |         
433 |         reasoning = parts[0].strip()
434 |         answer = parts[1].strip() if len(parts) > 1 else "No clear answer found"
435 |         
436 |         # Apply format enforcement if needed
437 |         if local_settings["enforce_format"] and approach == "CoD":
438 |             reasoning = self.format_enforcer.enforce_word_limit(reasoning, word_limit)
439 |             adherence = self.format_enforcer.analyze_adherence(reasoning, word_limit)
440 |         else:
441 |             adherence = None
442 |         
443 |         # Record analytics
444 |         if local_settings["track_analytics"]:
445 |             execution_time = (time.time() - start_time) * 1000  # ms
446 |             await self.analytics.record_inference(
447 |                 problem=problem,
448 |                 domain=domain,
449 |                 approach=approach,
450 |                 word_limit=word_limit,
451 |                 tokens_used=len(full_response.split()),
452 |                 execution_time=execution_time,
453 |                 reasoning=reasoning,
454 |                 answer=answer,
455 |                 metadata={
456 |                     "complexity": complexity,
457 |                     "approach_reason": approach_reason,
458 |                     "adherence": adherence
459 |                 }
460 |             )
461 |         
462 |         return {
463 |             "reasoning_steps": reasoning,
464 |             "final_answer": answer,
465 |             "token_count": len(full_response.split()),
466 |             "approach": approach,
467 |             "complexity": complexity,
468 |             "word_limit": word_limit
469 |         }
470 |     
471 |     # Utility methods
472 |     async def get_performance_stats(self, domain=None):
473 |         """Get performance statistics for CoD vs CoT approaches."""
474 |         return await self.analytics.get_performance_by_domain(domain)
475 |     
476 |     async def get_token_reduction_stats(self):
477 |         """Get token reduction statistics for CoD vs CoT."""
478 |         return await self.analytics.get_token_reduction_stats()
479 |     
480 |     def update_settings(self, **kwargs):
481 |         """Update the client settings."""
482 |         self.settings.update(kwargs)
483 |         return self.settings
484 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
   1 | #!/usr/bin/env node
   2 | 
   3 | /**
   4 |  * Chain of Draft (CoD) MCP Server in TypeScript
   5 |  * Implements the MCP server for Chain of Draft reasoning
   6 |  */
   7 | 
   8 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
   9 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  10 | import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
  11 | import { Anthropic } from "@anthropic-ai/sdk";
  12 | import OpenAI from "openai";
  13 | import { Mistral } from "@mistralai/mistralai";
  14 | import { Ollama, type ListResponse, type ModelResponse } from "ollama";
  15 | import dotenv from "dotenv";
  16 | import {
  17 |   AnalyticsRecord,
  18 |   PerformanceStats,
  19 |   TokenReductionStats,
  20 |   ComplexityAnalysis,
  21 |   ChainOfDraftParams,
  22 |   ChainOfDraftResult,
  23 |   AnalyticsDatabase,
  24 |   ComplexityEstimator,
  25 |   FormatEnforcer,
  26 |   ReasoningSelector,
  27 |   ToolArguments
  28 | } from './types';
  29 | import { logger } from './utils/logger.js';
  30 | 
  31 | // Common interfaces for response types
  32 | interface LLMMessage {
  33 |   role: 'user' | 'assistant' | 'system';
  34 |   content: string;
  35 | }
  36 | 
  37 | interface LLMResponse {
  38 |   content: string;
  39 |   usage?: {
  40 |     input_tokens: number;
  41 |     output_tokens: number;
  42 |   };
  43 | }
  44 | 
  45 | // Type for model responses
  46 | interface ModelInfo {
  47 |   id: string;
  48 |   name?: string;
  49 | }
  50 | 
  51 | // Load environment variables
  52 | dotenv.config();
  53 | 
  54 | class UnifiedLLMClient {
  55 |   private anthropicClient?: Anthropic;
  56 |   private openaiClient?: OpenAI;
  57 |   private mistralClient?: Mistral;
  58 |   private ollamaClient?: Ollama;
  59 |   private provider: string;
  60 |   private model: string;
  61 | 
  62 |   constructor() {
  63 |     // Load from environment variables with defaults
  64 |     this.provider = process.env.LLM_PROVIDER?.toLowerCase() || 'anthropic';
  65 |     this.model = process.env.LLM_MODEL || 'claude-3-7-sonnet-latest';
  66 | 
  67 |     logger.devLog('Initializing LLM client', {
  68 |       provider: this.provider,
  69 |       model: this.model,
  70 |       env: process.env.NODE_ENV
  71 |     });
  72 | 
  73 |     // Initialize the appropriate client based on provider
  74 |     this.initializeClient();
  75 |   }
  76 | 
  77 |   private initializeClient() {
  78 |     logger.devLog('Initializing LLM provider:', this.provider);
  79 |     
  80 |     switch (this.provider) {
  81 |       case 'anthropic':
  82 |         if (!process.env.ANTHROPIC_API_KEY) {
  83 |           throw new Error('ANTHROPIC_API_KEY is required for Anthropic provider');
  84 |         }
  85 |         this.anthropicClient = new Anthropic({
  86 |           apiKey: process.env.ANTHROPIC_API_KEY,
  87 |           baseURL: process.env.ANTHROPIC_BASE_URL
  88 |         });
  89 |         break;
  90 | 
  91 |       case 'openai':
  92 |         if (!process.env.OPENAI_API_KEY) {
  93 |           throw new Error('OPENAI_API_KEY is required for OpenAI provider');
  94 |         }
  95 |         this.openaiClient = new OpenAI({
  96 |           apiKey: process.env.OPENAI_API_KEY,
  97 |           baseURL: process.env.OPENAI_BASE_URL
  98 |         });
  99 |         break;
 100 | 
 101 |       case 'mistral':
 102 |         if (!process.env.MISTRAL_API_KEY) {
 103 |           throw new Error('MISTRAL_API_KEY is required for Mistral provider');
 104 |         }
 105 |         this.mistralClient = new Mistral({
 106 |           apiKey: process.env.MISTRAL_API_KEY
 107 |         });
 108 |         break;
 109 | 
 110 |       case 'ollama':
 111 |         this.ollamaClient = new Ollama({
 112 |           host: process.env.OLLAMA_BASE_URL || 'http://localhost:11434'
 113 |         });
 114 |         break;
 115 | 
 116 |       default:
 117 |         throw new Error(`Unsupported LLM provider: ${this.provider}`);
 118 |     }
 119 |   }
 120 | 
 121 |   async getAvailableModels(): Promise<string[]> {
 122 |     logger.devLog('Fetching available models');
 123 |     try {
 124 |       switch (this.provider) {
 125 |         case 'anthropic': {
 126 |           const response = await fetch(`${process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com'}/v1/models`, {
 127 |             headers: {
 128 |               'x-api-key': process.env.ANTHROPIC_API_KEY || '',
 129 |               'anthropic-version': '2023-06-01'
 130 |             }
 131 |           });
 132 |           const data = await response.json();
 133 |           return data.models.map((model: ModelInfo) => model.id);
 134 |         }
 135 | 
 136 |         case 'openai':
 137 |           const openaiModels = await this.openaiClient!.models.list();
 138 |           return openaiModels.data.map((model: ModelInfo) => model.id);
 139 | 
 140 |         case 'mistral': {
 141 |           const response = await this.mistralClient!.models.list();
 142 |           return (response.data || []).map(model => model.id);
 143 |         }
 144 | 
 145 |         case 'ollama':
 146 |           const ollamaModels = await this.ollamaClient!.list();
 147 |           return ollamaModels.models.map((model: ModelResponse) => model.name);
 148 | 
 149 |         default:
 150 |           return [];
 151 |       }
 152 |     } catch (error) {
 153 |       logger.error('Error fetching models:', error);
 154 |       logger.devLog('Detailed error:', {
 155 |         name: error instanceof Error ? error.name : 'Unknown',
 156 |         message: error instanceof Error ? error.message : String(error),
 157 |         stack: error instanceof Error ? error.stack : undefined
 158 |       });
 159 |       return [];
 160 |     }
 161 |   }
 162 | 
 163 |   async chat(messages: LLMMessage[], options: {
 164 |     model?: string;
 165 |     max_tokens?: number;
 166 |     temperature?: number;
 167 |   } = {}): Promise<LLMResponse> {
 168 |     const model = options.model || this.model;
 169 |     logger.devLog('Initiating chat:', {
 170 |       model,
 171 |       messageCount: messages.length,
 172 |       options
 173 |     });
 174 | 
 175 |     const max_tokens = options.max_tokens || undefined;
 176 |     const temperature = options.temperature || undefined;
 177 | 
 178 |     try {
 179 |       switch (this.provider) {
 180 |         case 'anthropic': {
 181 |           const response = await this.anthropicClient!.messages.create({
 182 |             model,
 183 |             messages: messages.map(m => ({
 184 |               role: m.role as 'user' | 'assistant',
 185 |               content: m.content
 186 |             })),
 187 |             max_tokens: max_tokens || 1000,
 188 |             temperature: temperature || 0.7
 189 |           });
 190 |           
 191 |           // Handle different content block types safely
 192 |           const content = response.content[0];
 193 |           let textContent = '';
 194 |           
 195 |           try {
 196 |             if (content && typeof content === 'object') {
 197 |               // Try to access text property safely
 198 |               const text = 'text' in content ? content.text : null;
 199 |               textContent = text ? String(text) : JSON.stringify(content);
 200 |             } else {
 201 |               textContent = String(content);
 202 |             }
 203 |           } catch (e) {
 204 |             textContent = JSON.stringify(content);
 205 |           }
 206 |           
 207 |           return {
 208 |             content: textContent,
 209 |             usage: {
 210 |               input_tokens: response.usage?.input_tokens || 0,
 211 |               output_tokens: response.usage?.output_tokens || 0
 212 |             }
 213 |           };
 214 |         }
 215 | 
 216 |         case 'openai': {
 217 |           const response = await this.openaiClient!.chat.completions.create({
 218 |             model,
 219 |             messages,
 220 |             max_tokens,
 221 |             temperature
 222 |           });
 223 |           return {
 224 |             content: response.choices[0].message.content || '',
 225 |             usage: {
 226 |               input_tokens: response.usage?.prompt_tokens || 0,
 227 |               output_tokens: response.usage?.completion_tokens || 0
 228 |             }
 229 |           };
 230 |         }
 231 | 
 232 |         case 'mistral': {
 233 |           const response = await this.mistralClient!.chat.complete({
 234 |             model,
 235 |             messages: messages.map(m => ({
 236 |               role: m.role as 'user' | 'assistant',
 237 |               content: m.content
 238 |             })),
 239 |             maxTokens: max_tokens,
 240 |             temperature: temperature
 241 |           });
 242 |           
 243 |           const messageContent = response.choices?.[0]?.message?.content;
 244 |           let content: string;
 245 |           
 246 |           if (Array.isArray(messageContent)) {
 247 |             content = messageContent.map(chunk => 
 248 |               typeof chunk === 'string' ? chunk : JSON.stringify(chunk)
 249 |             ).join('');
 250 |           } else if (typeof messageContent === 'string') {
 251 |             content = messageContent;
 252 |           } else {
 253 |             throw new Error('Invalid content format in Mistral response');
 254 |           }
 255 |           
 256 |           return {
 257 |             content,
 258 |             usage: {
 259 |               input_tokens: response.usage?.promptTokens || 0,
 260 |               output_tokens: response.usage?.completionTokens || 0
 261 |             }
 262 |           };
 263 |         }
 264 | 
 265 |         case 'ollama': {
 266 |           const response = await this.ollamaClient!.chat({
 267 |             model,
 268 |             messages,
 269 |             options: {
 270 |               num_predict: max_tokens,
 271 |               temperature
 272 |             }
 273 |           });
 274 |           return {
 275 |             content: response.message.content,
 276 |             usage: {
 277 |               input_tokens: 0,
 278 |               output_tokens: 0
 279 |             }
 280 |           };
 281 |         }
 282 | 
 283 |         default:
 284 |           throw new Error(`Unsupported LLM provider: ${this.provider}`);
 285 |       }
 286 |     } catch (error) {
 287 |       logger.error('Error in chat:', error);
 288 |       logger.devLog('Chat error details:', {
 289 |         name: error instanceof Error ? error.name : 'Unknown',
 290 |         message: error instanceof Error ? error.message : String(error),
 291 |         stack: error instanceof Error ? error.stack : undefined,
 292 |         provider: this.provider,
 293 |         model
 294 |       });
 295 |       throw error;
 296 |     }
 297 |   }
 298 | }
 299 | 
 300 | // Initialize the unified LLM client
 301 | const llmClient = new UnifiedLLMClient();
 302 | 
 303 | // Initialize database connection for analytics
 304 | // This is a simplified in-memory version
 305 | const analyticsDb: AnalyticsDatabase = {
 306 |   records: [],
 307 |   addRecord(record: AnalyticsRecord): number {
 308 |     this.records.push({
 309 |       ...record,
 310 |       timestamp: new Date()
 311 |     });
 312 |     return this.records.length;
 313 |   },
 314 |   getPerformanceByDomain(domain?: string): PerformanceStats[] {
 315 |     let filtered = domain 
 316 |       ? this.records.filter(r => r.domain === domain) 
 317 |       : this.records;
 318 |     
 319 |     // Group by domain and approach
 320 |     const grouped: { [key: string]: {
 321 |       domain: string;
 322 |       approach: string;
 323 |       total_tokens: number;
 324 |       total_time: number;
 325 |       count: number;
 326 |       accuracy: number | null;
 327 |     }} = {};
 328 | 
 329 |     for (const record of filtered) {
 330 |       const key = `${record.domain}-${record.approach}`;
 331 |       if (!grouped[key]) {
 332 |         grouped[key] = {
 333 |           domain: record.domain,
 334 |           approach: record.approach,
 335 |           total_tokens: 0,
 336 |           total_time: 0,
 337 |           count: 0,
 338 |           accuracy: null
 339 |         };
 340 |       }
 341 |       grouped[key].total_tokens += record.tokens_used;
 342 |       grouped[key].total_time += record.execution_time_ms;
 343 |       grouped[key].count += 1;
 344 |     }
 345 |     
 346 |     // Calculate averages
 347 |     return Object.values(grouped).map(g => ({
 348 |       domain: g.domain,
 349 |       approach: g.approach,
 350 |       avg_tokens: g.total_tokens / g.count,
 351 |       avg_time_ms: g.total_time / g.count,
 352 |       accuracy: g.accuracy,
 353 |       count: g.count
 354 |     }));
 355 |   },
 356 |   getTokenReductionStats(): TokenReductionStats[] {
 357 |     // Group by domain
 358 |     const domains: { [key: string]: {
 359 |       cod_tokens: number[];
 360 |       cot_tokens: number[];
 361 |     }} = {};
 362 | 
 363 |     for (const record of this.records) {
 364 |       if (!domains[record.domain]) {
 365 |         domains[record.domain] = {
 366 |           cod_tokens: [],
 367 |           cot_tokens: []
 368 |         };
 369 |       }
 370 |       
 371 |       if (record.approach === 'CoD') {
 372 |         domains[record.domain].cod_tokens.push(record.tokens_used);
 373 |       } else if (record.approach === 'CoT') {
 374 |         domains[record.domain].cot_tokens.push(record.tokens_used);
 375 |       }
 376 |     }
 377 |     
 378 |     // Calculate reduction stats
 379 |     return Object.entries(domains).map(([domain, data]) => {
 380 |       const cod_avg = data.cod_tokens.length 
 381 |         ? data.cod_tokens.reduce((a, b) => a + b, 0) / data.cod_tokens.length 
 382 |         : 0;
 383 |       const cot_avg = data.cot_tokens.length 
 384 |         ? data.cot_tokens.reduce((a, b) => a + b, 0) / data.cot_tokens.length 
 385 |         : 0;
 386 |       
 387 |       let reduction = 0;
 388 |       if (cot_avg > 0 && cod_avg > 0) {
 389 |         reduction = 100 * (1 - (cod_avg / cot_avg));
 390 |       }
 391 |       
 392 |       return {
 393 |         domain,
 394 |         cod_avg_tokens: cod_avg,
 395 |         cot_avg_tokens: cot_avg,
 396 |         reduction_percentage: reduction
 397 |       };
 398 |     });
 399 |   }
 400 | };
 401 | 
 402 | // Complexity estimation logic
 403 | const complexityEstimator: ComplexityEstimator = {
 404 |   domainBaseLimits: {
 405 |     math: 6,
 406 |     logic: 5,
 407 |     common_sense: 4,
 408 |     physics: 7,
 409 |     chemistry: 6,
 410 |     biology: 5,
 411 |     code: 8,
 412 |     puzzle: 5,
 413 |     general: 5
 414 |   },
 415 |   complexityIndicators: {
 416 |     math: ['integral', 'derivative', 'equation', 'theorem', 'calculus', 'polynomial', 'algorithm'],
 417 |     logic: ['if-then', 'premise', 'fallacy', 'syllogism', 'deduction', 'induction'],
 418 |     physics: ['velocity', 'acceleration', 'quantum', 'momentum', 'thermodynamics'],
 419 |     code: ['function', 'algorithm', 'recursive', 'complexity', 'optimization', 'edge case']
 420 |   },
 421 |   analyzeProblem(problem: string, domain: string): ComplexityAnalysis {
 422 |     const wordCount = problem.split(/\s+/).filter(Boolean).length;
 423 |     const sentences = problem.split(/[.!?]+/).filter(Boolean);
 424 |     const sentenceCount = sentences.length;
 425 |     const wordsPerSentence = sentenceCount > 0 ? wordCount / sentenceCount : 0;
 426 |     
 427 |     // Count indicators
 428 |     const indicators = this.complexityIndicators[domain] || this.complexityIndicators.general || [];
 429 |     const lowerProblem = problem.toLowerCase();
 430 |     const foundIndicators = indicators.filter(i => lowerProblem.includes(i.toLowerCase()));
 431 |     
 432 |     // Count questions
 433 |     const questionCount = (problem.match(/\?/g) || []).length;
 434 |     
 435 |     // Estimate complexity
 436 |     let complexity = this.domainBaseLimits[domain] || this.domainBaseLimits.general;
 437 |     
 438 |     // Adjust for length
 439 |     if (wordCount > 100) complexity += 2;
 440 |     else if (wordCount > 50) complexity += 1;
 441 |     
 442 |     // Adjust for sentences
 443 |     if (wordsPerSentence > 20) complexity += 1;
 444 |     
 445 |     // Adjust for indicators
 446 |     complexity += Math.min(3, foundIndicators.length);
 447 |     
 448 |     // Adjust for questions
 449 |     complexity += Math.min(2, questionCount);
 450 |     
 451 |     return {
 452 |       word_count: wordCount,
 453 |       sentence_count: sentenceCount,
 454 |       words_per_sentence: wordsPerSentence,
 455 |       indicator_count: foundIndicators.length,
 456 |       found_indicators: foundIndicators,
 457 |       question_count: questionCount,
 458 |       estimated_complexity: complexity
 459 |     };
 460 |   }
 461 | };
 462 | 
 463 | // Format enforcement
 464 | const formatEnforcer: FormatEnforcer = {
 465 |   enforceWordLimit(reasoning: string, maxWordsPerStep: number | null): string {
 466 |     if (!maxWordsPerStep) return reasoning;
 467 |     
 468 |     // Split into steps
 469 |     const stepPattern = /(\d+\.\s*|Step\s+\d+:|\n-\s+|\n\*\s+|•\s+|^\s*-\s+|^\s*\*\s+)/;
 470 |     let steps = reasoning.split(stepPattern).filter(Boolean);
 471 |     
 472 |     // Process steps
 473 |     const processed: string[] = [];
 474 |     for (let i = 0; i < steps.length; i++) {
 475 |       const step = steps[i];
 476 |       // Check if this is a step marker
 477 |       if (step.match(stepPattern)) {
 478 |         processed.push(step);
 479 |         continue;
 480 |       }
 481 |       
 482 |       // Enforce word limit on this step
 483 |       const words = step.split(/\s+/).filter(Boolean);
 484 |       if (words.length <= maxWordsPerStep) {
 485 |         processed.push(step);
 486 |       } else {
 487 |         // Truncate to word limit
 488 |         processed.push(words.slice(0, maxWordsPerStep).join(' '));
 489 |       }
 490 |     }
 491 |     
 492 |     return processed.join('');
 493 |   },
 494 |   
 495 |   analyzeAdherence(reasoning: string, maxWordsPerStep: number) {
 496 |     // Split into steps
 497 |     const stepPattern = /(\d+\.\s*|Step\s+\d+:|\n-\s+|\n\*\s+|•\s+|^\s*-\s+|^\s*\*\s+)/;
 498 |     let steps = reasoning.split(stepPattern).filter(Boolean);
 499 |     
 500 |     let totalWords = 0;
 501 |     let stepCount = 0;
 502 |     let overLimitSteps = 0;
 503 |     
 504 |     for (let i = 0; i < steps.length; i++) {
 505 |       const step = steps[i];
 506 |       // Skip step markers
 507 |       if (step.match(stepPattern)) continue;
 508 |       
 509 |       stepCount++;
 510 |       const wordCount = step.split(/\s+/).filter(Boolean).length;
 511 |       totalWords += wordCount;
 512 |       
 513 |       if (wordCount > maxWordsPerStep) {
 514 |         overLimitSteps++;
 515 |       }
 516 |     }
 517 |     
 518 |     return {
 519 |       step_count: stepCount,
 520 |       total_words: totalWords,
 521 |       avg_words_per_step: stepCount > 0 ? totalWords / stepCount : 0,
 522 |       over_limit_steps: overLimitSteps,
 523 |       adherence_percentage: stepCount > 0 ? (1 - (overLimitSteps / stepCount)) * 100 : 100
 524 |     };
 525 |   }
 526 | };
 527 | 
 528 | // Reasoning approach selector
 529 | const reasoningSelector: ReasoningSelector = {
 530 |   defaultPreferences: {
 531 |     math: {
 532 |       complexity_threshold: 7,
 533 |       accuracy_threshold: 0.85
 534 |     },
 535 |     code: {
 536 |       complexity_threshold: 8,
 537 |       accuracy_threshold: 0.9
 538 |     },
 539 |     general: {
 540 |       complexity_threshold: 6,
 541 |       accuracy_threshold: 0.8
 542 |     }
 543 |   },
 544 |   
 545 |   selectApproach(domain: string, complexity: number, performanceStats: PerformanceStats[]): string {
 546 |     // Default preferences for domain
 547 |     const prefs = this.defaultPreferences[domain] || this.defaultPreferences.general;
 548 |     
 549 |     // By default, use CoD for simpler problems
 550 |     if (complexity < prefs.complexity_threshold) {
 551 |       return 'CoD';
 552 |     }
 553 |     
 554 |     // For complex problems, check past performance if available
 555 |     if (performanceStats && performanceStats.length > 0) {
 556 |       const domainStats = performanceStats.filter(s => s.domain === domain);
 557 |       
 558 |       // Check if CoD has good enough accuracy
 559 |       const codStats = domainStats.find(s => s.approach === 'CoD');
 560 |       if (codStats && codStats.accuracy && codStats.accuracy >= prefs.accuracy_threshold) {
 561 |         return 'CoD';
 562 |       }
 563 |       
 564 |       // Otherwise, use CoT for complex problems
 565 |       return 'CoT';
 566 |     }
 567 |     
 568 |     // Use CoT for complex problems by default
 569 |     return complexity >= prefs.complexity_threshold ? 'CoT' : 'CoD';
 570 |   }
 571 | };
 572 | 
 573 | // Prompt creation
 574 | function createCodPrompt(problem: string, domain: string, examples: string[] = [], wordLimit: number = 5): string {
 575 |   return `
 576 | You will solve this ${domain} problem using the Chain of Draft technique, which uses very concise reasoning steps.
 577 | 
 578 | For each step, use at most ${wordLimit} words. Be extremely concise but clear.
 579 | 
 580 | PROBLEM:
 581 | ${problem}
 582 | 
 583 | To solve this, create short reasoning steps, with each step using at most ${wordLimit} words.
 584 | After your reasoning, state your final answer clearly.
 585 | `.trim();
 586 | }
 587 | 
 588 | function createCotPrompt(problem: string, domain: string, examples: string[] = []): string {
 589 |   return `
 590 | You will solve this ${domain} problem using detailed step-by-step reasoning.
 591 | 
 592 | PROBLEM:
 593 | ${problem}
 594 | 
 595 | Think through this problem step-by-step with clear reasoning.
 596 | After your reasoning, state your final answer clearly.
 597 | `.trim();
 598 | }
 599 | 
 600 | // Chain of Draft client implementation
 601 | const chainOfDraftClient = {
 602 |   async solveWithReasoning(params: ChainOfDraftParams): Promise<ChainOfDraftResult> {
 603 |     const {
 604 |       problem,
 605 |       domain = 'general',
 606 |       max_words_per_step = null,
 607 |       approach = null,
 608 |       enforce_format = true,
 609 |       adaptive_word_limit = true
 610 |     } = params;
 611 |     
 612 |     logger.devLog('Starting Chain of Draft reasoning', params);
 613 |     
 614 |     const startTime = Date.now();
 615 |     
 616 |     // Analyze problem complexity
 617 |     const analysis = complexityEstimator.analyzeProblem(problem, domain);
 618 |     logger.devLog('Problem complexity analysis', analysis);
 619 |     const complexity = analysis.estimated_complexity;
 620 |     
 621 |     // Determine word limit
 622 |     let wordLimit = max_words_per_step;
 623 |     if (!wordLimit && adaptive_word_limit) {
 624 |       wordLimit = complexity;
 625 |     } else if (!wordLimit) {
 626 |       wordLimit = complexityEstimator.domainBaseLimits[domain] || 5;
 627 |     }
 628 |     
 629 |     // Determine approach (CoD or CoT)
 630 |     const performanceStats = analyticsDb.getPerformanceByDomain(domain);
 631 |     const selectedApproach = approach || 
 632 |       reasoningSelector.selectApproach(domain, complexity, performanceStats);
 633 |     
 634 |     // Create prompt based on approach
 635 |     const prompt = selectedApproach === 'CoD' 
 636 |       ? createCodPrompt(problem, domain, [], wordLimit)
 637 |       : createCotPrompt(problem, domain, []);
 638 |     
 639 |     // Call LLM using unified client
 640 |     const response = await llmClient.chat([
 641 |       { role: 'user', content: prompt }
 642 |     ], {
 643 |       max_tokens: 1000,
 644 |       temperature: 0.7
 645 |     });
 646 |     
 647 |     // Extract reasoning and answer
 648 |     const fullText = response.content;
 649 |     
 650 |     // Extract final answer (assuming it comes after the reasoning, often starts with "Answer:" or similar)
 651 |     let reasoningSteps = fullText;
 652 |     let finalAnswer = '';
 653 |     
 654 |     // Common patterns for final answer sections
 655 |     const answerPatterns = [
 656 |       /(?:Final Answer|Answer|Therefore):?\s*(.*?)$/is,
 657 |       /(?:In conclusion|To conclude|Thus|Hence|So),\s*(.*?)$/is,
 658 |       /(?:The answer is|The result is|The solution is)\s*(.*?)$/is
 659 |     ];
 660 |     
 661 |     // Try to extract the final answer with each pattern
 662 |     for (const pattern of answerPatterns) {
 663 |       const match = fullText.match(pattern);
 664 |       if (match && match[1]) {
 665 |         finalAnswer = match[1].trim();
 666 |         reasoningSteps = fullText.substring(0, fullText.indexOf(match[0])).trim();
 667 |         break;
 668 |       }
 669 |     }
 670 |     
 671 |     // If no pattern matched, just use the last sentence
 672 |     if (!finalAnswer) {
 673 |       const sentences = fullText.split(/[.!?]+\s+/);
 674 |       if (sentences.length > 1) {
 675 |         finalAnswer = sentences.pop()!.trim();
 676 |         reasoningSteps = sentences.join('. ') + '.';
 677 |       }
 678 |     }
 679 |     
 680 |     // Apply format enforcement if needed
 681 |     if (enforce_format && selectedApproach === 'CoD') {
 682 |       reasoningSteps = formatEnforcer.enforceWordLimit(reasoningSteps, wordLimit);
 683 |     }
 684 |     
 685 |     // Calculate execution time
 686 |     const executionTime = Date.now() - startTime;
 687 |     
 688 |     // Get token count from response
 689 |     const tokenCount = (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0);
 690 |     
 691 |     // Record analytics
 692 |     analyticsDb.addRecord({
 693 |       problem_id: problem.substring(0, 20),
 694 |       problem_text: problem,
 695 |       domain,
 696 |       approach: selectedApproach,
 697 |       word_limit: wordLimit,
 698 |       tokens_used: tokenCount,
 699 |       execution_time_ms: executionTime,
 700 |       reasoning_steps: reasoningSteps,
 701 |       answer: finalAnswer
 702 |     });
 703 |     
 704 |     // Return result
 705 |     return {
 706 |       approach: selectedApproach,
 707 |       reasoning_steps: reasoningSteps,
 708 |       final_answer: finalAnswer,
 709 |       token_count: tokenCount,
 710 |       word_limit: wordLimit,
 711 |       complexity: complexity,
 712 |       execution_time_ms: executionTime
 713 |     };
 714 |   }
 715 | };
 716 | 
 717 | // Define the Chain of Draft tool
 718 | const CHAIN_OF_DRAFT_TOOL = {
 719 |   name: "chain_of_draft_solve",
 720 |   description: "Solve a reasoning problem using Chain of Draft approach",
 721 |   inputSchema: {
 722 |     type: "object",
 723 |     properties: {
 724 |       problem: {
 725 |         type: "string",
 726 |         description: "The problem to solve"
 727 |       },
 728 |       domain: {
 729 |         type: "string",
 730 |         description: "Domain for context (math, logic, code, common-sense, etc.)"
 731 |       },
 732 |       max_words_per_step: {
 733 |         type: "number",
 734 |         description: "Maximum words per reasoning step"
 735 |       },
 736 |       approach: {
 737 |         type: "string",
 738 |         description: "Force 'CoD' or 'CoT' approach"
 739 |       },
 740 |       enforce_format: {
 741 |         type: "boolean",
 742 |         description: "Whether to enforce the word limit"
 743 |       },
 744 |       adaptive_word_limit: {
 745 |         type: "boolean",
 746 |         description: "Adjust word limits based on complexity"
 747 |       }
 748 |     },
 749 |     required: ["problem"]
 750 |   }
 751 | };
 752 | 
 753 | const MATH_TOOL = {
 754 |   name: "math_solve",
 755 |   description: "Solve a math problem using Chain of Draft reasoning",
 756 |   inputSchema: {
 757 |     type: "object",
 758 |     properties: {
 759 |       problem: {
 760 |         type: "string",
 761 |         description: "The math problem to solve"
 762 |       },
 763 |       approach: {
 764 |         type: "string",
 765 |         description: "Force 'CoD' or 'CoT' approach"
 766 |       },
 767 |       max_words_per_step: {
 768 |         type: "number",
 769 |         description: "Maximum words per reasoning step"
 770 |       }
 771 |     },
 772 |     required: ["problem"]
 773 |   }
 774 | };
 775 | 
 776 | const CODE_TOOL = {
 777 |   name: "code_solve",
 778 |   description: "Solve a coding problem using Chain of Draft reasoning",
 779 |   inputSchema: {
 780 |     type: "object",
 781 |     properties: {
 782 |       problem: {
 783 |         type: "string",
 784 |         description: "The coding problem to solve"
 785 |       },
 786 |       approach: {
 787 |         type: "string",
 788 |         description: "Force 'CoD' or 'CoT' approach"
 789 |       },
 790 |       max_words_per_step: {
 791 |         type: "number",
 792 |         description: "Maximum words per reasoning step"
 793 |       }
 794 |     },
 795 |     required: ["problem"]
 796 |   }
 797 | };
 798 | 
 799 | const LOGIC_TOOL = {
 800 |   name: "logic_solve",
 801 |   description: "Solve a logic problem using Chain of Draft reasoning",
 802 |   inputSchema: {
 803 |     type: "object",
 804 |     properties: {
 805 |       problem: {
 806 |         type: "string",
 807 |         description: "The logic problem to solve"
 808 |       },
 809 |       approach: {
 810 |         type: "string",
 811 |         description: "Force 'CoD' or 'CoT' approach"
 812 |       },
 813 |       max_words_per_step: {
 814 |         type: "number",
 815 |         description: "Maximum words per reasoning step"
 816 |       }
 817 |     },
 818 |     required: ["problem"]
 819 |   }
 820 | };
 821 | 
 822 | const PERFORMANCE_TOOL = {
 823 |   name: "get_performance_stats",
 824 |   description: "Get performance statistics for CoD vs CoT approaches",
 825 |   inputSchema: {
 826 |     type: "object",
 827 |     properties: {
 828 |       domain: {
 829 |         type: "string",
 830 |         description: "Filter for specific domain"
 831 |       }
 832 |     }
 833 |   }
 834 | };
 835 | 
 836 | const TOKEN_TOOL = {
 837 |   name: "get_token_reduction",
 838 |   description: "Get token reduction statistics for CoD vs CoT",
 839 |   inputSchema: {
 840 |     type: "object",
 841 |     properties: {}
 842 |   }
 843 | };
 844 | 
 845 | const COMPLEXITY_TOOL = {
 846 |   name: "analyze_problem_complexity",
 847 |   description: "Analyze the complexity of a problem",
 848 |   inputSchema: {
 849 |     type: "object",
 850 |     properties: {
 851 |       problem: {
 852 |         type: "string",
 853 |         description: "The problem to analyze"
 854 |       },
 855 |       domain: {
 856 |         type: "string",
 857 |         description: "Problem domain"
 858 |       }
 859 |     },
 860 |     required: ["problem"]
 861 |   }
 862 | };
 863 | 
 864 | // Initialize the MCP server
 865 | const server = new Server({
 866 |   name: "mcp-chain-of-draft-prompt-tool",
 867 |   version: "1.0.0"
 868 | }, {
 869 |   capabilities: {
 870 |     tools: {},
 871 |   },
 872 | });
 873 | 
 874 | // Expose available tools
 875 | server.setRequestHandler(ListToolsRequestSchema, async () => ({
 876 |   tools: [
 877 |     CHAIN_OF_DRAFT_TOOL,
 878 |     MATH_TOOL,
 879 |     CODE_TOOL,
 880 |     LOGIC_TOOL,
 881 |     PERFORMANCE_TOOL,
 882 |     TOKEN_TOOL,
 883 |     COMPLEXITY_TOOL
 884 |   ],
 885 | }));
 886 | 
 887 | // Handle tool calls
 888 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
 889 |   const { name, arguments: toolArgs } = request.params;
 890 |   const args = toolArgs as unknown as ToolArguments;
 891 |   
 892 |   try {
 893 |     // Chain of Draft solve
 894 |     if (name === "chain_of_draft_solve" && args?.problem) {
 895 |       const params: ChainOfDraftParams = {
 896 |         problem: args.problem,
 897 |         domain: args.domain,
 898 |         max_words_per_step: args.max_words_per_step ?? null,
 899 |         approach: args.approach,
 900 |         enforce_format: args.enforce_format,
 901 |         adaptive_word_limit: args.adaptive_word_limit
 902 |       };
 903 |       
 904 |       const result = await chainOfDraftClient.solveWithReasoning(params);
 905 |       
 906 |       const formattedResponse = 
 907 |         `Chain of ${result.approach} reasoning (${result.word_limit} word limit):\n\n` +
 908 |         `${result.reasoning_steps}\n\n` +
 909 |         `Final answer: ${result.final_answer}\n\n` +
 910 |         `Stats: ${result.token_count} tokens, ${result.execution_time_ms.toFixed(0)}ms, ` +
 911 |         `complexity score: ${result.complexity}`;
 912 |       
 913 |       return {
 914 |         content: [{
 915 |           type: "text",
 916 |           text: formattedResponse
 917 |         }]
 918 |       };
 919 |     }
 920 |     
 921 |     // Math solver
 922 |     if (name === "math_solve" && args?.problem) {
 923 |       const params: ChainOfDraftParams = {
 924 |         problem: args.problem,
 925 |         domain: "math",
 926 |         max_words_per_step: args.max_words_per_step ?? null,
 927 |         approach: args.approach
 928 |       };
 929 |       
 930 |       const result = await chainOfDraftClient.solveWithReasoning(params);
 931 |       
 932 |       const formattedResponse = 
 933 |         `Chain of ${result.approach} reasoning (${result.word_limit} word limit):\n\n` +
 934 |         `${result.reasoning_steps}\n\n` +
 935 |         `Final answer: ${result.final_answer}\n\n` +
 936 |         `Stats: ${result.token_count} tokens, ${result.execution_time_ms.toFixed(0)}ms, ` +
 937 |         `complexity score: ${result.complexity}`;
 938 |       
 939 |       return {
 940 |         content: [{
 941 |           type: "text",
 942 |           text: formattedResponse
 943 |         }]
 944 |       };
 945 |     }
 946 |     
 947 |     // Code solver
 948 |     if (name === "code_solve" && args?.problem) {
 949 |       const params: ChainOfDraftParams = {
 950 |         problem: args.problem,
 951 |         domain: "code",
 952 |         max_words_per_step: args.max_words_per_step ?? null,
 953 |         approach: args.approach
 954 |       };
 955 |       
 956 |       const result = await chainOfDraftClient.solveWithReasoning(params);
 957 |       
 958 |       const formattedResponse = 
 959 |         `Chain of ${result.approach} reasoning (${result.word_limit} word limit):\n\n` +
 960 |         `${result.reasoning_steps}\n\n` +
 961 |         `Final answer: ${result.final_answer}\n\n` +
 962 |         `Stats: ${result.token_count} tokens, ${result.execution_time_ms.toFixed(0)}ms, ` +
 963 |         `complexity score: ${result.complexity}`;
 964 |       
 965 |       return {
 966 |         content: [{
 967 |           type: "text",
 968 |           text: formattedResponse
 969 |         }]
 970 |       };
 971 |     }
 972 |     
 973 |     // Logic solver
 974 |     if (name === "logic_solve" && args?.problem) {
 975 |       const params: ChainOfDraftParams = {
 976 |         problem: args.problem,
 977 |         domain: "logic",
 978 |         max_words_per_step: args.max_words_per_step ?? null,
 979 |         approach: args.approach
 980 |       };
 981 |       
 982 |       const result = await chainOfDraftClient.solveWithReasoning(params);
 983 |       
 984 |       const formattedResponse = 
 985 |         `Chain of ${result.approach} reasoning (${result.word_limit} word limit):\n\n` +
 986 |         `${result.reasoning_steps}\n\n` +
 987 |         `Final answer: ${result.final_answer}\n\n` +
 988 |         `Stats: ${result.token_count} tokens, ${result.execution_time_ms.toFixed(0)}ms, ` +
 989 |         `complexity score: ${result.complexity}`;
 990 |       
 991 |       return {
 992 |         content: [{
 993 |           type: "text",
 994 |           text: formattedResponse
 995 |         }]
 996 |       };
 997 |     }
 998 |     
 999 |     // Performance stats
1000 |     if (name === "get_performance_stats") {
1001 |       const stats = analyticsDb.getPerformanceByDomain(args?.domain);
1002 |       
1003 |       let result = "Performance Comparison (CoD vs CoT):\n\n";
1004 |       
1005 |       if (!stats || stats.length === 0) {
1006 |         result = "No performance data available yet.";
1007 |       } else {
1008 |         for (const stat of stats) {
1009 |           result += `Domain: ${stat.domain}\n`;
1010 |           result += `Approach: ${stat.approach}\n`;
1011 |           result += `Average tokens: ${stat.avg_tokens.toFixed(1)}\n`;
1012 |           result += `Average time: ${stat.avg_time_ms.toFixed(1)}ms\n`;
1013 |           
1014 |           if (stat.accuracy !== null) {
1015 |             result += `Accuracy: ${(stat.accuracy * 100).toFixed(1)}%\n`;
1016 |           }
1017 |           
1018 |           result += `Sample size: ${stat.count}\n\n`;
1019 |         }
1020 |       }
1021 |       
1022 |       return {
1023 |         content: [{
1024 |           type: "text",
1025 |           text: result
1026 |         }]
1027 |       };
1028 |     }
1029 |     
1030 |     // Token reduction
1031 |     if (name === "get_token_reduction") {
1032 |       const stats = analyticsDb.getTokenReductionStats();
1033 |       
1034 |       let result = "Token Reduction Analysis:\n\n";
1035 |       
1036 |       if (!stats || stats.length === 0) {
1037 |         result = "No reduction data available yet.";
1038 |       } else {
1039 |         for (const stat of stats) {
1040 |           result += `Domain: ${stat.domain}\n`;
1041 |           result += `CoD avg tokens: ${stat.cod_avg_tokens.toFixed(1)}\n`;
1042 |           result += `CoT avg tokens: ${stat.cot_avg_tokens.toFixed(1)}\n`;
1043 |           result += `Reduction: ${stat.reduction_percentage.toFixed(1)}%\n\n`;
1044 |         }
1045 |       }
1046 |       
1047 |       return {
1048 |         content: [{
1049 |           type: "text",
1050 |           text: result
1051 |         }]
1052 |       };
1053 |     }
1054 |     
1055 |     // Complexity analysis
1056 |     if (name === "analyze_problem_complexity" && args?.problem) {
1057 |       const analysis = complexityEstimator.analyzeProblem(
1058 |         args.problem,
1059 |         args.domain || "general"
1060 |       );
1061 |       
1062 |       let result = `Complexity Analysis for ${args.domain || "general"} problem:\n\n`;
1063 |       result += `Word count: ${analysis.word_count}\n`;
1064 |       result += `Sentence count: ${analysis.sentence_count}\n`;
1065 |       result += `Words per sentence: ${analysis.words_per_sentence.toFixed(1)}\n`;
1066 |       result += `Complexity indicators found: ${analysis.indicator_count}\n`;
1067 |       
1068 |       if (analysis.found_indicators && analysis.found_indicators.length > 0) {
1069 |         result += `Indicators: ${analysis.found_indicators.join(", ")}\n`;
1070 |       }
1071 |       
1072 |       result += `Question count: ${analysis.question_count}\n`;
1073 |       result += `\nEstimated complexity score: ${analysis.estimated_complexity}\n`;
1074 |       result += `Recommended word limit per step: ${analysis.estimated_complexity}\n`;
1075 |       
1076 |       return {
1077 |         content: [{
1078 |           type: "text",
1079 |           text: result
1080 |         }]
1081 |       };
1082 |     }
1083 |     
1084 |     // Handle unknown tool
1085 |     return {
1086 |       content: [{
1087 |         type: "text",
1088 |         text: `Unknown tool: ${name}`
1089 |       }],
1090 |       isError: true
1091 |     };
1092 |   } catch (error) {
1093 |     logger.error('Error executing tool:', error);
1094 |     return {
1095 |       content: [{
1096 |         type: "text",
1097 |         text: `Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}`
1098 |       }],
1099 |       isError: true
1100 |     };
1101 |   }
1102 | });
1103 | 
1104 | // Start the server
1105 | async function startServer() {
1106 |   const transport = new StdioServerTransport();
1107 |   
1108 |   logger.info('Chain of Draft MCP Server starting...');
1109 |   
1110 |   try {
1111 |     // Connect to the transport
1112 |     await server.connect(transport);
1113 |     logger.success('Server connected to transport');
1114 | 
1115 |     // Keep the process alive
1116 |     process.stdin.resume();
1117 | 
1118 |     // Handle shutdown signals
1119 |     const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
1120 |     signals.forEach(signal => {
1121 |       process.on(signal, async () => {
1122 |         logger.info(`Received ${signal}, shutting down gracefully...`);
1123 |         try {
1124 |           await server.close();
1125 |           logger.success('Server disconnected successfully');
1126 |           process.exit(0);
1127 |         } catch (error) {
1128 |           logger.error('Error during shutdown:', error);
1129 |           process.exit(1);
1130 |         }
1131 |       });
1132 |     });
1133 |   } catch (error) {
1134 |     logger.error('Error starting server:', error);
1135 |     process.exit(1);
1136 |   }
1137 | }
1138 | 
1139 | // Run the server
1140 | startServer().catch(error => {
1141 |   logger.error('Fatal error:', error);
1142 |   process.exit(1);
1143 | });
1144 | 
1145 | process.on('uncaughtException', (error) => {
1146 |   logger.error('Fatal error:', error);
1147 |   process.exit(1);
1148 | }); 
1149 | 
```
Page 1/2FirstPrevNextLast