reactive-resume
reactive-resume
A one-of-a-kind resume builder that keeps your privacy in mind. Completely secure, customizable, portable, open-source and free forever. Try it out today!
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:Leawind/dsh-ops-mcp
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
Operate DeepSeek Harness (dsh) over MCP: run an MCP server inside the dsh process and expose session execution, task queues, and workspace management to any MCP client.
📖 English (this page) · 中文
dsh ships a complete agent runtime — model routing, tool sandbox, presets, persistent sessions — but it is a Cordis application that external programs cannot call directly. dsh-ops-mcp turns it inside out: the plugin starts an MCP server (StreamableHTTP) inside the dsh process and bridges the live harness through ctx.agents / ctx.agentPresets / ctx.tools.
Your MCP client is the commander; dsh is the executor. Works with Claude Code, Codex CLI, Cursor, another dsh instance (via the official dsh-mcp-client), or any automation script.
MCP client (Claude Code / Codex / another dsh / …)
│ agent_run / task_inbox / task_result / model_list / select_model (HTTP + Bearer)
▼
dsh-ops-mcp (MCP server, 127.0.0.1:8090)
│ ctx.agents.create → mount preset
▼
dsh agent — full toolset: bash, fs, todo, web…
| Tool | Purpose |
|---|---|
echo |
connectivity check |
dsh_list_tools |
list the host-global tool registry (name + description; model tools are preset-scoped, usually empty) |
model_list |
list currently routable providers, model ids, reasoning efforts, and the default selection (look here before picking a model) |
agent_run |
run a task synchronously, structured result; sessionId continues a session; provider/model/reasoningEffort pick this run's model; detail controls result size |
task_inbox |
push a structured task (task + context + cwd + model) into the async queue, returns taskId |
task_result |
fetch a queued task's result; detail=status is a lightweight poll that never re-injects the payload |
select_model |
switch the model of an existing session (official sessionController.selectModel path) |
attach_session |
attach a session to the workspace of its cwd |
rename_session |
rename an existing session |
Priority: per-call arguments > plugin config (provider+model) > host default selection (ctx.agentDefaultModel.currentSelection(), the same source the Web UI uses when creating a session). Supplying only one half completes it from the lower-priority source; if the pair still cannot be resolved the call fails loudly instead of running with an empty model (an empty model leaves the persona's {{model}} variable unset and fails the whole turn at assembly time).
reasoningEffort follows the model's source: call argument > plugin config > the host default selection's own effort. But when the caller or the plugin config pins provider+model explicitly, the host's effort (chosen for some other model) is not inherited — the host rejects unsupported explicit efforts instead of clamping or aliasing, so inheriting one would break a deployment that used to work.
Three ways to change models:
| Goal | How |
|---|---|
| Run this one task on another model | pass provider+model (plus optional reasoningEffort) to agent_run / task_inbox |
| Switch models mid-conversation, keeping history | select_model (official sessionController.selectModel: validated, writes a durable notice, takes effect on the next step; history is preserved) |
| Pin one model for the whole deployment | set provider+model in the plugin config and allowModelOverride: false to refuse caller overrides |
Two source semantics worth knowing:
cwd + model triple. Task 1 on model A and task 2 on model B in the same directory are two separate sessions (no shared context), so which model a session uses is always predictable; to switch models inside one session, use select_model.model: {provider, model, reasoningEffort?} so the caller never has to guess who answered.model_list's source names the catalog's origin: sessionController = the official view (same data source as the Web UI's model selector; includes default, routableProviders, per-provider load failures, and reasoning efforts); llm = fallback (llm.listProviders() + per-provider listModels(), for deployments without sessionController, e.g. headless, and without reasoning metadata). It also reports the plugin's own model config and allowModelOverride, so a caller can see at a glance whether it may choose.
Result detail levels (token budget) — the whole point of this plugin is saving the caller's (operator's) context: execution details stay inside dsh, and read-back is projected by detail:
summary (default, a few hundred tokens): the changes/verification/leftovers three-line summary + the answer tail (the summary JSON sits at the end) + tool-name list + errornormal (~2k tokens): the above + truncated tool-call arguments and resultsfull (up to tens of thousands of tokens, for debugging): the full texttask_result also has a status level: polling returns only {taskId, status, error?} — fetch the summary once after completion instead of re-injecting the payload on every pollWhen continuing the same sessionId, the executor already remembers prior turns — send only the delta in context.
Every result is structured: sessionId / model / assistantText / toolCalls / toolResults / changes / verification / leftovers — ready to be persisted by the caller.
Sessions are reused per cwd + model (LRU, default 8) to avoid reloading project context on every call.
The plugin must be installed into a dsh profile directory (the loader resolves plugin names from there; --patch alone from a repo checkout will not find the local package — see finding 1 in the E2E report):
git clone https://github.com/Leawind/dsh-ops-mcp.git
cd dsh-ops-mcp
npm install && npm run build
npm pack # produces dsh-ops-mcp-<ver>.tgz
# install into the profile (Windows note: use the tarball; pnpm mangles file:D:/... specifiers)
pnpm -C ~/.dsh/profiles/<profile> add -w <path-to-tarball>
export DEEPSEEK_API_KEY=... # model credentials (or use what ~/.dsh already stores)
dsh --profile <profile> --patch ./cordis.yml --no-open --port 3081
The MCP server listens on 127.0.0.1:8090 (StreamableHTTP). Point any MCP client at http://127.0.0.1:8090/mcp.
Generic (any streamable-http capable MCP client):
{
"mcpServers": {
"dsh": {
"url": "http://127.0.0.1:8090/mcp",
"headers": { "Authorization": "Bearer <your-secret-token>" }
}
}
}
Let another dsh operate this one (add to the peer profile's cordis.patch.yml, using the official dsh-mcp-client):
- id: mcp-dsh-ops
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: dsh
transport: streamable-http
url: http://127.0.0.1:8090/mcp
headers:
Authorization: 'Bearer <your-secret-token>'
- insert:
- id: dsh-ops-mcp
name: 'dsh-ops-mcp'
config:
http: true
port: 8090
host: 127.0.0.1 # localhost only by default; add auth before exposing
# authToken: 'your-secret-token' # Bearer token auth (constant-time compare)
# workspaceRoots: ['/workspace'] # cwd whitelist (separator/case-safe cross-platform)
# allowedHosts: ['my-box.lan'] # extra allowed Host header values (DNS-rebinding guard)
# preset: 'standard' # agent preset to mount
# model: '' # empty = follow dsh user/default settings
# provider: '' # pair with model; empty = follow host default
# reasoningEffort: '' # default reasoning effort (empty = adapter default)
# allowModelOverride: true # false = pin the model, refuse caller overrides
| Field | Default | Meaning |
|---|---|---|
host / port |
127.0.0.1 / 8090 |
MCP server bind address; a listen failure (port in use, …) fails plugin startup loudly |
authToken |
— | Bearer token; enforced on every request when set (constant-time compare) |
workspaceRoots |
— | cwd whitelist; agents may only work inside the listed directories (subdirs included) |
allowedHosts |
— | extra allowed Host-header values; the bind host and loopback aliases are always allowed, everything else gets 403 |
provider / model |
follow host user settings (agentDefaultModel) |
spawned-agent model selection; configure as a pair — a partial setting is completed from the host default |
reasoningEffort |
adapter default | default reasoning effort (adapter-defined id, see reasoningEfforts in model_list) |
allowModelOverride |
true |
whether callers (agent_run/task_inbox/select_model) may override the model; false pins it and refuses overrides explicitly |
preset |
standard |
agent preset to mount |
defaultDetail |
summary |
default detail level for agent_run/task_result (overridable per call via detail) |
reattachOrphans |
false |
bulk-attach ungrouped sessions to workspaces at startup (writes user data; the attach_session tool remains available anytime) |
maxQueue / taskTtlMs / maxAgents |
100 / 10 min / 8 |
queue capacity, result TTL, session-pool LRU limit |
Every result is structured: sessionId / model / changes / verification / leftovers / error / toolCallCount … (projected by detail level); error carries non-normal turn endings (model failure / cancel / blocked), so a silent empty "success" can no longer happen.
The plugin has *zero runtime dependencies on `@deepseek-ai/**: every dsh capability is reached through injected host services (ctx.agents/ctx.tools/ctx.agentPresets…), types only augment the compiler viaimport type(erased at build), and@deepseek-ai/*packages are devDependencies. A plugin therefore can never drag a mismatched copy of a host package into the process — the root cause behind the upstream-erascopeOfsymbol mismatch that silently left agents tool-less. Event reads go through the publicsession.snapshotEvents()API and message construction uses a local, field-for-field equivalent of the host'screateUserMessage`.
⚠️ This plugin exposes local execution capability (equivalent to RCE). It binds 127.0.0.1 only by default. When enabling:
authToken — against other local processes and DNS-rebinding attacks (constant-time compare);workspaceRoots — constrain where agents may work;0.0.0.0 or expose to LAN/WAN without a reverse proxy + TLS + auth.Built-in guards: a Host-header allowlist (bind host + loopback aliases by default, guarding against
DNS rebinding; a missing Host header gets 400) and a /mcp-only HTTP surface (everything else 404).
The initial source of this project was copied from chushixixin/dsh-harness-mcp-server (MIT, thanks @chushixixin) and then evolved as an independent project — no git fork relationship, no upstream contributions planned. Key changes:
dsh-ops-mcp.The 0.2.0 compatibility issues were fixed in 0.3.0; 0.3.1 completed live-host E2E verification (all green — see docs/e2e-0.1.5-rc.2.zh.md) and fixed what it uncovered: the {{model}} prompt variable (model selection now completed via agentDefaultModel), turn-failure surfacing, pool-session flush, startup reattach off by default, and corrected install docs.
0.5.0 completed the model-selection surface: model_list (official catalog / llm fallback), per-call overrides on agent_run + task_inbox, select_model (in-session switch), reasoningEffort, the allowModelOverride gate, model reported in every result, and a session pool keyed by cwd + model.
What remains:
agent_run / task_inbox — a hung agent holds its cwd's serial lock and later same-directory tasks queue behind it; callers should bring their own MCP-level timeout. The queue cannot be listed or cancelled either.session_list (list sessions, read history, read a session's current model) — only attach_session / rename_session / select_model.preset remains deployment-level (one persona per MCP server instance); it cannot be chosen per call.ask may pop a dialog or fail closed; the read-only E2E operation was unaffected).dsh_list_tools only lists the host-global registry; listing an agent's actually-visible tools needs a host-side API (the ScopeKey is a private symbol, unreachable under the zero-copy principle).select_model requires the web profile's sessionController; where that service is absent (e.g. headless) only the catalog fallback and per-call overrides work, and the tool says so explicitly.@deepseek-ai/* devDependencies need syncing (compile-time only; the zero-runtime-dependency design is unaffected).npm install
npm run build # standalone build (plain tsc) -> lib/
npm run smoke # fake-ctx smoke on ports 8099/8098/8096/8095 (57 checks, real MCP protocol round-trips)
# + a port-conflict case (apply must fail loudly)
Live-host E2E (needs a local dsh with model credentials; costs a few tokens): boot a dedicated
profile as described in docs/e2e-0.1.5-rc.2.zh.md, then run
E2E_WITH_AGENT=1 node e2e.mjs.
MIT — see LICENSE (upstream copyright notice retained).
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: mcp。