dsh-web-mcp
dsh-web-mcp
๐ค AI-assisted project ยท A college student's learning project, with significant code generated by an LLM agent under human direction and review.
MCP bridge for DeepSeek Harness (DSH) web UI โ exposes the cordis RPC API over HTTP /api/<endpoint> as stdio MCP tools. Any MCP client (Codex CLI, Claude Desktop, etc.) can drive DSH sessions and benefit from prompt-prefix cache reuse.
๐ ไธญๆๆๆกฃ๏ผREADME.zh.md
What is in the box
Eight tools, all backed by DSH web's cordis RPC over HTTP /api/<endpoint>:
Tool | Purpose |
| Enumerate every workspace known to the running DSH web UI. |
| Adopt a directory (creating the workspace if missing), then create a session bound to it. Selects the model in one shot. Returns |
| Send a prompt and block until the assistant turn completes. Returns the assistant text plus per-turn token usage including |
| Wait for the in-flight turn to finish without sending a new prompt. Use it after answering a pending approval. |
| List still-pending approval requests ( |
| Answer one pending approval ( |
| Fetch cached projections: |
| Verify a session is still alive and surface its current model. Subsequent |
Approval callback (ๆ้ๅฎกๆนๅ่ฐ)
DSH agents ask for permission before sensitive tool calls (e.g. a sandbox
escalation to danger-full-access). The request surfaces in the session log
as approval/asked and โ on the DSH web event stream โ as an answerable
approval/requested frame. This bridge turns that into a two-track callback:
Sampling callback (default, works with Hermes CN Desktop โฅ 0.18): while
dsh_send_message/dsh_wait_turnare waiting for the turn, the server sends the client asampling/createMessagerequest describing the tool and the DSH-provided reason; the client's answer (allowed-once/rejected) is posted back to DSH viaPOST /api/respondand the turn continues. Hermes supports this out of the box (samplingenabled by default; see its Native MCP docs). Sampling needs to run inside an MCP request, so it is only active on the real MCP transport.Tool fallback (works with any MCP client): set
auto_respond_approvals=falseondsh_send_message(or sampling fails / is unavailable), and the call returns immediately withawaitingApproval: true+pendingApprovalsinstead of waiting. The caller (or a human) then decides:dsh_send_message(...) -> {"awaitingApproval": true, "pendingApprovals": [...]} dsh_respond_approval(session_id, <approvalId>, "allowed-once") -> {"accepted": true} dsh_wait_turn(session_id, ...) -> normal turn resultdsh_list_pending_approvalslists whatever is still pending at any moment (the DSH event stream replays every unanswered approval on connect).
Wire facts (verified against DSH source and a live probe): the answer must
echo the rpcId of the approval/requested frame โ a fresh UUID minted by
the host's pending table, not the audit approvalId โ and the payload
must carry the matching approvalId. The event stream is a WebSocket
(GET /api/events.mux; plain HTTP gets 426), which is why the websockets
package is a hard dependency.
Requirements
Python 3.10+
A running
dsh webinstance onhttp://127.0.0.1:3080(override withDSH_BASE_URL)uv(recommended) orpipwebsockets(installed byuv sync; needed for the approval event stream)
Install
git clone https://github.com/cv588888888888888888888ju/dsh-web-mcp.git
cd dsh-web-mcp
uv syncThen run as a stdio MCP server:
uv run dsh-web-mcpWire up Hermes Agent
Register with hermes mcp add:
hermes mcp add dsh --command uv --args --directory C:\Users\chenty\Documents\feishu-bot\dsh-mcp run dsh-web-mcpโ ๏ธ Known pitfall (tested):
Do not pass
--env DSH_BASE_URL=...โ it gets forwarded todsh-web-mcp's argparse and fails withunrecognized arguments. SetDSH_BASE_URLas a user/system environment variable instead.A new session is required after registering (config loads at session start).
If prompted
Enable all 8 tools? [Y/n/select], answerY.
Wire up Codex CLI
Edit %USERPROFILE%\.codex\config.toml (or ~/.codex/config.toml on other OS):
[mcp_servers.dsh]
command = "uv"
args = ["--directory", "C:\\path\\to\\dsh-web-mcp", "run", "dsh-web-mcp"]
# Optional, defaults to http://127.0.0.1:3080 if omitted
[mcp_servers.dsh.env]
DSH_BASE_URL = "http://127.0.0.1:3080"Restart Codex CLI. The eight dsh_* tools appear alongside the built-in tools.
Probe
probe.py is a developer-side smoke test that exercises all tools end-to-end against the running DSH web UI โ including the approval chain (trigger a real approval, answer it with accepted: true, and wait for the turn to finish):
uv run python probe.pyIt prints a JSON blob per step; expected outcome is
{
"ok": true,
"step": "send_message",
"reply_contains": "TASK_OK",
"cacheReadTokens": 8192
}The approval steps report send_message_awaiting_approval, respond_approval
(accepted: true), and wait_turn_after_approval (reply_contains: "APPROVAL_OK").
Failure modes
DSH web not reachable โ the server starts but every tool returns
{"ok": false, "error": "DSH web not reachable at ..."}. Make suredsh webis running (dsh web --port 3080).DSH schema drift (rc.X โ rc.Y) โ unknown field errors come back as
{ok: false, error: "dsh returned <code>: <msg>"}. The model schema inmodels.pyis intentionallyextra="allow"so additional fields pass through; reported mis-parses should be filed againstmodels.py.Prompt timeout โ
dsh_send_messagetimes out aftertimeout_s(default 120s); rerun with a larger value if your prompt is long. An approval that nobody answers also holds the turn: usedsh_list_pending_approvals/dsh_respond_approval(or wait for the human in the DSH web UI) and thendsh_wait_turn.Sampling unavailable โ outside an MCP request (e.g.
probe.py) or with a client that does not implementsampling/createMessage,dsh_send_messagefalls back to returningawaitingApproval: truewithpendingApprovals; use the tool fallback above.
Configuration
Environment variables read by dsh-web-mcp:
Var | Default | Description |
|
| DSH web base URL. |
|
| Per-request timeout in seconds (generous; a single LLM turn may take ~30s). |
|
| Python |
CLI flags mirror the env vars: --base-url, --timeout, --check.
Why this exists
By default Codex CLI talks to the OpenAI / Azure providers directly. When MCP routing through DSH, the deepseek-v4-flash preset in DSH keeps system + tools + conversation prefix cached, so every subsequent turn in the same session reads 8K+ cached tokens and only pays uncached input for the new prompt + uncached output for the new reply โ measured per tokenUsage.cacheReadTokens in dsh_send_message results.
Background
This project was built as a learning exercise by an undergraduate student exploring agent tooling. The bulk of the code was generated by an LLM coding agent (Codex CLI + DeepSeek) under human direction; every line was reviewed and the behavior verified end-to-end before publication. Bugs are likely given the author's experience level โ please open issues.
Status
Pre-release. API surface follows DSH 0.1.0-rc.6 schema (rpc-map.d.ts); regenerate from source if you bump DSH.