# Directory Structure ``` ├── .env.example ├── .gitignore ├── Dockerfile ├── LICENSE ├── mcp_server_deepseek │ ├── config.py │ └── server.py ├── README.md └── requirements.txt ``` # Files -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` 1 | DEEPSEEK_API_KEY=your_api_key_here ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # PyPI configuration file 171 | .pypirc 172 | 173 | .vscode ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP Server for Deepseek Integration 2 | 3 | This repository contains a Model Control Protocol (MCP) server implementation that allows Claude Desktop to use Deepseek models running in Docker. 4 | 5 | ## Prerequisites 6 | 7 | - Docker 8 | - Python 3.11 or later 9 | - A Deepseek API key 10 | - Claude Desktop 11 | 12 | ## Installation 13 | 14 | 1. Clone the repository: 15 | 16 | ```bash 17 | git clone https://github.com/vincentf305/mcp-server-deepseek.git 18 | cd mcp-server-deepseek 19 | ``` 20 | 21 | 2. Install dependencies: 22 | 23 | ```bash 24 | pip install -r requirements.txt 25 | ``` 26 | 27 | ## Setup Environment Variables 28 | 29 | Create a `.env` file in the root directory of the project and add the following environment variable: 30 | 31 | ``` 32 | DEEPSEEK_API_KEY=your_api_key_here 33 | ``` 34 | 35 | Make sure to replace `your_api_key_here` with your actual Deepseek API key. 36 | 37 | ## Running the Server 38 | 39 | ### Using Docker 40 | 41 | 1. Build the Docker image: 42 | 43 | ```bash 44 | docker build -t mcp_server_deepseek . 45 | ``` 46 | 47 | 2. Run the container: 48 | 49 | ```bash 50 | docker run -d \ 51 | --name mcp-server-deepseek \ 52 | -p 8765:8765 \ 53 | -e DEEPSEEK_API_KEY=your_api_key_here \ 54 | mcp-server-deepseek 55 | ``` 56 | 57 | ### Running Locally 58 | 59 | ```bash 60 | python -m mcp_server_deepseek.server 61 | ``` 62 | 63 | ## Usage with Claude Desktop 64 | 65 | 1. Ensure you have a Deepseek API key 66 | 67 | 2. Add the following to your Claude Desktop configuration (claude_desktop_config.json): 68 | 69 | ```json 70 | { 71 | "mcpServers": { 72 | "deepseek-server": { 73 | "command": "docker", 74 | "args": [ 75 | "run", 76 | "-i", 77 | "--rm", 78 | "-e", 79 | "DEEPSEEK_API_KEY", 80 | "mcp_server_deepseek" 81 | ], 82 | "env": { 83 | "DEEPSEEK_API_KEY": "your_api_key_here" 84 | } 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | 3. Restart Claude Desktop to load the new configuration 91 | 92 | ## Contributing 93 | 94 | 1. Fork the repository 95 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 96 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 97 | 4. Push to the branch (`git push origin feature/amazing-feature`) 98 | 5. Create a Pull Request 99 | 100 | ## License 101 | 102 | MIT License - see the [LICENSE](LICENSE) file for details 103 | ``` -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- ``` 1 | requests==2.31.0 2 | pydantic==2.10.6 3 | pydantic-settings==2.7.1 4 | mcp>=0.9.1 ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | FROM python:3.13-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt . 6 | RUN pip install --no-cache-dir -r requirements.txt 7 | 8 | COPY mcp_server_deepseek/ ./mcp_server_deepseek/ 9 | 10 | EXPOSE 3000 11 | 12 | CMD ["python", "-m", "mcp_server_deepseek.server"] ``` -------------------------------------------------------------------------------- /mcp_server_deepseek/config.py: -------------------------------------------------------------------------------- ```python 1 | from pydantic_settings import BaseSettings 2 | 3 | class Settings(BaseSettings): 4 | deepseek_base_url: str = "https://api.deepseek.com" 5 | deepseek_api_key: str = "" 6 | 7 | class Config: 8 | env_file = ".env" 9 | env_file_encoding = "utf-8" 10 | 11 | settings = Settings() ``` -------------------------------------------------------------------------------- /mcp_server_deepseek/server.py: -------------------------------------------------------------------------------- ```python 1 | import asyncio 2 | import click 3 | import json 4 | import logging 5 | import requests 6 | import sys 7 | 8 | import mcp 9 | import mcp.types as types 10 | 11 | from mcp.server import Server, NotificationOptions 12 | from mcp.server.models import InitializationOptions 13 | 14 | from .config import settings 15 | 16 | logging.basicConfig(level=logging.DEBUG) 17 | logger = logging.getLogger(__name__) 18 | 19 | def serve() -> Server: 20 | server = Server("deepseek-server") 21 | 22 | @server.list_tools() 23 | async def handle_list_tools() -> list[types.Tool]: 24 | return [ 25 | types.Tool( 26 | name="ask-deepseek", 27 | description="Generate responses using the Deepseek model", 28 | inputSchema={ 29 | "type": "object", 30 | "properties": { 31 | "messages": { 32 | "type": "array", 33 | "items": { 34 | "type": "object", 35 | "properties": { 36 | "role": {"type": "string", "enum": ["user", "assistant", "system"]}, 37 | "content": {"type": "string"} 38 | }, 39 | "required": ["role", "content"] 40 | } 41 | }, 42 | "model": {"type": "string", "default": "deepseek-coder", "enum": ["deepseek-coder", "deepseek-chat"]}, 43 | "temperature": {"type": "number", "default": 0.7, "minimum": 0, "maximum": 2}, 44 | "max_tokens": {"type": "integer", "default": 500, "minimum": 1, "maximum": 4000}, 45 | "top_p": {"type": "number", "default": 1.0, "minimum": 0, "maximum": 1}, 46 | "stream": {"type": "boolean", "default": False} 47 | }, 48 | "required": ["messages"] 49 | } 50 | ) 51 | ] 52 | 53 | @server.call_tool() 54 | async def handle_tool_call(name: str, arguments: dict | None) -> list[types.TextContent]: 55 | try: 56 | if not arguments: 57 | raise ValueError("No arguments provided") 58 | 59 | if name == "ask-deepseek": 60 | messages = arguments["messages"] 61 | model = arguments.get("model", "deepseek-coder") 62 | temperature = arguments.get("temperature", 0.7) 63 | max_tokens = arguments.get("max_tokens", 500) 64 | top_p = arguments.get("top_p", 1.0) 65 | stream = arguments.get("stream", False) 66 | 67 | deepseek_request = { 68 | "model": model, 69 | "messages": messages, 70 | "temperature": temperature, 71 | "max_tokens": max_tokens, 72 | "top_p": top_p, 73 | "stream": stream 74 | } 75 | 76 | json_data = json.dumps(deepseek_request) 77 | 78 | response = requests.post( 79 | f"{settings.deepseek_base_url}/v1/chat/completions", 80 | headers={ 81 | "Authorization": f"Bearer {settings.deepseek_api_key}", 82 | "Content-Type": "application/json" 83 | }, 84 | data=json_data 85 | ) 86 | 87 | response.raise_for_status() 88 | data = response.json() 89 | chat_response = data["choices"][0]["message"]["content"] 90 | 91 | return [types.TextContent(type="text", text=chat_response)] 92 | 93 | raise ValueError(f"Unknown tool: {name}") 94 | except Exception as e: 95 | logger.error(f"Tool call failed: {str(e)}") 96 | return [types.TextContent(type="text", text=f"Error: {str(e)}")] 97 | 98 | return server 99 | 100 | @click.command() 101 | def main(): 102 | try: 103 | async def _run(): 104 | async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): 105 | server = serve() 106 | await server.run( 107 | read_stream, write_stream, 108 | InitializationOptions( 109 | server_name="deepseek-server", 110 | server_version="0.1.0", 111 | capabilities=server.get_capabilities( 112 | notification_options=NotificationOptions(), 113 | experimental_capabilities={} 114 | ) 115 | ) 116 | ) 117 | asyncio.run(_run()) 118 | except KeyboardInterrupt: 119 | logger.info("Server stopped by user") 120 | except Exception as e: 121 | logger.exception("Server failed") 122 | sys.exit(1) 123 | 124 | if __name__ == "__main__": 125 | main() ```