dsh-mcp
README.md
<div align="center">
# dsh-mcp ⚡
### Fast and cheap — the heavy work runs on deepseek-v4-flash.
### An execution layer for MCP agents — powered by a local DeepSeek Harness.
Agentic coding tools are expensive and slow for one reason: the parent model
reads files one by one. `dsh-mcp` flips that — **the parent agent keeps the
reasoning, the local DeepSeek Harness does the heavy lifting**: batch reads,
edits, and command runs, priced at deepseek-v4-flash rates, on your machine.



**Works with any MCP client · tested end-to-end with Claude Code**
**🌐 [English](README.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja-JP.md)**
</div>
---
## Why: fast and cheap, by design
**The pain.** Long-horizon, multi-file work is where agentic tools bleed money
and time: the parent model grinds through every file itself — one `Read` per
file, exploration loops, context that grows and drifts. You pay the parent's
per-token rates for the *reading*, and you wait for the model to do it
*serially*.
**The fix.** One `delegate_to_dsh` call hands the whole token-heavy subtask to
a fresh, focused DeepSeek Harness agent running **deepseek-v4-flash** inside
your workspace. All the file I/O happens there — batched, local, cheap. The
parent stays in the loop for what actually needs a brain: **planning,
diagnosis, decisions, verification**.
Measured on 100 SWE-bench-style long-horizon tasks (heterogeneous model setup:
parent = deepseek-v4-pro, DSH = deepseek-v4-flash; arm B runs the same parent
model without the plugin):
| | with `dsh-mcp` | raw agent |
|---|---|---|
| **Accuracy** | **100/100 · 100%** | 89/100 · 89% |
| **Avg. wall time** | **76s** | 120s |
| **Statistical significance** | — | Fisher p < 0.001 |
Faster, more accurate — and the accuracy gap is now statistically significant
(p = 3.7×10⁻⁴ at n=100), not just directional. The expensive reads now run on
deepseek-v4-flash. [Full report](tests/bench/bench-report.md) ·
[Reproduce it](#benchmark)
## How it works

*The prompt used to generate this is in
[`docs/architecture-image-prompt.md`](docs/architecture-image-prompt.md).*
- **A standard MCP stdio server.** Any client that speaks MCP can use it —
Claude Code is the client we validated end-to-end (real API calls, subdir
launches, Chinese, spaces in paths).
- **Parent-agent model.** Each delegation is one self-contained subtask; DSH is
a stateless worker (fresh process per call — no session to corrupt).
- **Zero config, works out of the box.** The DSH CLI is auto-discovered (env override →
sibling checkout → child checkout → installed under `%USERPROFILE%\.dsh`).
- **Windows-first.** `process.execPath` + explicit argv, `shell: false` — no
quoting bugs, no `.cmd` shims, spaces-in-paths just work.
## Quick start
**Step 1 — build once**
```powershell
cd dsh-mcp
npm install
npm run build # → dist\index.js
```
**Step 2 — register with your MCP client (Claude Code shown as the example)**
```powershell
claude mcp add dsh `
--transport stdio `
--env DEEPSEEK_API_KEY=sk-... `
-- node D:\path\to\dsh-mcp\dist\index.js
claude mcp list # confirm "dsh" is listed
```
This is a standard MCP server — the same `dist\index.js` also works with
OpenAI Codex, Cursor, VS Code and any other MCP client (see
[Deployment](#deployment)).
**Step 3 — restart Claude Code, then make your first delegation**
Claude Code loads MCP servers at startup, so restart it inside any project.
Then just ask, in plain words:
> Have DSH read this project's README.md and package.json, and report the
> project name and the first paragraph of the README. Don't read the files
> yourself — delegate everything.
You'll see Claude call `delegate_to_dsh`, and the answer comes back in
seconds. DSH runs in **your** project root — never in the `dsh-mcp` directory.
> `delegate_to_dsh` not showing up? Re-run `claude mcp list` — if the server
> failed to start, it will report an error there. Jump to
> [Troubleshooting](#troubleshooting).
## Benchmark
> 100 long-horizon, multi-file tasks in two batches (01–50: bug fixes,
> refactors, migrations, implement-from-spec, test-driven, audit; 51–100:
> harder — deep data flows, async races, state machines, parsers, caches,
> cross-process state) · automated ground-truth verification (run assertions +
> stdout checks; clean fixtures must fail, reference fixes must pass)
> · heterogeneous model setup: parent = deepseek-v4-pro on both arms, DSH =
> deepseek-v4-flash on arm A · full breakdown:
> [`tests/bench/bench-report.md`](tests/bench/bench-report.md)
> · **experiment note: the Claude Code in this benchmark runs on a
> deepseek-v4-pro kernel** (configured via the `ANTHROPIC_MODEL` env var) —
> the parent is a DeepSeek model, not an Anthropic model, on both arms. The
> delta is therefore exactly the plugin: the same DeepSeek parent, with vs.
> without `dsh-mcp`
> · dataset: [`tests/bench/tasks.mjs`](tests/bench/tasks.mjs)
| | with `dsh-mcp` | raw agent |
|---|---|---|
| **Accuracy** | **100/100 · 100%** | 89/100 · 89% |
| **Avg. wall time** | **76.5s** | 120.2s |
| **Total wall time** | 7647s | 12019s |
| **Failures** | 0 | 11 |
**What the numbers say**
- **Every raw-agent failure is a "finished half" failure.** 5 of 11 are
cross-file jobs (renames, log migrations, util splits) where the raw agent
changed references but left the old file behind, or migrated 3 of 5 files;
5 are single-point jobs where it fixed one bug out of two, escaped 3 of 5
HTML chars, deduped but didn't sort. DSH verifies inside the delegation and
catches the leftovers before handing back.
- **Multi-file investigation stays the biggest win** — batch "read N files +
analyze" in one delegation vs. file-by-file reads that lose early context.
- **Statistically significant now.** Fisher exact test on accuracy
(100% vs 89%, n=100): p = 3.7×10⁻⁴ (< 0.001). The 20-task pilot was
directional only; at 100 tasks the gap is real.
- **The harder batch (51–100) makes the plugin look better, not worse** —
arm A: 100% in both batches, avg 65s on the hard batch; arm B: 88% → 90%
but still leaves 11 failures overall.
- **Honest exception** — one shared-module extraction (10) was slower through
DSH (multi-file rewrite round-trips). Nothing like it reappeared in the hard
batch.
Reproduce:
```powershell
DEEPSEEK_API_KEY=sk-... node tests\bench\run-bench.mjs AB 01-50 # both arms, 100 tasks
DEEPSEEK_API_KEY=sk-... node tests\bench\run-bench.mjs AB 51-100 # (range filter)
DEEPSEEK_API_KEY=sk-... node tests\e2e-headless.mjs # real E2E (A/B/C/D)
DEEPSEEK_API_KEY=sk-... node tests\long-horizon-e2e.mjs # multi-delegation loop
```
> Keys only ever enter a temp `mcp.json` inside a sandbox, deleted on exit —
> never written to the repo, never printed.
## Tools
One tool family, split by responsibility so the parent agent picks by intent
(tool name + description are the model's decision input):
| Tool | Use it for | Constraint |
|---|---|---|
| `delegate_to_dsh` | generic self-contained tasks | none — everything is up to your task text |
| `dsh_investigate` | read-only analysis: read files, trace call flows, "find where X is used" | the agent is instructed to never modify files or run side-effecting commands |
| `dsh_fix` | modifying code: bug fixes, refactors, migrations, well-specified implementations | the agent verifies (when the task names a check) and lists every file it changed |
| `dsh_execute` | running commands: test suites, build scripts, environment queries | the agent reports full output + exit code, leaves source files alone |
| `dsh_status` | environment diagnostics: DSH resolution, CLI version, model config, timeout, key presence | no DSH child process, no API key required |
All delegation tools share one schema: `task` (required), `cwd` (optional),
`timeoutMs` (per-call override of `DSH_MCP_TIMEOUT_MS`) and `trackChanges`
(default true).
**Change tracking — the black box gets a tail.** DSH's headless CLI only
returns its final message: no diff, no file list. Every delegation therefore
snapshots your workspace before and after and appends a `[mcp]` block to the
result, so the parent can see what the delegation actually touched:
```text
<DSH final output>
[mcp] exitCode: 0
[mcp] durationMs: 45210
[mcp] cwd: D:\workspace\foo
[mcp] changedFiles: 2
[mcp] M src/store.js (modified)
[mcp] A src/store.test.js (added)
```
Use `trackChanges: false` (or `DSH_MCP_TRACK_CHANGES=0`) to skip snapshots on
very large repositories. Dependency/build dirs (`node_modules`, `.git`,
`dist`, `build`, `coverage`, …) and hidden dirs are always excluded.
> Category constraints are enforced via task templates, not the CLI — DSH's
> headless mode has no read-only flag. Change tracking is the safety net: if
> `dsh_investigate` did modify something anyway, the `[mcp] changedFiles`
> list exposes it and the parent can check the files itself.
## Deployment
`dsh-mcp` is a **standard MCP stdio server**. Any client that speaks MCP can
host it — Claude Code, OpenAI Codex, Cursor, VS Code, Claude Desktop, or your
own tooling. The server and the `dsh` tool family are identical everywhere;
only the registration step differs. Claude Code is the client we validated
end-to-end.
### Claude Code (one command — the example)
```powershell
claude mcp add dsh `
--transport stdio `
--env DEEPSEEK_API_KEY=sk-... `
-- node D:\path\to\dsh-mcp\dist\index.js
claude mcp list
```
### OpenAI Codex
Codex loads MCP servers from its config at `%USERPROFILE%\.codex\config.toml`
(project-level: `.codex\config.toml`). Append:
```toml
[mcp_servers.dsh]
command = "node"
args = ["D:\\path\\to\\dsh-mcp\\dist\\index.js"]
env = { DEEPSEEK_API_KEY = "sk-..." }
```
Restart `codex`, then just ask — e.g. "Use the dsh_investigate tool to read
the README and package.json and report the project name." Codex cannot see
inside DSH (it is a black box to the host), which is exactly why every
delegation returns the `[mcp] changedFiles` block — the parent learns what
the delegation touched even when the host is not Claude Code.
### Any other MCP client
Point your client at `dist\index.js` with the same env vars. Most clients
accept a JSON `mcpServers` block (Claude Code project-level config, Cursor
`mcp.json`, VS Code, Claude Desktop, …):
```json
{
"mcpServers": {
"dsh": {
"command": "node",
"args": ["D:\\path\\to\\dsh-mcp\\dist\\index.js"],
"env": { "DEEPSEEK_API_KEY": "sk-..." }
}
}
}
```
> Don't commit the key. The automation scripts delete their temp config before
> exiting.
### Where can DSH be installed?
DSH is located in exactly four places, checked in order — **the server never
looks at `PATH`**:
1. `DSH_ROOT` env var — explicit override, wins unconditionally
2. A sibling checkout next to this package: `..\deepseek-harness`
3. A child checkout inside this package: `.\deepseek-harness`
4. An installed CLI package under the DSH home:
`%USERPROFILE%\.dsh\profiles\node_modules\@deepseek-ai\dsh`
(`DSH_HOME` overrides the home directory)
So you can install DSH **anywhere** — a sibling checkout, a dedicated tools
directory, a different drive — and point `DSH_ROOT` at it. Either layout
works: a source checkout (build its CLI first: `pnpm install && pnpm run build`)
or an installed CLI package (`<root>\lib\bin.js`). Anything outside these four
locations is simply not discovered; the error message tells you to set
`DSH_ROOT`.
```powershell
# register with a DSH that lives in your own directory
claude mcp add dsh `
--transport stdio `
--env DEEPSEEK_API_KEY=sk-... `
--env DSH_ROOT=D:\tools\deepseek-harness `
-- node D:\path\to\dsh-mcp\dist\index.js
```
### Environment variables
| Variable | Meaning |
| --- | --- |
| `DEEPSEEK_API_KEY` | **Required.** Passed to DSH through the environment. |
| `DSH_ROOT` | Optional. Point at a DSH checkout / installed CLI. |
| `DSH_HOME` | Optional. DSH home (default `%USERPROFILE%\.dsh`). |
| `DSH_MCP_TIMEOUT_MS` | Optional. Child timeout; **unset = no timeout**. |
| `DSH_MCP_DEBUG=1` | stderr diagnostics (`[dsh-mcp] dshRoot=... cli=...`). |
### Troubleshooting
| Symptom | Fix |
| --- | --- |
| `Unable to locate DeepSeek Harness` | Set `DSH_ROOT`, or install the DSH CLI (`npx @deepseek-ai/dsh`) |
| `CLI build artifact is missing` | In the DSH checkout: `pnpm install && pnpm run build` |
| `DEEPSEEK_API_KEY is not configured` | Re-add with `--env DEEPSEEK_API_KEY=...`, restart Claude Code |
| Wrong workspace | Resolution: tool `cwd` → `CLAUDE_PROJECT_DIR` → `MCP_WORKSPACE_DIR` → server cwd |
| Delegation fails, can't tell why | Run `dsh_status` first — it reports DSH resolution, CLI version, model config, timeout and key presence |
## How to delegate
Every delegation tool takes one `task` argument (see [Tools](#tools) for
the full family). Each call spawns a **brand-new** DSH agent that runs in
your project workspace and returns its final result. DSH has no memory
across calls — that's the design (no session to corrupt) — and it sets the
one rule that matters:
> **Make every delegation self-contained.** Give DSH everything the subtask
> needs: the file paths, the context, the expected result. Never write
> "as above", "like before", or "that file I mentioned earlier" — DSH cannot
> see your conversation.
### Good vs. bad delegation
The same task, written two ways:
```text
❌ "Fix the bug in the store module and verify."
— Which module? What bug? Verify how? DSH has no memory of "the" bug.
✅ "In src/store.js, createOrder() (around line 42) computes the order total
without the tax field. Fix it so the total includes tax. Then run
node src/tests/order.test.js and report the output."
— Self-contained: file, bug, expected behavior, verification command.
```
### Three templates that cover most delegations
**1. Investigation — DSH reads the files, the parent reads the report**
```text
Read src/modules/a.js, b.js and c.js and report: (1) every exported
function signature, (2) all TODO/FIXME comments with line numbers,
(3) where each module is imported from. Don't modify anything.
```
**2. Fix + verify — the parent decides, DSH executes and checks**
```text
In src/utils.js, slugify(" hello ") returns "hello " instead of "hello"
(leading whitespace leaks through). Fix it, then run
node src/test/utils.test.js and report which assertions pass.
```
**3. Batch edit — one delegation instead of N read/edit round-trips**
```text
Across the project, replace every occurrence of config.port with
config.serverPort in all .js files (skip node_modules). List each file
you changed, one line per file.
```
### When to delegate — and when not to
| Delegate (token-heavy, self-contained) | Keep with the parent (needs context) |
|---|---|
| Read + summarize N files | Weighing two architectures |
| Multi-file rename / migration / refactor | Deciding what to build next |
| Run a suite / script and report output | A debugging conversation that evolves |
| Implement a well-specified function | Anything whose goal is still unclear |
### What a delegation looks like in a session
```text
You: The login flow is broken. Have DSH trace login.js → session.js → db.js
under src/auth/ and report where an error could be swallowed, with
line numbers. Don't read the files yourself.
DSH: Found it: src/auth/session.js:37 catches the error and returns null
instead of rethrowing, so login.js treats the failure as "not logged
in". Three files read, nothing modified.
```
## Cost model
Fast and cheap both come from the model on the DSH side: **deepseek-v4-flash**.
Early development ran entirely on flash (parent and DSH alike); the 100-task
benchmark above runs the heterogeneous combo **parent = deepseek-v4-pro[1m],
DSH = deepseek-v4-flash** (arm B uses the same pro parent without the plugin,
so the delta is the plugin). The Claude Code kernel in the benchmark is
DeepSeek's model — `ANTHROPIC_MODEL: deepseek-v4-pro[1m]` — not an Anthropic
one; the claims below only assume a strong parent, regardless of vendor.
- **Faster** — token-heavy subtasks (file reads, exploration loops) run batched
in DSH instead of serially in the parent session; measured −36% average wall
time (76.5s vs 120.2s).
- **Cheaper** — the heavy token spend runs at deepseek-v4-flash rates, and the
parent's token budget is minimized by delegation (pure reasoning).
- **Zero migration** — `delegate_to_dsh` is model-agnostic end to end.
## Verified
| Layer | Coverage | Status |
| --- | --- | --- |
| Unit | 42 vitest (DSH discovery / workspace / runner / change tracking / tools) | ✅ |
| Real E2E (Claude Code) | subdir launch · Chinese · spaces in path · real API | ✅ |
| Long-horizon | plan → investigate → decide → fix → read-back → verify | ✅ 5/5 |
| A/B benchmark | 100 tasks × 2 arms (accuracy + runtime, p<0.001) | ✅ |
| DSH write probe | headless profile can write files | ✅ |
## Design & limitations (V0.x)
- **Synchronous wait-for-result** — no streaming, no background, no polling
(ENGINEERING §0/§23). A long task is one longer MCP call; no timeout by
default.
- **Stateless workers** — each delegation is a fresh DSH process (fresh startup
cost; fine for simple tasks).
- **Concurrent delegations** edit the same workspace at their own risk — prefer
read-only / independent subtasks.
- **Windows-first**, MIT licensed. Docs & engineering rationale:
[`DSH_MCP_ENGINEERING(1).md`](DSH_MCP_ENGINEERING(1).md) (§0–§40).
## Disclaimer
`dsh-mcp` is an independent open-source project. It is **not affiliated with,
endorsed by, or sponsored by OpenAI, Anthropic, or DeepSeek**. DeepSeek Harness
is DeepSeek's open-source project; this adapter merely integrates with its CLI
over the MCP protocol. All product names and trademarks belong to their
respective owners.
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues