├── .env.example ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── smithery.yaml ├── src └── index.ts └── tsconfig.json /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Skirano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP 2 | 3 | [![smithery badge](https://smithery.ai/badge/@newideas99/Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP)](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 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deepseek-thinking-claude-3-5-sonnet-cline-mcp", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "deepseek-thinking-claude-3-5-sonnet-cline-mcp", 9 | "version": "0.1.0", 10 | "dependencies": { 11 | "@anthropic-ai/sdk": "^0.36.2", 12 | "@modelcontextprotocol/sdk": "0.6.0", 13 | "dotenv": "^16.4.7", 14 | "openai": "^4.80.1", 15 | "uuid": "^11.0.5" 16 | }, 17 | "bin": { 18 | "deepseek-thinking-claude-mcp": "build/index.js" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^20.11.24", 22 | "@types/uuid": "^10.0.0", 23 | "typescript": "^5.3.3" 24 | } 25 | }, 26 | "node_modules/@anthropic-ai/sdk": { 27 | "version": "0.36.2", 28 | "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.36.2.tgz", 29 | "integrity": "sha512-+/DcVCGoPtUBOPO+1w+7YPdw9Xzt2M5GW6LpSxeGwDfdUSoJbyezGJvGEtdYjV/XXyGx5pf852S4+PG+3MUEsA==", 30 | "license": "MIT", 31 | "dependencies": { 32 | "@types/node": "^18.11.18", 33 | "@types/node-fetch": "^2.6.4", 34 | "abort-controller": "^3.0.0", 35 | "agentkeepalive": "^4.2.1", 36 | "form-data-encoder": "1.7.2", 37 | "formdata-node": "^4.3.2", 38 | "node-fetch": "^2.6.7" 39 | } 40 | }, 41 | "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { 42 | "version": "18.19.74", 43 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.74.tgz", 44 | "integrity": "sha512-HMwEkkifei3L605gFdV+/UwtpxP6JSzM+xFk2Ia6DNFSwSVBRh9qp5Tgf4lNFOMfPVuU0WnkcWpXZpgn5ufO4A==", 45 | "license": "MIT", 46 | "dependencies": { 47 | "undici-types": "~5.26.4" 48 | } 49 | }, 50 | "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { 51 | "version": "5.26.5", 52 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 53 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 54 | "license": "MIT" 55 | }, 56 | "node_modules/@modelcontextprotocol/sdk": { 57 | "version": "0.6.0", 58 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", 59 | "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", 60 | "license": "MIT", 61 | "dependencies": { 62 | "content-type": "^1.0.5", 63 | "raw-body": "^3.0.0", 64 | "zod": "^3.23.8" 65 | } 66 | }, 67 | "node_modules/@types/node": { 68 | "version": "20.17.16", 69 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.16.tgz", 70 | "integrity": "sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==", 71 | "license": "MIT", 72 | "dependencies": { 73 | "undici-types": "~6.19.2" 74 | } 75 | }, 76 | "node_modules/@types/node-fetch": { 77 | "version": "2.6.12", 78 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", 79 | "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", 80 | "license": "MIT", 81 | "dependencies": { 82 | "@types/node": "*", 83 | "form-data": "^4.0.0" 84 | } 85 | }, 86 | "node_modules/@types/uuid": { 87 | "version": "10.0.0", 88 | "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", 89 | "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", 90 | "dev": true, 91 | "license": "MIT" 92 | }, 93 | "node_modules/abort-controller": { 94 | "version": "3.0.0", 95 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 96 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 97 | "license": "MIT", 98 | "dependencies": { 99 | "event-target-shim": "^5.0.0" 100 | }, 101 | "engines": { 102 | "node": ">=6.5" 103 | } 104 | }, 105 | "node_modules/agentkeepalive": { 106 | "version": "4.6.0", 107 | "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", 108 | "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", 109 | "license": "MIT", 110 | "dependencies": { 111 | "humanize-ms": "^1.2.1" 112 | }, 113 | "engines": { 114 | "node": ">= 8.0.0" 115 | } 116 | }, 117 | "node_modules/asynckit": { 118 | "version": "0.4.0", 119 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 120 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 121 | "license": "MIT" 122 | }, 123 | "node_modules/bytes": { 124 | "version": "3.1.2", 125 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 126 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 127 | "license": "MIT", 128 | "engines": { 129 | "node": ">= 0.8" 130 | } 131 | }, 132 | "node_modules/combined-stream": { 133 | "version": "1.0.8", 134 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 135 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 136 | "license": "MIT", 137 | "dependencies": { 138 | "delayed-stream": "~1.0.0" 139 | }, 140 | "engines": { 141 | "node": ">= 0.8" 142 | } 143 | }, 144 | "node_modules/content-type": { 145 | "version": "1.0.5", 146 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 147 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 148 | "license": "MIT", 149 | "engines": { 150 | "node": ">= 0.6" 151 | } 152 | }, 153 | "node_modules/delayed-stream": { 154 | "version": "1.0.0", 155 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 156 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 157 | "license": "MIT", 158 | "engines": { 159 | "node": ">=0.4.0" 160 | } 161 | }, 162 | "node_modules/depd": { 163 | "version": "2.0.0", 164 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 165 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 166 | "license": "MIT", 167 | "engines": { 168 | "node": ">= 0.8" 169 | } 170 | }, 171 | "node_modules/dotenv": { 172 | "version": "16.4.7", 173 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", 174 | "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", 175 | "license": "BSD-2-Clause", 176 | "engines": { 177 | "node": ">=12" 178 | }, 179 | "funding": { 180 | "url": "https://dotenvx.com" 181 | } 182 | }, 183 | "node_modules/event-target-shim": { 184 | "version": "5.0.1", 185 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 186 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", 187 | "license": "MIT", 188 | "engines": { 189 | "node": ">=6" 190 | } 191 | }, 192 | "node_modules/form-data": { 193 | "version": "4.0.1", 194 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", 195 | "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", 196 | "license": "MIT", 197 | "dependencies": { 198 | "asynckit": "^0.4.0", 199 | "combined-stream": "^1.0.8", 200 | "mime-types": "^2.1.12" 201 | }, 202 | "engines": { 203 | "node": ">= 6" 204 | } 205 | }, 206 | "node_modules/form-data-encoder": { 207 | "version": "1.7.2", 208 | "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", 209 | "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", 210 | "license": "MIT" 211 | }, 212 | "node_modules/formdata-node": { 213 | "version": "4.4.1", 214 | "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", 215 | "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", 216 | "license": "MIT", 217 | "dependencies": { 218 | "node-domexception": "1.0.0", 219 | "web-streams-polyfill": "4.0.0-beta.3" 220 | }, 221 | "engines": { 222 | "node": ">= 12.20" 223 | } 224 | }, 225 | "node_modules/http-errors": { 226 | "version": "2.0.0", 227 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 228 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 229 | "license": "MIT", 230 | "dependencies": { 231 | "depd": "2.0.0", 232 | "inherits": "2.0.4", 233 | "setprototypeof": "1.2.0", 234 | "statuses": "2.0.1", 235 | "toidentifier": "1.0.1" 236 | }, 237 | "engines": { 238 | "node": ">= 0.8" 239 | } 240 | }, 241 | "node_modules/humanize-ms": { 242 | "version": "1.2.1", 243 | "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", 244 | "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", 245 | "license": "MIT", 246 | "dependencies": { 247 | "ms": "^2.0.0" 248 | } 249 | }, 250 | "node_modules/iconv-lite": { 251 | "version": "0.6.3", 252 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 253 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 254 | "license": "MIT", 255 | "dependencies": { 256 | "safer-buffer": ">= 2.1.2 < 3.0.0" 257 | }, 258 | "engines": { 259 | "node": ">=0.10.0" 260 | } 261 | }, 262 | "node_modules/inherits": { 263 | "version": "2.0.4", 264 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 265 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 266 | "license": "ISC" 267 | }, 268 | "node_modules/mime-db": { 269 | "version": "1.52.0", 270 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 271 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 272 | "license": "MIT", 273 | "engines": { 274 | "node": ">= 0.6" 275 | } 276 | }, 277 | "node_modules/mime-types": { 278 | "version": "2.1.35", 279 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 280 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 281 | "license": "MIT", 282 | "dependencies": { 283 | "mime-db": "1.52.0" 284 | }, 285 | "engines": { 286 | "node": ">= 0.6" 287 | } 288 | }, 289 | "node_modules/ms": { 290 | "version": "2.1.3", 291 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 292 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 293 | "license": "MIT" 294 | }, 295 | "node_modules/node-domexception": { 296 | "version": "1.0.0", 297 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 298 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 299 | "funding": [ 300 | { 301 | "type": "github", 302 | "url": "https://github.com/sponsors/jimmywarting" 303 | }, 304 | { 305 | "type": "github", 306 | "url": "https://paypal.me/jimmywarting" 307 | } 308 | ], 309 | "license": "MIT", 310 | "engines": { 311 | "node": ">=10.5.0" 312 | } 313 | }, 314 | "node_modules/node-fetch": { 315 | "version": "2.7.0", 316 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 317 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 318 | "license": "MIT", 319 | "dependencies": { 320 | "whatwg-url": "^5.0.0" 321 | }, 322 | "engines": { 323 | "node": "4.x || >=6.0.0" 324 | }, 325 | "peerDependencies": { 326 | "encoding": "^0.1.0" 327 | }, 328 | "peerDependenciesMeta": { 329 | "encoding": { 330 | "optional": true 331 | } 332 | } 333 | }, 334 | "node_modules/openai": { 335 | "version": "4.80.1", 336 | "resolved": "https://registry.npmjs.org/openai/-/openai-4.80.1.tgz", 337 | "integrity": "sha512-+6+bbXFwbIE88foZsBEt36bPkgZPdyFN82clAXG61gnHb2gXdZApDyRrcAHqEtpYICywpqaNo57kOm9dtnb7Cw==", 338 | "license": "Apache-2.0", 339 | "dependencies": { 340 | "@types/node": "^18.11.18", 341 | "@types/node-fetch": "^2.6.4", 342 | "abort-controller": "^3.0.0", 343 | "agentkeepalive": "^4.2.1", 344 | "form-data-encoder": "1.7.2", 345 | "formdata-node": "^4.3.2", 346 | "node-fetch": "^2.6.7" 347 | }, 348 | "bin": { 349 | "openai": "bin/cli" 350 | }, 351 | "peerDependencies": { 352 | "ws": "^8.18.0", 353 | "zod": "^3.23.8" 354 | }, 355 | "peerDependenciesMeta": { 356 | "ws": { 357 | "optional": true 358 | }, 359 | "zod": { 360 | "optional": true 361 | } 362 | } 363 | }, 364 | "node_modules/openai/node_modules/@types/node": { 365 | "version": "18.19.74", 366 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.74.tgz", 367 | "integrity": "sha512-HMwEkkifei3L605gFdV+/UwtpxP6JSzM+xFk2Ia6DNFSwSVBRh9qp5Tgf4lNFOMfPVuU0WnkcWpXZpgn5ufO4A==", 368 | "license": "MIT", 369 | "dependencies": { 370 | "undici-types": "~5.26.4" 371 | } 372 | }, 373 | "node_modules/openai/node_modules/undici-types": { 374 | "version": "5.26.5", 375 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 376 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 377 | "license": "MIT" 378 | }, 379 | "node_modules/raw-body": { 380 | "version": "3.0.0", 381 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 382 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 383 | "license": "MIT", 384 | "dependencies": { 385 | "bytes": "3.1.2", 386 | "http-errors": "2.0.0", 387 | "iconv-lite": "0.6.3", 388 | "unpipe": "1.0.0" 389 | }, 390 | "engines": { 391 | "node": ">= 0.8" 392 | } 393 | }, 394 | "node_modules/safer-buffer": { 395 | "version": "2.1.2", 396 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 397 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 398 | "license": "MIT" 399 | }, 400 | "node_modules/setprototypeof": { 401 | "version": "1.2.0", 402 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 403 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 404 | "license": "ISC" 405 | }, 406 | "node_modules/statuses": { 407 | "version": "2.0.1", 408 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 409 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 410 | "license": "MIT", 411 | "engines": { 412 | "node": ">= 0.8" 413 | } 414 | }, 415 | "node_modules/toidentifier": { 416 | "version": "1.0.1", 417 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 418 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 419 | "license": "MIT", 420 | "engines": { 421 | "node": ">=0.6" 422 | } 423 | }, 424 | "node_modules/tr46": { 425 | "version": "0.0.3", 426 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 427 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", 428 | "license": "MIT" 429 | }, 430 | "node_modules/typescript": { 431 | "version": "5.7.3", 432 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", 433 | "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", 434 | "dev": true, 435 | "license": "Apache-2.0", 436 | "bin": { 437 | "tsc": "bin/tsc", 438 | "tsserver": "bin/tsserver" 439 | }, 440 | "engines": { 441 | "node": ">=14.17" 442 | } 443 | }, 444 | "node_modules/undici-types": { 445 | "version": "6.19.8", 446 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 447 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 448 | "license": "MIT" 449 | }, 450 | "node_modules/unpipe": { 451 | "version": "1.0.0", 452 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 453 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 454 | "license": "MIT", 455 | "engines": { 456 | "node": ">= 0.8" 457 | } 458 | }, 459 | "node_modules/uuid": { 460 | "version": "11.0.5", 461 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", 462 | "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", 463 | "funding": [ 464 | "https://github.com/sponsors/broofa", 465 | "https://github.com/sponsors/ctavan" 466 | ], 467 | "license": "MIT", 468 | "bin": { 469 | "uuid": "dist/esm/bin/uuid" 470 | } 471 | }, 472 | "node_modules/web-streams-polyfill": { 473 | "version": "4.0.0-beta.3", 474 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", 475 | "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", 476 | "license": "MIT", 477 | "engines": { 478 | "node": ">= 14" 479 | } 480 | }, 481 | "node_modules/webidl-conversions": { 482 | "version": "3.0.1", 483 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 484 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", 485 | "license": "BSD-2-Clause" 486 | }, 487 | "node_modules/whatwg-url": { 488 | "version": "5.0.0", 489 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 490 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 491 | "license": "MIT", 492 | "dependencies": { 493 | "tr46": "~0.0.3", 494 | "webidl-conversions": "^3.0.0" 495 | } 496 | }, 497 | "node_modules/zod": { 498 | "version": "3.24.1", 499 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", 500 | "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", 501 | "license": "MIT", 502 | "funding": { 503 | "url": "https://github.com/sponsors/colinhacks" 504 | } 505 | } 506 | } 507 | } 508 | -------------------------------------------------------------------------------- /package.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 | -------------------------------------------------------------------------------- /smithery.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 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 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 { 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 => 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 = 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 { 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 { 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 { 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: `${reasoning}` 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 | -------------------------------------------------------------------------------- /tsconfig.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 | --------------------------------------------------------------------------------