# Directory Structure ``` ├── .env.example ├── .gitignore ├── Dockerfile ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── smithery.yaml ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build output 5 | build/ 6 | dist/ 7 | 8 | # Logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Environment variables 15 | .env 16 | .env.local 17 | .env.*.local 18 | 19 | # Editor directories and files 20 | .idea/ 21 | .vscode/ 22 | *.swp 23 | *.swo 24 | *.swn 25 | .DS_Store 26 | ``` -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` 1 | # Required: OpenRouter API key for both DeepSeek and Claude models 2 | OPENROUTER_API_KEY=your_openrouter_api_key_here 3 | 4 | # Optional: Model configuration (defaults shown below) 5 | DEEPSEEK_MODEL=deepseek/deepseek-r1:free # DeepSeek model for reasoning 6 | CLAUDE_MODEL=anthropic/claude-3.5-sonnet:beta # Claude model for responses 7 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP 2 | 3 | [](https://smithery.ai/server/@newideas99/Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP) 4 | 5 | A Model Context Protocol (MCP) server that combines DeepSeek R1's reasoning capabilities with Claude 3.5 Sonnet's response generation through OpenRouter. This implementation uses a two-stage process where DeepSeek provides structured reasoning which is then incorporated into Claude's response generation. 6 | 7 | ## Features 8 | 9 | - **Two-Stage Processing**: 10 | - Uses DeepSeek R1 for initial reasoning (50k character context) 11 | - Uses Claude 3.5 Sonnet for final response (600k character context) 12 | - Both models accessed through OpenRouter's unified API 13 | - Injects DeepSeek's reasoning tokens into Claude's context 14 | 15 | - **Smart Conversation Management**: 16 | - Detects active conversations using file modification times 17 | - Handles multiple concurrent conversations 18 | - Filters out ended conversations automatically 19 | - Supports context clearing when needed 20 | 21 | - **Optimized Parameters**: 22 | - Model-specific context limits: 23 | * DeepSeek: 50,000 characters for focused reasoning 24 | * Claude: 600,000 characters for comprehensive responses 25 | - Recommended settings: 26 | * temperature: 0.7 for balanced creativity 27 | * top_p: 1.0 for full probability distribution 28 | * repetition_penalty: 1.0 to prevent repetition 29 | 30 | ## Installation 31 | 32 | ### Installing via Smithery 33 | 34 | To install DeepSeek Thinking with Claude 3.5 Sonnet for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@newideas99/Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP): 35 | 36 | ```bash 37 | npx -y @smithery/cli install @newideas99/Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP --client claude 38 | ``` 39 | 40 | ### Manual Installation 41 | 1. Clone the repository: 42 | ```bash 43 | git clone https://github.com/yourusername/Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP.git 44 | cd Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP 45 | ``` 46 | 47 | 2. Install dependencies: 48 | ```bash 49 | npm install 50 | ``` 51 | 52 | 3. Create a `.env` file with your OpenRouter API key: 53 | ```env 54 | # Required: OpenRouter API key for both DeepSeek and Claude models 55 | OPENROUTER_API_KEY=your_openrouter_api_key_here 56 | 57 | # Optional: Model configuration (defaults shown below) 58 | DEEPSEEK_MODEL=deepseek/deepseek-r1 # DeepSeek model for reasoning 59 | CLAUDE_MODEL=anthropic/claude-3.5-sonnet:beta # Claude model for responses 60 | ``` 61 | 62 | 4. Build the server: 63 | ```bash 64 | npm run build 65 | ``` 66 | 67 | ## Usage with Cline 68 | 69 | Add to your Cline MCP settings (usually in `~/.vscode/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`): 70 | 71 | ```json 72 | { 73 | "mcpServers": { 74 | "deepseek-claude": { 75 | "command": "/path/to/node", 76 | "args": ["/path/to/Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP/build/index.js"], 77 | "env": { 78 | "OPENROUTER_API_KEY": "your_key_here" 79 | }, 80 | "disabled": false, 81 | "autoApprove": [] 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | ## Tool Usage 88 | 89 | The server provides two tools for generating and monitoring responses: 90 | 91 | ### generate_response 92 | 93 | Main tool for generating responses with the following parameters: 94 | 95 | ```typescript 96 | { 97 | "prompt": string, // Required: The question or prompt 98 | "showReasoning"?: boolean, // Optional: Show DeepSeek's reasoning process 99 | "clearContext"?: boolean, // Optional: Clear conversation history 100 | "includeHistory"?: boolean // Optional: Include Cline conversation history 101 | } 102 | ``` 103 | 104 | ### check_response_status 105 | 106 | Tool for checking the status of a response generation task: 107 | 108 | ```typescript 109 | { 110 | "taskId": string // Required: The task ID from generate_response 111 | } 112 | ``` 113 | 114 | ### Response Polling 115 | 116 | The server uses a polling mechanism to handle long-running requests: 117 | 118 | 1. Initial Request: 119 | - `generate_response` returns immediately with a task ID 120 | - Response format: `{"taskId": "uuid-here"}` 121 | 122 | 2. Status Checking: 123 | - Use `check_response_status` to poll the task status 124 | - **Note:** Responses can take up to 60 seconds to complete 125 | - Status progresses through: pending → reasoning → responding → complete 126 | 127 | Example usage in Cline: 128 | ```typescript 129 | // Initial request 130 | const result = await use_mcp_tool({ 131 | server_name: "deepseek-claude", 132 | tool_name: "generate_response", 133 | arguments: { 134 | prompt: "What is quantum computing?", 135 | showReasoning: true 136 | } 137 | }); 138 | 139 | // Get taskId from result 140 | const taskId = JSON.parse(result.content[0].text).taskId; 141 | 142 | // Poll for status (may need multiple checks over ~60 seconds) 143 | const status = await use_mcp_tool({ 144 | server_name: "deepseek-claude", 145 | tool_name: "check_response_status", 146 | arguments: { taskId } 147 | }); 148 | 149 | // Example status response when complete: 150 | { 151 | "status": "complete", 152 | "reasoning": "...", // If showReasoning was true 153 | "response": "..." // The final response 154 | } 155 | ``` 156 | 157 | ## Development 158 | 159 | For development with auto-rebuild: 160 | ```bash 161 | npm run watch 162 | ``` 163 | 164 | ## How It Works 165 | 166 | 1. **Reasoning Stage (DeepSeek R1)**: 167 | - Uses OpenRouter's reasoning tokens feature 168 | - Prompt is modified to output 'done' while capturing reasoning 169 | - Reasoning is extracted from response metadata 170 | 171 | 2. **Response Stage (Claude 3.5 Sonnet)**: 172 | - Receives the original prompt and DeepSeek's reasoning 173 | - Generates final response incorporating the reasoning 174 | - Maintains conversation context and history 175 | 176 | ## License 177 | 178 | MIT License - See LICENSE file for details. 179 | 180 | ## Credits 181 | 182 | Based on the RAT (Retrieval Augmented Thinking) concept by [Skirano](https://x.com/skirano/status/1881922469411643413), which enhances AI responses through structured reasoning and knowledge retrieval. 183 | 184 | This implementation specifically combines DeepSeek R1's reasoning capabilities with Claude 3.5 Sonnet's response generation through OpenRouter's unified API. 185 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - openrouterApiKey 10 | properties: 11 | openrouterApiKey: 12 | type: string 13 | description: The API key for accessing the OpenRouter service. 14 | commandFunction: 15 | # A function that produces the CLI command to start the MCP on stdio. 16 | |- 17 | (config) => ({ command: 'node', args: ['build/index.js'], env: { OPENROUTER_API_KEY: config.openrouterApiKey } }) 18 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "deepseek-thinking-claude-3-5-sonnet-cline-mcp", 3 | "version": "0.1.0", 4 | "description": "MCP server that combines DeepSeek's reasoning with Claude 3.5 Sonnet's response generation through Cline", 5 | "private": true, 6 | "type": "module", 7 | "bin": { 8 | "deepseek-thinking-claude-mcp": "./build/index.js" 9 | }, 10 | "files": [ 11 | "build" 12 | ], 13 | "scripts": { 14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 15 | "prepare": "npm run build", 16 | "watch": "tsc --watch", 17 | "inspector": "npx @modelcontextprotocol/inspector build/index.js" 18 | }, 19 | "dependencies": { 20 | "@anthropic-ai/sdk": "^0.36.2", 21 | "@modelcontextprotocol/sdk": "0.6.0", 22 | "dotenv": "^16.4.7", 23 | "openai": "^4.80.1", 24 | "uuid": "^11.0.5" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^20.11.24", 28 | "@types/uuid": "^10.0.0", 29 | "typescript": "^5.3.3" 30 | } 31 | } 32 | ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | # Stage 1: Build the application using Node.js 3 | FROM node:18-alpine AS builder 4 | 5 | # Set working directory 6 | WORKDIR /app 7 | 8 | # Copy package.json and package-lock.json to the working directory 9 | COPY package.json package-lock.json ./ 10 | 11 | # Install dependencies 12 | RUN npm install 13 | 14 | # Copy source files 15 | COPY src ./src 16 | 17 | # Build the project 18 | RUN npm run build 19 | 20 | # Stage 2: Create a lightweight image for production 21 | FROM node:18-alpine 22 | 23 | # Set working directory 24 | WORKDIR /app 25 | 26 | # Copy built files from builder 27 | COPY --from=builder /app/build ./build 28 | 29 | # Copy necessary files 30 | COPY package.json package-lock.json ./ 31 | 32 | # Install only production dependencies 33 | RUN npm install --omit=dev 34 | 35 | # Environment variables 36 | ENV NODE_ENV=production 37 | 38 | # Entrypoint command to run the MCP server 39 | ENTRYPOINT ["node", "build/index.js"] 40 | 41 | # Command to start the server 42 | CMD ["node", "build/index.js"] 43 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { 5 | CallToolRequestSchema, 6 | ErrorCode, 7 | ListToolsRequestSchema, 8 | McpError, 9 | } from '@modelcontextprotocol/sdk/types.js'; 10 | import { OpenAI } from 'openai'; 11 | import dotenv from 'dotenv'; 12 | import * as os from 'os'; 13 | import * as path from 'path'; 14 | import * as fs from 'fs/promises'; 15 | import { v4 as uuidv4 } from 'uuid'; 16 | 17 | // Load environment variables 18 | dotenv.config(); 19 | 20 | // Debug logging 21 | const DEBUG = true; 22 | const log = (...args: any[]) => { 23 | if (DEBUG) { 24 | console.error('[DEEPSEEK-CLAUDE MCP]', ...args); 25 | } 26 | }; 27 | 28 | // Constants 29 | const DEEPSEEK_MODEL = "deepseek/deepseek-r1"; 30 | const CLAUDE_MODEL = "anthropic/claude-3.5-sonnet:beta"; 31 | 32 | interface ConversationEntry { 33 | timestamp: number; 34 | prompt: string; 35 | reasoning: string; 36 | response: string; 37 | model: string; 38 | } 39 | 40 | interface ConversationContext { 41 | entries: ConversationEntry[]; 42 | maxEntries: number; 43 | } 44 | 45 | interface GenerateResponseArgs { 46 | prompt: string; 47 | showReasoning?: boolean; 48 | clearContext?: boolean; 49 | includeHistory?: boolean; 50 | } 51 | 52 | interface CheckResponseStatusArgs { 53 | taskId: string; 54 | } 55 | 56 | interface TaskStatus { 57 | status: 'pending' | 'reasoning' | 'responding' | 'complete' | 'error'; 58 | prompt: string; 59 | showReasoning?: boolean; 60 | reasoning?: string; 61 | response?: string; 62 | error?: string; 63 | timestamp: number; 64 | } 65 | 66 | const isValidCheckResponseStatusArgs = (args: any): args is CheckResponseStatusArgs => 67 | typeof args === 'object' && 68 | args !== null && 69 | typeof args.taskId === 'string'; 70 | 71 | interface ClaudeMessage { 72 | role: 'user' | 'assistant'; 73 | content: string | { type: string; text: string }[]; 74 | } 75 | 76 | interface UiMessage { 77 | ts: number; 78 | type: string; 79 | say?: string; 80 | ask?: string; 81 | text: string; 82 | conversationHistoryIndex: number; 83 | } 84 | 85 | const isValidGenerateResponseArgs = (args: any): args is GenerateResponseArgs => 86 | typeof args === 'object' && 87 | args !== null && 88 | typeof args.prompt === 'string' && 89 | (args.showReasoning === undefined || typeof args.showReasoning === 'boolean') && 90 | (args.clearContext === undefined || typeof args.clearContext === 'boolean') && 91 | (args.includeHistory === undefined || typeof args.includeHistory === 'boolean'); 92 | 93 | function getClaudePath(): string { 94 | const homeDir = os.homedir(); 95 | switch (process.platform) { 96 | case 'win32': 97 | return path.join(homeDir, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'tasks'); 98 | case 'darwin': 99 | return path.join(homeDir, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'tasks'); 100 | default: // linux 101 | return path.join(homeDir, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'tasks'); 102 | } 103 | } 104 | 105 | async function findActiveConversation(): Promise<ClaudeMessage[] | null> { 106 | try { 107 | const tasksPath = getClaudePath(); 108 | const dirs = await fs.readdir(tasksPath); 109 | 110 | // Get modification time for each api_conversation_history.json 111 | const dirStats = await Promise.all( 112 | dirs.map(async (dir) => { 113 | try { 114 | const historyPath = path.join(tasksPath, dir, 'api_conversation_history.json'); 115 | const stats = await fs.stat(historyPath); 116 | const uiPath = path.join(tasksPath, dir, 'ui_messages.json'); 117 | const uiContent = await fs.readFile(uiPath, 'utf8'); 118 | const uiMessages: UiMessage[] = JSON.parse(uiContent); 119 | const hasEnded = uiMessages.some(m => m.type === 'conversation_ended'); 120 | 121 | return { 122 | dir, 123 | mtime: stats.mtime.getTime(), 124 | hasEnded 125 | }; 126 | } catch (error) { 127 | log('Error checking folder:', dir, error); 128 | return null; 129 | } 130 | }) 131 | ); 132 | 133 | // Filter out errors and ended conversations, then sort by modification time 134 | const sortedDirs = dirStats 135 | .filter((stat): stat is NonNullable<typeof stat> => 136 | stat !== null && !stat.hasEnded 137 | ) 138 | .sort((a, b) => b.mtime - a.mtime); 139 | 140 | // Use most recently modified active conversation 141 | const latest = sortedDirs[0]?.dir; 142 | if (!latest) { 143 | log('No active conversations found'); 144 | return null; 145 | } 146 | 147 | const historyPath = path.join(tasksPath, latest, 'api_conversation_history.json'); 148 | const history = await fs.readFile(historyPath, 'utf8'); 149 | return JSON.parse(history); 150 | } catch (error) { 151 | log('Error finding active conversation:', error); 152 | return null; 153 | } 154 | } 155 | 156 | function formatHistoryForModel(history: ClaudeMessage[], isDeepSeek: boolean): string { 157 | const maxLength = isDeepSeek ? 50000 : 600000; // 50k chars for DeepSeek, 600k for Claude 158 | const formattedMessages = []; 159 | let totalLength = 0; 160 | 161 | // Process messages in reverse chronological order to get most recent first 162 | for (let i = history.length - 1; i >= 0; i--) { 163 | const msg = history[i]; 164 | const content = Array.isArray(msg.content) 165 | ? msg.content.map(c => c.text).join('\n') 166 | : msg.content; 167 | 168 | const formattedMsg = `${msg.role === 'user' ? 'Human' : 'Assistant'}: ${content}`; 169 | const msgLength = formattedMsg.length; 170 | 171 | // Stop adding messages if we'd exceed the limit 172 | if (totalLength + msgLength > maxLength) { 173 | break; 174 | } 175 | 176 | formattedMessages.push(formattedMsg); // Add most recent messages first 177 | totalLength += msgLength; 178 | } 179 | 180 | // Reverse to get chronological order 181 | return formattedMessages.reverse().join('\n\n'); 182 | } 183 | 184 | class DeepseekClaudeServer { 185 | private server: Server; 186 | private openrouterClient: OpenAI; 187 | private context: ConversationContext = { 188 | entries: [], 189 | maxEntries: 10 190 | }; 191 | private activeTasks: Map<string, TaskStatus> = new Map(); 192 | 193 | constructor() { 194 | log('Initializing API clients...'); 195 | 196 | // Initialize OpenRouter client 197 | this.openrouterClient = new OpenAI({ 198 | baseURL: "https://openrouter.ai/api/v1", 199 | apiKey: process.env.OPENROUTER_API_KEY 200 | }); 201 | log('OpenRouter client initialized'); 202 | 203 | // Initialize MCP server 204 | this.server = new Server( 205 | { 206 | name: 'deepseek-thinking-claude-mcp', 207 | version: '0.1.0', 208 | }, 209 | { 210 | capabilities: { 211 | tools: {}, 212 | }, 213 | } 214 | ); 215 | 216 | this.setupToolHandlers(); 217 | 218 | // Error handling 219 | this.server.onerror = (error) => console.error('[MCP Error]', error); 220 | process.on('SIGINT', async () => { 221 | await this.server.close(); 222 | process.exit(0); 223 | }); 224 | } 225 | 226 | private addToContext(entry: ConversationEntry) { 227 | this.context.entries.push(entry); 228 | if (this.context.entries.length > this.context.maxEntries) { 229 | this.context.entries.shift(); // Remove oldest 230 | } 231 | } 232 | 233 | private formatContextForPrompt(): string { 234 | return this.context.entries 235 | .map(entry => `Question: ${entry.prompt}\nReasoning: ${entry.reasoning}\nAnswer: ${entry.response}`) 236 | .join('\n\n'); 237 | } 238 | 239 | private setupToolHandlers() { 240 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 241 | tools: [ 242 | { 243 | name: 'generate_response', 244 | description: 'Generate a response using DeepSeek\'s reasoning and Claude\'s response generation through OpenRouter.', 245 | inputSchema: { 246 | type: 'object', 247 | properties: { 248 | prompt: { 249 | type: 'string', 250 | description: 'The user\'s input prompt' 251 | }, 252 | showReasoning: { 253 | type: 'boolean', 254 | description: 'Whether to include reasoning in response', 255 | default: false 256 | }, 257 | clearContext: { 258 | type: 'boolean', 259 | description: 'Clear conversation history before this request', 260 | default: false 261 | }, 262 | includeHistory: { 263 | type: 'boolean', 264 | description: 'Include Cline conversation history for context', 265 | default: true 266 | } 267 | }, 268 | required: ['prompt'] 269 | } 270 | }, 271 | { 272 | name: 'check_response_status', 273 | description: 'Check the status of a response generation task', 274 | inputSchema: { 275 | type: 'object', 276 | properties: { 277 | taskId: { 278 | type: 'string', 279 | description: 'The task ID returned by generate_response' 280 | } 281 | }, 282 | required: ['taskId'] 283 | } 284 | } 285 | ] 286 | })); 287 | 288 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 289 | if (request.params.name === 'generate_response') { 290 | if (!isValidGenerateResponseArgs(request.params.arguments)) { 291 | throw new McpError( 292 | ErrorCode.InvalidParams, 293 | 'Invalid generate_response arguments' 294 | ); 295 | } 296 | 297 | const taskId = uuidv4(); 298 | const { prompt, showReasoning, clearContext, includeHistory } = request.params.arguments; 299 | 300 | // Initialize task status 301 | this.activeTasks.set(taskId, { 302 | status: 'pending', 303 | prompt, 304 | showReasoning, 305 | timestamp: Date.now() 306 | }); 307 | 308 | // Start processing in background 309 | this.processTask(taskId, clearContext, includeHistory).catch(error => { 310 | log('Error processing task:', error); 311 | this.activeTasks.set(taskId, { 312 | ...this.activeTasks.get(taskId)!, 313 | status: 'error', 314 | error: error.message 315 | }); 316 | }); 317 | 318 | // Return task ID immediately 319 | return { 320 | content: [ 321 | { 322 | type: 'text', 323 | text: JSON.stringify({ taskId }) 324 | } 325 | ] 326 | }; 327 | } else if (request.params.name === 'check_response_status') { 328 | if (!isValidCheckResponseStatusArgs(request.params.arguments)) { 329 | throw new McpError( 330 | ErrorCode.InvalidParams, 331 | 'Invalid check_response_status arguments' 332 | ); 333 | } 334 | 335 | const taskId = request.params.arguments.taskId; 336 | const task = this.activeTasks.get(taskId); 337 | 338 | if (!task) { 339 | throw new McpError( 340 | ErrorCode.InvalidRequest, 341 | `No task found with ID: ${taskId}` 342 | ); 343 | } 344 | 345 | return { 346 | content: [ 347 | { 348 | type: 'text', 349 | text: JSON.stringify({ 350 | status: task.status, 351 | reasoning: task.showReasoning ? task.reasoning : undefined, 352 | response: task.status === 'complete' ? task.response : undefined, 353 | error: task.error 354 | }) 355 | } 356 | ] 357 | }; 358 | } else { 359 | throw new McpError( 360 | ErrorCode.MethodNotFound, 361 | `Unknown tool: ${request.params.name}` 362 | ); 363 | } 364 | }); 365 | } 366 | 367 | private async processTask(taskId: string, clearContext?: boolean, includeHistory?: boolean): Promise<void> { 368 | const task = this.activeTasks.get(taskId); 369 | if (!task) { 370 | throw new Error(`No task found with ID: ${taskId}`); 371 | } 372 | 373 | try { 374 | if (clearContext) { 375 | this.context.entries = []; 376 | } 377 | 378 | // Update status to reasoning 379 | this.activeTasks.set(taskId, { 380 | ...task, 381 | status: 'reasoning' 382 | }); 383 | 384 | // Get Cline conversation history if requested 385 | let history: ClaudeMessage[] | null = null; 386 | if (includeHistory !== false) { 387 | history = await findActiveConversation(); 388 | } 389 | 390 | // Get DeepSeek reasoning with limited history 391 | const reasoningHistory = history ? formatHistoryForModel(history, true) : ''; 392 | const reasoningPrompt = reasoningHistory 393 | ? `${reasoningHistory}\n\nNew question: ${task.prompt}` 394 | : task.prompt; 395 | const reasoning = await this.getDeepseekReasoning(reasoningPrompt); 396 | 397 | // Update status with reasoning 398 | this.activeTasks.set(taskId, { 399 | ...task, 400 | status: 'responding', 401 | reasoning 402 | }); 403 | 404 | // Get final response with full history 405 | const responseHistory = history ? formatHistoryForModel(history, false) : ''; 406 | const fullPrompt = responseHistory 407 | ? `${responseHistory}\n\nCurrent task: ${task.prompt}` 408 | : task.prompt; 409 | const response = await this.getFinalResponse(fullPrompt, reasoning); 410 | 411 | // Add to context after successful response 412 | this.addToContext({ 413 | timestamp: Date.now(), 414 | prompt: task.prompt, 415 | reasoning, 416 | response, 417 | model: CLAUDE_MODEL 418 | }); 419 | 420 | // Update status to complete 421 | this.activeTasks.set(taskId, { 422 | ...task, 423 | status: 'complete', 424 | reasoning, 425 | response, 426 | timestamp: Date.now() 427 | }); 428 | } catch (error) { 429 | // Update status to error 430 | this.activeTasks.set(taskId, { 431 | ...task, 432 | status: 'error', 433 | error: error instanceof Error ? error.message : 'Unknown error', 434 | timestamp: Date.now() 435 | }); 436 | throw error; 437 | } 438 | } 439 | 440 | private async getDeepseekReasoning(prompt: string): Promise<string> { 441 | const contextPrompt = this.context.entries.length > 0 442 | ? `Previous conversation:\n${this.formatContextForPrompt()}\n\nNew question: ${prompt}` 443 | : prompt; 444 | 445 | try { 446 | // Get reasoning from DeepSeek 447 | const response = await this.openrouterClient.chat.completions.create({ 448 | model: DEEPSEEK_MODEL, 449 | messages: [{ 450 | role: "user", 451 | content: contextPrompt 452 | }], 453 | include_reasoning: true, 454 | temperature: 0.7, 455 | top_p: 1 456 | } as any); 457 | 458 | // Get reasoning from response 459 | const responseData = response as any; 460 | if (!responseData.choices?.[0]?.message?.reasoning) { 461 | throw new Error('No reasoning received from DeepSeek'); 462 | } 463 | return responseData.choices[0].message.reasoning; 464 | } catch (error) { 465 | log('Error in getDeepseekReasoning:', error); 466 | throw error; 467 | } 468 | } 469 | 470 | private async getFinalResponse(prompt: string, reasoning: string): Promise<string> { 471 | try { 472 | // Create messages array with proper structure 473 | const messages = [ 474 | // First the user's question 475 | { 476 | role: "user" as const, 477 | content: prompt 478 | }, 479 | // Then the reasoning as assistant's thoughts 480 | { 481 | role: "assistant" as const, 482 | content: `<thinking>${reasoning}</thinking>` 483 | } 484 | ]; 485 | 486 | // If we have context, prepend it as previous turns 487 | if (this.context.entries.length > 0) { 488 | const contextMessages = this.context.entries.flatMap(entry => [ 489 | { 490 | role: "user" as const, 491 | content: entry.prompt 492 | }, 493 | { 494 | role: "assistant" as const, 495 | content: entry.response 496 | } 497 | ]); 498 | messages.unshift(...contextMessages); 499 | } 500 | 501 | const response = await this.openrouterClient.chat.completions.create({ 502 | model: CLAUDE_MODEL, 503 | messages: messages, 504 | temperature: 0.7, 505 | top_p: 1, 506 | repetition_penalty: 1 507 | } as any); 508 | 509 | return response.choices[0].message.content || "Error: No response content"; 510 | } catch (error) { 511 | log('Error in getFinalResponse:', error); 512 | throw error; 513 | } 514 | } 515 | 516 | async run() { 517 | const transport = new StdioServerTransport(); 518 | await this.server.connect(transport); 519 | console.error('DeepSeek-Claude MCP server running on stdio'); 520 | } 521 | } 522 | 523 | const server = new DeepseekClaudeServer(); 524 | server.run().catch(console.error); 525 | ```