dsh-subagent-mcp
Give Codex a DeepSeek crew: one MCP call hands a task to a fresh DSH agent that already has your skills, AGENTS.md and sign-in. V4.1-Flash at max reasoning, ~9k tokens per delegation, in the project directory you name.
- Stars
- 0
- Language
- JavaScript
- Created
- Oct 17, 2026
- Updated
- Oct 19, 2026
Introduction
dsh-subagent-mcp
Give Codex a DeepSeek crew. One tool call hands a task to a fresh DeepSeek Harness agent that
already has your skills, your AGENTS.md and your sign-in — and it works in the project directory
you name, then hands back the answer.
| What you get | Why it matters |
|---|---|
| ≈9k tokens per delegation | A lite run costs about one file read, not a second conversation. |
V4.1-Flash at reasoning max | The strong preset is the default. Nothing to tune. |
Your skills and AGENTS.md | It behaves like the DSH you configured, because it uses your DSH_HOME. |
| Long tasks can't be killed by a timeout | It waits wait_sec, then returns a task id you collect later. |
| Zero runtime dependencies | ~600 lines with a hand-rolled JSON-RPC loop. Nothing to audit but the bridge. |
| One process, one turn | No daemon, no state to leak between tasks, no edits to your DSH profile. |
Codex / Claude Code / any MCP client
│ MCP over stdio
▼
dsh-subagent-mcp ← this package, zero deps
│ spawn: dsh --profile <p> --patch <overlay> --json
▼
DeepSeek Harness (headless) ← inherits DSH_HOME: AGENTS.md, skills, sign-in
▼
your project directory · one turn · final answer
Compared with other bridges in this space
Bridges between agent hosts and DeepSeek Harness come in three shapes: a resident daemon that keeps agents alive and calls back into the host, an SDK bridge that runs workers in git worktrees, and a relay that exposes an already-running DSH instance. This package is the fourth: a stateless one-shot bridge. Where it wins, and where it deliberately loses:
| Dimension | This package | Typical alternative | Why it matters |
|---|---|---|---|
| Runtime dependencies | Zero — only dsh itself | ws + @modelcontextprotocol/sdk + zod, or a chain of SDK packages | A ~600-line single file is auditable in one sitting and cannot break because a transitive dep changed |
| Your DSH profile | Never touched — config arrives as --patch overlays | Written into the profile, or requires a plugin on the DSH side | Upgrading DSH can't collide with a bridge that wrote itself into your config |
| Per-call cost | Two explicit tiers: ≈9k or ≈300k tokens | One implicit size you discover on the bill | You choose the cost before the run |
| Lifetime | One process per task, then exit | Resident daemon / worker pool | Nothing runs in the background; a crash can't poison the next task |
| Orphan processes | Process-tree kill on cancel and shutdown | Sometimes leaves the MCP children dsh spawned | No slow memory drain after a few cancelled runs |
| Config inheritance | Real DSH_HOME → your skills, AGENTS.md, sign-in | Some runtimes get an isolated temporary DSH_HOME (an empty shell) | The child actually behaves like the DSH you configured |
| Install | npx -y github:…, no registry account | npm publish only | One config block, works today |
| Cold start | ≈1.7 s per delegation ⚠️ | Warm daemon is faster | The honest price of statelessness |
| Native callback / session attach | Not offered — needs host hooks ⚠️ | Daemon bridges call back natively and can attach to running sessions | If you need either, a daemon-shaped bridge is the right tool |
Short version: pick this one when you want a cheap, auditable, no-moving-parts way to hand work to DSH; pick a daemon-shaped bridge when you want long-lived agents that wake the host by themselves.
- Default preset: DeepSeek-V4.1-Flash (id
deepseek-flash) at reasoning effortmax. - Nothing to configure: the first call creates its own
dsh-subagentprofile from the shipped headless template.
Install
Requires Node.js ≥ 22.19. The bundled dependency provides the dsh CLI.
A · straight from GitHub — no clone, no build, no npm account:
# ~/.codex/config.toml
[mcp_servers.dsh_subagent]
command = 'npx'
args = ['-y', 'github:mbj733/dsh-subagent-mcp']
startup_timeout_sec = 60
B · pinned tarball
npm i -g https://github.com/mbj733/dsh-subagent-mcp/releases/download/v0.5.0/mbj733-dsh-subagent-mcp-0.5.0.tgz
C · clone (when you want to read or patch the code)
git clone https://github.com/mbj733/dsh-subagent-mcp
cd dsh-subagent-mcp && npm install
[mcp_servers.dsh_subagent]
command = 'node'
args = ['/absolute/path/to/dsh-subagent-mcp/index.mjs']
startup_timeout_sec = 30
Restart the host afterwards. Then just ask:
Delegate to DSH: summarise
src/and list the files that look unused. Work in/path/to/project.
Tools
| Tool | Overlay | What it does |
|---|---|---|
subagent_dsh | lite | Delegate a task: skills, shell, file editing, web search — no MCP tools. ≈9k input tokens. |
subagent_dsh_full | full | Same, plus every MCP server declared in overlay.yml. ≈300k input tokens with a dozen servers. |
subagent_dsh_status | — | Collect a task. Accepts a task id (t-…) or a dsh session id. |
subagent_dsh_continue | — | Follow-up in the same dsh session; cwd/overlay default to the remembered values. |
subagent_dsh_cancel | — | Kill a running or queued task and its whole process tree. |
| Argument | Default | Meaning |
|---|---|---|
prompt | — | Required. The child cannot see the host conversation — include everything it needs. |
cwd | — | Required. Absolute project directory. Not a sandbox (see Security). |
wait_sec | 120 | Wait before returning a task id. 0 returns immediately. |
timeout_sec | 1800 | Hard limit; on expiry the tree is killed and partial output returned. |
read_only | false | Ask the child to make no filesystem changes (instruction + detection, not a kernel sandbox). |
format | text | json returns {ok, state, answer, usage, meta}. |
Long tasks are safe by design: the call waits wait_sec, then returns a task id, so a host-side
tool timeout can never kill the work.
Model and cost
The child runs DeepSeek-V4.1-Flash — model id deepseek-flash — at reasoning effort max.
That is what both overlays ship; verify it yourself:
dsh --profile dsh-subagent --patch overlay.lite.yml --dump-config | grep -A4 agent-default-model
The account line exposes two ids (checked against GET https://api.deepseek.com/models):
| id | displayed as |
|---|---|
deepseek-flash | DeepSeek-V4.1-Flash · default |
deepseek-v4-pro | DeepSeek-V4-Pro |
Switch per machine without editing files: export DSH_SUBAGENT_MODEL=deepseek-v4-pro.
Pin the id, not the label. Provider-specific ids from other lines — for example opencode's
deepseek-v4.1-flash— are not valid here; they need that provider's key andbase_urlon thedsh-subagentprofile.
| Overlay | Mounts | Typical input tokens | Use it for |
|---|---|---|---|
lite | credentials + skills | ~9,000 | writing, refactoring, analysis — anything done with shell and files |
full | + 11 MCP servers | ~300,000 | tasks that genuinely need PDF/office/browser/GitHub/map tools |
A short lite reply, measured end to end: startup 1.67s · work 1.19s · tokens in 9073 / out 2.
Verify an install without a host
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| npx -y github:mbj733/dsh-subagent-mcp
Expect serverInfo.name = dsh-subagent-mcp and exactly five tools.
Installing this for someone else, or as an agent? Read llms.txt for the
machine-readable guide and AGENTS.md for registration rules and repository
conventions. Generic client config:
{"mcpServers":{"dsh_subagent":{"command":"npx","args":["-y","github:mbj733/dsh-subagent-mcp"]}}}
Optional: report finished tasks back into the host
MCP has no channel for a late result, so long tasks return a task id. On hosts with hooks,
notify-hook.mjs injects any finished task into the next turn.
# Codex. `type = "command"` is mandatory — a missing type breaks the whole config file.
[[hooks.PostToolUse]]
[[hooks.PostToolUse.hooks]]
type = "command"
command = "'node' '/absolute/path/to/dsh-subagent-mcp/notify-hook.mjs' PostToolUse"
timeout = 5
It reads state/tasks.json, reports each finished task exactly once, and stays silent otherwise.
How it behaves
- Stateless per call — a new process and exactly one turn; nothing to supervise, nothing to leak between tasks.
- Overlay, not profile surgery — configuration arrives as
dsh --patchlayers, so your existing profiles are never rewritten. - Bounded concurrency —
DSH_SUBAGENT_MAX_CONCURRENCY(default 2) with a FIFO queue; extra calls returnqueuedplus their position. - Usage reporting — tokens, tool calls and names,
startup_ms/work_ms,exit_code,error_code(TIMEOUT,CANCELLED,SPAWN_FAILED,EMPTY_ANSWER,CHILD_EXIT,RATE_LIMIT). - Oversized answers — above
DSH_SUBAGENT_INLINE_LIMIT(default 24000 chars) the full text spills tostate/results/<task>.md; the reply states the percentage shown. - No orphans — cancellation and shutdown kill the whole process tree.
Security
cwdis not a sandbox. It selects the working directory only; the child inherits the host user's filesystem and network rights.read_onlyis advisory. Enforced by instruction and reported by detection, not by the kernel.- The child inherits
DSH_HOME, so it reads yourAGENTS.md, skills and stored credentials. - Nothing is sent anywhere beyond the model provider you configured.
Differences and limits
Deliberately stateless: a process per task, one turn, then exit. That keeps the failure surface small and the billing predictable, and it means there is
- no daemon and no queue beyond the in-process FIFO;
- no native "wake the parent when done" callback — that needs host hooks, above;
- no way to attach to a DSH session already running elsewhere;
- no streaming and no mid-run questions — use
subagent_dsh_continuefor follow-ups; - no visibility from the host into the child's conversation; only the final answer crosses back.
Several host windows each run their own server instance and share state/tasks.json, so hook
notifications can duplicate.
Built on
DeepSeek Harness. This is the reverse of
@deepseek-ai/dsh-subagent-codex,
which lets DSH drive a Codex thread.
Author
Built and maintained by @mbj733. MIT licensed.
Troubleshooting
failed to parse overlay … YAMLException: end of the stream or a document separator is expected
The dsh-subagent profile patch is broken. That file starts with a [] placeholder, so any
block appended after it makes the whole file invalid YAML — typically a persona/plugin block
copied in from another profile. The bridge detects this on every run, keeps a backup and resets the file automatically, so a
task still succeeds (the footer then says profile-patch auto-reset). To do it by hand:
node repair-profile.mjs # backs the file up, then writes the clean template
unknown prompt variable "{{cwd}}"
The profile patch sets personaSuffix to text containing {{cwd}}, which the unattended profiles
do not register (only provider and model exist there). Either drop the personaSuffix line or
keep the persona in personaPrefix, which takes no variables.
unknown task id after restarting the host
Task ids live in the server process. After a restart, continue by dsh session id instead:
subagent_dsh_continue(session_id="session-…", prompt="…").