# Directory Structure ``` ├── .env.example ├── .gitignore ├── Dockerfile ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── smithery.yaml ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Dependencies node_modules/ # Build output build/ dist/ # Logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Environment variables .env .env.local .env.*.local # Editor directories and files .idea/ .vscode/ *.swp *.swo *.swn .DS_Store ``` -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` # Required: OpenRouter API key for both DeepSeek and Claude models OPENROUTER_API_KEY=your_openrouter_api_key_here # Optional: Model configuration (defaults shown below) DEEPSEEK_MODEL=deepseek/deepseek-r1:free # DeepSeek model for reasoning CLAUDE_MODEL=anthropic/claude-3.5-sonnet:beta # Claude model for responses ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP [](https://smithery.ai/server/@newideas99/Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP) 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. ## Features - **Two-Stage Processing**: - Uses DeepSeek R1 for initial reasoning (50k character context) - Uses Claude 3.5 Sonnet for final response (600k character context) - Both models accessed through OpenRouter's unified API - Injects DeepSeek's reasoning tokens into Claude's context - **Smart Conversation Management**: - Detects active conversations using file modification times - Handles multiple concurrent conversations - Filters out ended conversations automatically - Supports context clearing when needed - **Optimized Parameters**: - Model-specific context limits: * DeepSeek: 50,000 characters for focused reasoning * Claude: 600,000 characters for comprehensive responses - Recommended settings: * temperature: 0.7 for balanced creativity * top_p: 1.0 for full probability distribution * repetition_penalty: 1.0 to prevent repetition ## Installation ### Installing via Smithery 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): ```bash npx -y @smithery/cli install @newideas99/Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP --client claude ``` ### Manual Installation 1. Clone the repository: ```bash git clone https://github.com/yourusername/Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP.git cd Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP ``` 2. Install dependencies: ```bash npm install ``` 3. Create a `.env` file with your OpenRouter API key: ```env # Required: OpenRouter API key for both DeepSeek and Claude models OPENROUTER_API_KEY=your_openrouter_api_key_here # Optional: Model configuration (defaults shown below) DEEPSEEK_MODEL=deepseek/deepseek-r1 # DeepSeek model for reasoning CLAUDE_MODEL=anthropic/claude-3.5-sonnet:beta # Claude model for responses ``` 4. Build the server: ```bash npm run build ``` ## Usage with Cline Add to your Cline MCP settings (usually in `~/.vscode/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`): ```json { "mcpServers": { "deepseek-claude": { "command": "/path/to/node", "args": ["/path/to/Deepseek-Thinking-Claude-3.5-Sonnet-CLINE-MCP/build/index.js"], "env": { "OPENROUTER_API_KEY": "your_key_here" }, "disabled": false, "autoApprove": [] } } } ``` ## Tool Usage The server provides two tools for generating and monitoring responses: ### generate_response Main tool for generating responses with the following parameters: ```typescript { "prompt": string, // Required: The question or prompt "showReasoning"?: boolean, // Optional: Show DeepSeek's reasoning process "clearContext"?: boolean, // Optional: Clear conversation history "includeHistory"?: boolean // Optional: Include Cline conversation history } ``` ### check_response_status Tool for checking the status of a response generation task: ```typescript { "taskId": string // Required: The task ID from generate_response } ``` ### Response Polling The server uses a polling mechanism to handle long-running requests: 1. Initial Request: - `generate_response` returns immediately with a task ID - Response format: `{"taskId": "uuid-here"}` 2. Status Checking: - Use `check_response_status` to poll the task status - **Note:** Responses can take up to 60 seconds to complete - Status progresses through: pending → reasoning → responding → complete Example usage in Cline: ```typescript // Initial request const result = await use_mcp_tool({ server_name: "deepseek-claude", tool_name: "generate_response", arguments: { prompt: "What is quantum computing?", showReasoning: true } }); // Get taskId from result const taskId = JSON.parse(result.content[0].text).taskId; // Poll for status (may need multiple checks over ~60 seconds) const status = await use_mcp_tool({ server_name: "deepseek-claude", tool_name: "check_response_status", arguments: { taskId } }); // Example status response when complete: { "status": "complete", "reasoning": "...", // If showReasoning was true "response": "..." // The final response } ``` ## Development For development with auto-rebuild: ```bash npm run watch ``` ## How It Works 1. **Reasoning Stage (DeepSeek R1)**: - Uses OpenRouter's reasoning tokens feature - Prompt is modified to output 'done' while capturing reasoning - Reasoning is extracted from response metadata 2. **Response Stage (Claude 3.5 Sonnet)**: - Receives the original prompt and DeepSeek's reasoning - Generates final response incorporating the reasoning - Maintains conversation context and history ## License MIT License - See LICENSE file for details. ## Credits 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. This implementation specifically combines DeepSeek R1's reasoning capabilities with Claude 3.5 Sonnet's response generation through OpenRouter's unified API. ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml startCommand: type: stdio configSchema: # JSON Schema defining the configuration options for the MCP. type: object required: - openrouterApiKey properties: openrouterApiKey: type: string description: The API key for accessing the OpenRouter service. commandFunction: # A function that produces the CLI command to start the MCP on stdio. |- (config) => ({ command: 'node', args: ['build/index.js'], env: { OPENROUTER_API_KEY: config.openrouterApiKey } }) ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "deepseek-thinking-claude-3-5-sonnet-cline-mcp", "version": "0.1.0", "description": "MCP server that combines DeepSeek's reasoning with Claude 3.5 Sonnet's response generation through Cline", "private": true, "type": "module", "bin": { "deepseek-thinking-claude-mcp": "./build/index.js" }, "files": [ "build" ], "scripts": { "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", "prepare": "npm run build", "watch": "tsc --watch", "inspector": "npx @modelcontextprotocol/inspector build/index.js" }, "dependencies": { "@anthropic-ai/sdk": "^0.36.2", "@modelcontextprotocol/sdk": "0.6.0", "dotenv": "^16.4.7", "openai": "^4.80.1", "uuid": "^11.0.5" }, "devDependencies": { "@types/node": "^20.11.24", "@types/uuid": "^10.0.0", "typescript": "^5.3.3" } } ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile # Stage 1: Build the application using Node.js FROM node:18-alpine AS builder # Set working directory WORKDIR /app # Copy package.json and package-lock.json to the working directory COPY package.json package-lock.json ./ # Install dependencies RUN npm install # Copy source files COPY src ./src # Build the project RUN npm run build # Stage 2: Create a lightweight image for production FROM node:18-alpine # Set working directory WORKDIR /app # Copy built files from builder COPY --from=builder /app/build ./build # Copy necessary files COPY package.json package-lock.json ./ # Install only production dependencies RUN npm install --omit=dev # Environment variables ENV NODE_ENV=production # Entrypoint command to run the MCP server ENTRYPOINT ["node", "build/index.js"] # Command to start the server CMD ["node", "build/index.js"] ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { OpenAI } from 'openai'; import dotenv from 'dotenv'; import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs/promises'; import { v4 as uuidv4 } from 'uuid'; // Load environment variables dotenv.config(); // Debug logging const DEBUG = true; const log = (...args: any[]) => { if (DEBUG) { console.error('[DEEPSEEK-CLAUDE MCP]', ...args); } }; // Constants const DEEPSEEK_MODEL = "deepseek/deepseek-r1"; const CLAUDE_MODEL = "anthropic/claude-3.5-sonnet:beta"; interface ConversationEntry { timestamp: number; prompt: string; reasoning: string; response: string; model: string; } interface ConversationContext { entries: ConversationEntry[]; maxEntries: number; } interface GenerateResponseArgs { prompt: string; showReasoning?: boolean; clearContext?: boolean; includeHistory?: boolean; } interface CheckResponseStatusArgs { taskId: string; } interface TaskStatus { status: 'pending' | 'reasoning' | 'responding' | 'complete' | 'error'; prompt: string; showReasoning?: boolean; reasoning?: string; response?: string; error?: string; timestamp: number; } const isValidCheckResponseStatusArgs = (args: any): args is CheckResponseStatusArgs => typeof args === 'object' && args !== null && typeof args.taskId === 'string'; interface ClaudeMessage { role: 'user' | 'assistant'; content: string | { type: string; text: string }[]; } interface UiMessage { ts: number; type: string; say?: string; ask?: string; text: string; conversationHistoryIndex: number; } const isValidGenerateResponseArgs = (args: any): args is GenerateResponseArgs => typeof args === 'object' && args !== null && typeof args.prompt === 'string' && (args.showReasoning === undefined || typeof args.showReasoning === 'boolean') && (args.clearContext === undefined || typeof args.clearContext === 'boolean') && (args.includeHistory === undefined || typeof args.includeHistory === 'boolean'); function getClaudePath(): string { const homeDir = os.homedir(); switch (process.platform) { case 'win32': return path.join(homeDir, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'tasks'); case 'darwin': return path.join(homeDir, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'tasks'); default: // linux return path.join(homeDir, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'tasks'); } } async function findActiveConversation(): Promise<ClaudeMessage[] | null> { try { const tasksPath = getClaudePath(); const dirs = await fs.readdir(tasksPath); // Get modification time for each api_conversation_history.json const dirStats = await Promise.all( dirs.map(async (dir) => { try { const historyPath = path.join(tasksPath, dir, 'api_conversation_history.json'); const stats = await fs.stat(historyPath); const uiPath = path.join(tasksPath, dir, 'ui_messages.json'); const uiContent = await fs.readFile(uiPath, 'utf8'); const uiMessages: UiMessage[] = JSON.parse(uiContent); const hasEnded = uiMessages.some(m => m.type === 'conversation_ended'); return { dir, mtime: stats.mtime.getTime(), hasEnded }; } catch (error) { log('Error checking folder:', dir, error); return null; } }) ); // Filter out errors and ended conversations, then sort by modification time const sortedDirs = dirStats .filter((stat): stat is NonNullable<typeof stat> => stat !== null && !stat.hasEnded ) .sort((a, b) => b.mtime - a.mtime); // Use most recently modified active conversation const latest = sortedDirs[0]?.dir; if (!latest) { log('No active conversations found'); return null; } const historyPath = path.join(tasksPath, latest, 'api_conversation_history.json'); const history = await fs.readFile(historyPath, 'utf8'); return JSON.parse(history); } catch (error) { log('Error finding active conversation:', error); return null; } } function formatHistoryForModel(history: ClaudeMessage[], isDeepSeek: boolean): string { const maxLength = isDeepSeek ? 50000 : 600000; // 50k chars for DeepSeek, 600k for Claude const formattedMessages = []; let totalLength = 0; // Process messages in reverse chronological order to get most recent first for (let i = history.length - 1; i >= 0; i--) { const msg = history[i]; const content = Array.isArray(msg.content) ? msg.content.map(c => c.text).join('\n') : msg.content; const formattedMsg = `${msg.role === 'user' ? 'Human' : 'Assistant'}: ${content}`; const msgLength = formattedMsg.length; // Stop adding messages if we'd exceed the limit if (totalLength + msgLength > maxLength) { break; } formattedMessages.push(formattedMsg); // Add most recent messages first totalLength += msgLength; } // Reverse to get chronological order return formattedMessages.reverse().join('\n\n'); } class DeepseekClaudeServer { private server: Server; private openrouterClient: OpenAI; private context: ConversationContext = { entries: [], maxEntries: 10 }; private activeTasks: Map<string, TaskStatus> = new Map(); constructor() { log('Initializing API clients...'); // Initialize OpenRouter client this.openrouterClient = new OpenAI({ baseURL: "https://openrouter.ai/api/v1", apiKey: process.env.OPENROUTER_API_KEY }); log('OpenRouter client initialized'); // Initialize MCP server this.server = new Server( { name: 'deepseek-thinking-claude-mcp', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); // Error handling this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private addToContext(entry: ConversationEntry) { this.context.entries.push(entry); if (this.context.entries.length > this.context.maxEntries) { this.context.entries.shift(); // Remove oldest } } private formatContextForPrompt(): string { return this.context.entries .map(entry => `Question: ${entry.prompt}\nReasoning: ${entry.reasoning}\nAnswer: ${entry.response}`) .join('\n\n'); } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'generate_response', description: 'Generate a response using DeepSeek\'s reasoning and Claude\'s response generation through OpenRouter.', inputSchema: { type: 'object', properties: { prompt: { type: 'string', description: 'The user\'s input prompt' }, showReasoning: { type: 'boolean', description: 'Whether to include reasoning in response', default: false }, clearContext: { type: 'boolean', description: 'Clear conversation history before this request', default: false }, includeHistory: { type: 'boolean', description: 'Include Cline conversation history for context', default: true } }, required: ['prompt'] } }, { name: 'check_response_status', description: 'Check the status of a response generation task', inputSchema: { type: 'object', properties: { taskId: { type: 'string', description: 'The task ID returned by generate_response' } }, required: ['taskId'] } } ] })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === 'generate_response') { if (!isValidGenerateResponseArgs(request.params.arguments)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid generate_response arguments' ); } const taskId = uuidv4(); const { prompt, showReasoning, clearContext, includeHistory } = request.params.arguments; // Initialize task status this.activeTasks.set(taskId, { status: 'pending', prompt, showReasoning, timestamp: Date.now() }); // Start processing in background this.processTask(taskId, clearContext, includeHistory).catch(error => { log('Error processing task:', error); this.activeTasks.set(taskId, { ...this.activeTasks.get(taskId)!, status: 'error', error: error.message }); }); // Return task ID immediately return { content: [ { type: 'text', text: JSON.stringify({ taskId }) } ] }; } else if (request.params.name === 'check_response_status') { if (!isValidCheckResponseStatusArgs(request.params.arguments)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid check_response_status arguments' ); } const taskId = request.params.arguments.taskId; const task = this.activeTasks.get(taskId); if (!task) { throw new McpError( ErrorCode.InvalidRequest, `No task found with ID: ${taskId}` ); } return { content: [ { type: 'text', text: JSON.stringify({ status: task.status, reasoning: task.showReasoning ? task.reasoning : undefined, response: task.status === 'complete' ? task.response : undefined, error: task.error }) } ] }; } else { throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } }); } private async processTask(taskId: string, clearContext?: boolean, includeHistory?: boolean): Promise<void> { const task = this.activeTasks.get(taskId); if (!task) { throw new Error(`No task found with ID: ${taskId}`); } try { if (clearContext) { this.context.entries = []; } // Update status to reasoning this.activeTasks.set(taskId, { ...task, status: 'reasoning' }); // Get Cline conversation history if requested let history: ClaudeMessage[] | null = null; if (includeHistory !== false) { history = await findActiveConversation(); } // Get DeepSeek reasoning with limited history const reasoningHistory = history ? formatHistoryForModel(history, true) : ''; const reasoningPrompt = reasoningHistory ? `${reasoningHistory}\n\nNew question: ${task.prompt}` : task.prompt; const reasoning = await this.getDeepseekReasoning(reasoningPrompt); // Update status with reasoning this.activeTasks.set(taskId, { ...task, status: 'responding', reasoning }); // Get final response with full history const responseHistory = history ? formatHistoryForModel(history, false) : ''; const fullPrompt = responseHistory ? `${responseHistory}\n\nCurrent task: ${task.prompt}` : task.prompt; const response = await this.getFinalResponse(fullPrompt, reasoning); // Add to context after successful response this.addToContext({ timestamp: Date.now(), prompt: task.prompt, reasoning, response, model: CLAUDE_MODEL }); // Update status to complete this.activeTasks.set(taskId, { ...task, status: 'complete', reasoning, response, timestamp: Date.now() }); } catch (error) { // Update status to error this.activeTasks.set(taskId, { ...task, status: 'error', error: error instanceof Error ? error.message : 'Unknown error', timestamp: Date.now() }); throw error; } } private async getDeepseekReasoning(prompt: string): Promise<string> { const contextPrompt = this.context.entries.length > 0 ? `Previous conversation:\n${this.formatContextForPrompt()}\n\nNew question: ${prompt}` : prompt; try { // Get reasoning from DeepSeek const response = await this.openrouterClient.chat.completions.create({ model: DEEPSEEK_MODEL, messages: [{ role: "user", content: contextPrompt }], include_reasoning: true, temperature: 0.7, top_p: 1 } as any); // Get reasoning from response const responseData = response as any; if (!responseData.choices?.[0]?.message?.reasoning) { throw new Error('No reasoning received from DeepSeek'); } return responseData.choices[0].message.reasoning; } catch (error) { log('Error in getDeepseekReasoning:', error); throw error; } } private async getFinalResponse(prompt: string, reasoning: string): Promise<string> { try { // Create messages array with proper structure const messages = [ // First the user's question { role: "user" as const, content: prompt }, // Then the reasoning as assistant's thoughts { role: "assistant" as const, content: `<thinking>${reasoning}</thinking>` } ]; // If we have context, prepend it as previous turns if (this.context.entries.length > 0) { const contextMessages = this.context.entries.flatMap(entry => [ { role: "user" as const, content: entry.prompt }, { role: "assistant" as const, content: entry.response } ]); messages.unshift(...contextMessages); } const response = await this.openrouterClient.chat.completions.create({ model: CLAUDE_MODEL, messages: messages, temperature: 0.7, top_p: 1, repetition_penalty: 1 } as any); return response.choices[0].message.content || "Error: No response content"; } catch (error) { log('Error in getFinalResponse:', error); throw error; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('DeepSeek-Claude MCP server running on stdio'); } } const server = new DeepseekClaudeServer(); server.run().catch(console.error); ```