DeepSeek Vision MCP
# DeepSeek Vision MCP
[](https://github.com/groxaxo/deepseek-vision-mcp/actions/workflows/ci.yml)
[](https://www.python.org/)
[](LICENSE)
A secure, lightweight MCP v2 server that gives any MCP-compatible agent
DeepSeek V4 Flash Vision: image analysis, faithful descriptions, OCR,
comparisons, UI/object localization, and reusable Files API uploads.
The server exposes the exact model `deepseek-v4-flash-vision-exp` without
embedding provider-specific image payloads in every agent integration.
## Tools
| Tool | Purpose |
|---|---|
| `vision_analyze` | General one/multi-image analysis with a custom prompt |
| `vision_describe` | Faithful scene/UI description |
| `vision_ocr` | Screenshot/document OCR |
| `vision_compare` | Compare two or more images |
| `vision_locate` | Best-effort UI/object localization to normalized coordinates |
| `vision_upload` | Upload a local image to DeepSeek Files API |
| `vision_files_list` | List reusable uploaded images |
| `vision_files_delete` | Delete an uploaded image |
Accepted image references:
- local server-side path
- public `http://` or `https://` URL
- `data:image/...;base64,...`
- DeepSeek `file-api-...` file ID
## Why this server is thin
The MCP server does not run a local vision model. It only validates/normalizes
image inputs and delegates inference to DeepSeek. That makes CPU/RAM usage tiny
and lets any MCP-capable agent gain vision without embedding DeepSeek-specific
payload shapes in the agent itself.
## Install with uv
```bash
git clone https://github.com/groxaxo/deepseek-vision-mcp.git
cd deepseek-vision-mcp
uv sync --locked
cp .env.example .env
# Set DEEPSEEK_API_KEY and allowed roots in .env
```
For a local MCP host, `stdio` is the preferred transport:
```bash
DEEPSEEK_API_KEY="..." \
DEEPSEEK_VISION_ALLOWED_ROOTS="/home/you/Pictures:/tmp/vision" \
uv run deepseek-vision-mcp
```
For development with MCP Inspector:
```bash
DEEPSEEK_API_KEY="..." uv run mcp dev src/deepseek_vision_mcp/server.py
```
## MCP host configuration
Typical stdio configuration:
```json
{
"mcpServers": {
"deepseek-vision": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/deepseek-vision-mcp",
"run",
"deepseek-vision-mcp"
],
"env": {
"DEEPSEEK_API_KEY": "YOUR_KEY",
"DEEPSEEK_VISION_ALLOWED_ROOTS": "/home/you/Pictures:/tmp/vision"
}
}
}
}
```
Do not commit the API key. If the host can inherit environment variables,
prefer injecting `DEEPSEEK_API_KEY` from your secret manager or shell.
### Safe shared launcher
`run-mcp.py` is useful when several local agents share one installation. It:
- reads only approved DeepSeek variables from the process or an env file;
- filters unrelated inherited secrets before launching the MCP;
- forces `stdio` transport;
- restricts local images to explicit roots.
By default it reads `~/.hermes/.env` and allows the usual image-working
directories under the current home directory plus `/tmp`. Override those
choices without editing the script:
```bash
export DEEPSEEK_VISION_ENV_FILE="$HOME/.config/deepseek-vision.env"
export DEEPSEEK_VISION_ALLOWED_ROOTS="$HOME/Pictures:/tmp/vision"
./run-mcp.py
```
Hermes configuration:
```yaml
mcp_servers:
deepseek-vision:
command: "/absolute/path/to/deepseek-vision-mcp/run-mcp.py"
args: []
enabled: true
```
OpenCode configuration:
```json
{
"mcp": {
"deepseek-vision": {
"type": "local",
"command": ["/absolute/path/to/deepseek-vision-mcp/run-mcp.py"],
"enabled": true
}
}
}
```
OMP and other standard MCP hosts can use the `mcpServers` example above with
`run-mcp.py` as the command.
## Streamable HTTP
```bash
export DEEPSEEK_API_KEY="..."
export MCP_TRANSPORT=streamable-http
export MCP_HOST=127.0.0.1
export MCP_PORT=8000
uv run deepseek-vision-mcp
```
The MCP endpoint is:
```text
http://127.0.0.1:8000/mcp
```
Use TLS and authentication in front of the server before exposing it outside a
trusted machine/network.
## Example tool calls
### Analyze a screenshot
```json
{
"images": ["/home/you/Pictures/screen.png"],
"prompt": "What application is open, what is the current state, and what should I click next?",
"detail": "original"
}
```
### Fast coarse screen read
```json
{
"images": ["/home/you/Pictures/screen.png"],
"prompt": "Is a modal dialog visible? Answer briefly.",
"detail": "low"
}
```
### OCR
```json
{
"image": "/home/you/Pictures/error.png",
"detail": "original"
}
```
### Best-effort UI grounding
```json
{
"image": "/home/you/Pictures/screen.png",
"target": "the blue Save button"
}
```
`vision_locate` returns coordinates normalized to 0..1000. It is intentionally
described as best-effort: a generative VLM is not a deterministic detector.
Validate its target before high-impact clicks.
## Security model
> Images analyzed by this MCP are sent to DeepSeek's external API. Do not send
> private or sensitive images without informed user intent.
Local paths are restricted to `DEEPSEEK_VISION_ALLOWED_ROOTS`. If no roots are
configured, the server only allows images under its current working directory.
This matters: an unrestricted `vision_analyze("/etc/...")` style tool would let
an MCP host turn image analysis into arbitrary local-file exfiltration.
The server also rejects obvious localhost/private-IP external URLs.
The shared launcher passes only baseline process variables, `XDG_*`, and the
four approved DeepSeek settings to the child process. It never sources an
entire credentials file.
## DeepSeek image behavior reflected by this server
- JPEG, PNG, GIF, WebP
- local/base64 inline image: max 32 MiB each
- DeepSeek Files API image: max 64 MiB
- max 600 images/request
- external URL length: max 8192 chars
- max dimension: 8192 px/side, or 4096 px/side for 15+ images
- `detail=low`: DeepSeek downsamples to 512x512
- `detail=original` / `high`: preserve original detail
- images are only sent in the user message
## Large/reused images
Upload once:
```json
{
"local_path": "/home/you/Pictures/large.png",
"expires_seconds": 86400
}
```
Then pass the returned `file-api-...` ID into `vision_analyze`. Use `null` for
`expires_seconds` only when you intentionally want permanent DeepSeek storage.
## Docker
```bash
docker build -t deepseek-vision-mcp .
docker run --rm \
-p 127.0.0.1:8000:8000 \
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
deepseek-vision-mcp
```
For local image paths in Docker, mount only the directories the MCP needs and
set `DEEPSEEK_VISION_ALLOWED_ROOTS` to the container-side path.
## Architecture
```text
MCP host / agent
|
| MCP tool call
v
DeepSeek Vision MCP
- validates source
- restricts local paths
- encodes local files
- shapes DeepSeek payload
|
| HTTPS
v
api.deepseek.com
deepseek-v4-flash-vision-exp
|
v
structured MCP result
```
## Development
```bash
uv sync --locked --extra dev
uv run ruff check .
uv run pytest
```
CI runs the same gates on Python 3.11 and 3.13. Contributions and focused bug
reports are welcome.
TDQS
Scored across 8 tools
Each tool targets a distinct visual task: custom analysis, faithful description, OCR, comparison, semantic localization, upload, listing, and deletion. The overlaps between analyze and describe are minor and clearly differentiated by purpose and usage notes.
Most tools follow a vision_<verb> pattern (analyze, describe, compare, locate, upload), but vision_files_list and vision_files_delete reverse the noun-verb order, and vision_ocr is an abbreviation noun. The shared prefix provides some consistency, but the mixed conventions prevent a higher score.
Eight tools is a well-scoped number for a vision MCP server, covering both image analysis and file lifecycle management without unnecessary bloat. Each tool serves a clear purpose.
The tool surface covers the major vision tasks (analysis, description, OCR, comparison, localization) and includes full file management via upload, list, and delete. There are no obvious dead ends; images are immutable so update/delete semantics are appropriately handled.