Skip to main content
Glama

🐳 dsh-any-agent

English | 中文

npm version node license dependencies

Give any MCP client a coding buddy that sticks around — powered by DeepSeek Harness.

An MCP server that drives DSH as a long-lived agent runtime instead of wrapping a CLI. Open a conversation, hand it a goal, and it writes code, runs scripts and spawns its own subagents — while you watch, cut in, and collect the result whenever you like. 🛠️

Works with any MCP client. Nothing here is Codex- or Claude-specific: the server speaks plain MCP over stdio and uses only the four standard methods (initialize, ping, tools/list, tools/call). Verified against a bare-bones client that negotiates an older protocol version and never sends notifications/initialized.

🧪 528 checks green · 🔌 MCP over stdio · 📦 Zero dependencies · 📜 MIT

📌 Read the Usage Notes first — the practical gotchas that will actually bite you: the ACP model-config trap, external MCP tools bypassing DSH's sandbox, the workspace/GUI registry cache, cost control, and a troubleshooting table.

🆕 Setting this up on a fresh machine? USAGE-MIGRATION.md is a step-by-step walkthrough for a new environment: prerequisites, how to find your DSH_BIN, ready-to-paste config blocks for Claude Code / Codex / Cursor / Windsurf / Cline / Zed, and a first-turn checklist.

📑 Contents

  any MCP client                       ← Claude Code, Codex, Cursor, Zed, Cline, your own…
  (Claude/Codex/Cursor/…)              │
         │ MCP over stdio              │ newline-delimited JSON-RPC (ACP)
         ▼                             ▼
   ┌─────────────┐            ┌──────────────────────────┐
   │dsh-any-agent│───────────▶│ dsh --profile dsh-mcp    │  one long-lived process
   └─────────────┘            │                          │  per conversation
                              └────────────┬─────────────┘
                                           │
                              ┌────────────┴─────────────┐
                              │ DSH's own tools          │ files, shell, search,
                              │                          │ skills, subagents, workflows
                              ├──────────────────────────┤
                              │ dsh-mcp-client           │ → your other MCP servers
                              └──────────────────────────┘

Related MCP server: dsh-mcp

🎯 The problem this solves

You already have a coding agent in your editor. What you do not have is one that can:

  • hand a multi-hour job to another agent and walk away — then collect the result when it's done;

  • keep the job alive across restarts of your editor, your session, your machine's idle reap;

  • cut in mid-flight when it's going the wrong way, without destroying the work;

  • run several of them at once and switch between them.

DSH (DeepSeek Harness) can do all of that — it has file/shell tools, skills, subagents and workflows — but it is a GUI-first application. Its own sdk profile exposes only 3 requests and can't resume, cancel, or answer approvals.

This server is the missing adapter: it makes DSH into a well-behaved background agent that any MCP client can hire, supervise and collect from.

Think of it as &job — like & in a shell, but for an agent.

✨ Why it's different

🔄 A dead process never kills a conversation

The identity lives on disk. A crash, an idle reap, even restarting this whole server costs you nothing — the next call transparently resumes it, memory intact (verified across processes).

🗣️ You are never stuck waiting

Steer a conversation mid-flight — interject to stop and redirect (converges in well under a second), or queue a remark for after the current turn. Interrupting never damages the conversation.

📡 Completion notification, not polling

Long tasks return instantly with a run_id; when the turn ends, a sentinel file is written atomically so a background task in your host wakes you. No polling, no occupied turn.

🪟 Many conversations at once

dsh_list(only_running) shows what's live; dsh_read gives cursor-based incremental output. Cycle between them like windows.

🧠 Reasoning hidden by default

You get "thought for N chars / M seconds" statistics instead of context-burning prose — and it is never logged, never written to disk.

🤫 Silent by default

Not one byte on stderr, so nothing pollutes your host's logs. stdout carries the protocol and nothing else.

🧩 Capabilities compose

DSH can mount its own MCP servers (IDA Pro, browsers, your internal tooling), so this is a bridge to a whole toolbox.

🛡️ Read-only that really is read-only

read-only sandbox plus auto-denied approvals, for working on untrusted samples.

⚡ Why not just wrap the DSH CLI?

DSH ships two programmable entry points. The sdk profile looks simpler, but its JSON-RPC surface has only 3 requests and 4 notifications — and it lacks three things that make an agent unusable in practice. The acp profile (Agent Client Protocol) has all of them. Everything below was verified against DSH 0.1.7-rc.2.

Capability

sdk profile

acp profile (this project)

Resume a conversation

❌ no resume method; reusing an id fails with already exists

✅ session/resume — verified across processes

Interrupt a running turn

❌ only by killing the process

✅ session/cancel — the same path as the user's own stop button

Answer approval prompts

❌ no channel; operations silently fail closed

✅ session/request_permission round-trip

List / close sessions

❌ none

✅ session/list, session/close

Gray vs. black streaming

one message, split by a type field

✅ two separate channels: agent_thought_chunk / agent_message_chunk

Per-session MCP servers

❌ process-wide

✅ mcpServers accepted by session/new and session/resume

Core idea: the process is a cache, not the identity. A conversation's identity is DSH's sessionId, persisted under ~/.dsh/sessions. If the process is reaped, crashes, or the whole MCP server restarts, the next call transparently session/resumes it — history intact.

📦 Requirements

  • Node.js ≥ 20

  • DSH installed (@deepseek-ai/dsh), with working credentials under ~/.dsh

  • A DSH profile named dsh-mcp derived from the shipped acp template, containing your own provider/model config (this repo deliberately ships no provider config)

🚀 Setup

1. Create the DSH profile

dsh dsh-mcp --from-default-profile acp

Then edit ~/.dsh/profiles/dsh-mcp/cordis.patch.yml and declare your provider and model. Start from profile-example/cordis.patch.yml.

⚠️ Two traps here (both learned the hard way):

  1. ACP's model does NOT come from agent-default-model. It comes from the dsh-acp plugin's own config.provider / config.model, which the acp bundle hard-codes to deepseek-official. If you don't override the acp row, your sessions silently run on the wrong provider and fail with no API key for provider route "deepseek-official".

  2. ACP's reasoning_effort defaults to empty (= "Provider default"), not to the maximum. This server explicitly sets it to max on every session/new and after every session/resume — because a fresh process does not remember the previous choice.

2. Register the server

From npm (recommended)

npm install -g dsh-any-agent

The dsh-any-agent executable is then on your PATH and every client below can just call it by name.

From a local checkout

Clone or unzip anywhere, then point command at node and args at <checkout>/server/bin/dsh-any-agent.mjs (shown below).


Claude Code

# global install
claude mcp add dsh-any-agent -- dsh-any-agent

# local checkout
claude mcp add dsh-any-agent -- node /absolute/path/to/server/bin/dsh-any-agent.mjs

Codex (~/.codex/config.toml)

[mcp_servers.dsh-any-agent]
command = "dsh-any-agent"          # or "node" + path to bin/dsh-any-agent.mjs
args = []
startup_timeout_sec = 20.0
tool_timeout_sec = 120.0

Generic MCP client — anything that speaks MCP over stdio:

{
  "mcpServers": {
    "dsh-any-agent": {
      "command": "dsh-any-agent",
      "args": []
    }
  }
}

For a local checkout, use "command": "node" and "args": ["/absolute/path/to/server/bin/dsh-any-agent.mjs"].

Cursor, Windsurf, Zed, Cline, Continue and most other clients use this same mcpServers shape — if it can launch a stdio MCP server, it works.

3. Try it

node test/smoke.mjs        # no LLM calls, verifies the whole plumbing

🧰 Tools

Tool

What it does

dsh_start

Create a conversation: workspace, permission tier, approval policy, reasoning effort (default max), model

dsh_send

Hand it a task. Defaults to wait=false: returns a run_id + sentinel_file immediately, the turn runs in the background and the sentinel file notifies you when it's done (recommended). wait=true blocks until the turn ends; hitting timeout_ms (default 0 = wait forever) only downgrades to background — the turn is never cancelled and the result is never lost

dsh_list

All conversations with live state: running / idle / detached, elapsed time, current tool, output so far. only_running=true for active ones

dsh_read

Cursor-based incremental read of what a running conversation is producing right now

dsh_get

Conversation details; with run_id, the final result of that dispatch (the "collect the result later" entry point)

dsh_interject

Interject while it's busy: interject = stop and redirect, queue = wait for the current turn, then speak. This round writes a sentinel too, and the reply carries its sentinel_file

dsh_takeover

Preempt the write lock. The only way out when another DSH process holds it (DSH has no steal API — preempting means killing the holder). It classifies the holder first: our own orphaned child → taken automatically; another live instance → needs force; GUI/unknown → never killed, reported instead

dsh_interrupt

Stop the current turn (the conversation stays healthy)

dsh_release

Hand the conversation back (frees the write lock so you can open it in your own DSH GUI). Still resumable

dsh_approval_decide

Adjudicate a pending approval (only when on_approval=ask)

dsh_status

Lightweight status check

📋 Workflow: dispatch, then collect later

MCP tool calls block, so dsh_send does not block by default (wait=false): you get a receipt immediately, the turn runs in the background, and the sentinel file wakes you when it finishes. The whole flow:

1. dsh_send(conversation_id, prompt, wait=false)
     → { run_id: "run-abc123", background: true }        # returns without waiting for the turn

2. …keep working on something else (verified: another conversation ran a full turn meanwhile)…

3. dsh_status(conversation_id)
     → state=running | elapsed=3.5s | current_tool=pwsh | out=37 chars

4. dsh_read(conversation_id, cursor)                      # peek mid-flight
     → [me] … | [turn] turn 1 started | [tool] pwsh [in_progress]

5. dsh_get(conversation_id, run_id)                       # collect the result
     → status=done, elapsed=20.3s, full answer

run_id is the receipt. Run records are memory-only, last 20 kept; under many concurrent runs old ones get evicted and dsh_get will fail — which is exactly why the completion sentinel below carries the result.

🔔 Workflow: get notified on completion (no polling)

Besides run_id, wait=false returns a sentinel_file path plus sentinel_file_posix (the Bash/MSYS form — use it directly; hand-converting Windows paths is the easiest way to make your waiter hang until timeout). When the turn ends — whether it succeeds, fails, or is cancelled — the service atomically writes a small file there containing the final status and result. So you neither poll nor block: wait for that file with a background task in your own host (Claude/Codex); the moment it appears, you're woken up.

# sent="$sentinel_file_posix"   ← copy it straight from the tool result
sent='/d/AI_MCP/DSH_MCP/.state/runs/<conversation_id>/<run_id>.json'
dl=$(( $(date +%s) + 2100 ))          # 35min safety net so a dead service can't hang you forever
until [ -f "$sent" ]; do
  [ "$(date +%s)" -ge "$dl" ] && { echo "TIMEOUT"; exit 1; }
  sleep 2
done
echo "DONE"                            # task exits → host wakes you → read the file to collect

Once the file exists: read it for status and result (preferred — immune to the in-memory window and survives restarts), or use dsh_get(conversation_id, run_id).

Design notes (especially under concurrent multi-session use):

  • No cross-talk: the path is .state/runs/<conversation_id>/<run_id>.json, namespaced by conversation. run_id sequence numbers are counted per-conversation, so two conversations can mint the same run_id in the same millisecond — the per-conversation directory keeps them apart. Arm one background task per run; each completes and wakes you independently.

  • Latch semantics: the file is written once and kept. Even if the run finishes before you arm the waiter, [ -f ] is immediately true — you never miss it.

  • Atomic write: tmp + rename; if the file exists its contents are complete. Each run uses its own tmp, so concurrent writes never clobber.

  • No server push: the service only answers requests over MCP (it sends no notifications). "Completion notification" is entirely the sentinel file plus your background waiter.

  • ★ No reasoning in the sentinel: the payload carries the answer and thinking statistics only — result.thinking is stripped and replaced by thinking_omitted: true, because the project's rule is that reasoning never reaches disk. dsh_get(run_id) still returns it from memory while the record lives. DSH_MCP_SENTINEL_INCLUDE_REASONING=1 breaks that guarantee deliberately.

Cleanup: sentinels contain full answers, so the service prunes sentinels older than 7 days at startup (tune with DSH_MCP_SENTINEL_TTL_MS; 0 disables). Your waiter should still rm the file after consuming it.

🧵 Workflow: many conversations at once

1. dsh_list(only_running=true)
     - … | id=448b239b… | 🔵 running (9s, tool=pwsh, 40 chars this turn) | cwd=…
     - … | id=33427096… | 🔵 running (8s, 34 chars this turn)        | cwd=…
2. dsh_read(id,  cursor=0)      # read the first, note the cursor
3. dsh_read(id2, cursor=0)      # switch to the second
4. dsh_read(id,  cursor=<prev>) # switch back, deltas only

🤫 Reasoning: hidden by default

DSH separates reasoning from answer text at the model layer, and ACP exposes them as two independent streams. This server hides reasoning by default and returns only statistics (characters, duration, chunk count) — so the caller knows thinking happened without paying for it in context.

  • reasoning=hide (default) — content dropped, stats returned

  • reasoning=marker / summary / full — progressively more content (mind your context budget)

  • dsh_read(include_reasoning=true) — live reasoning of the currently running turn only; it is never buffered and is discarded when the turn ends

🔐 Permissions

Tier

Approval default

Use for

danger-full-access (default)

auto-allow

Zero friction

workspace-write

auto-allow

Out-of-workspace operations surface as pending approvals

read-only

auto-deny

Analyzing untrusted samples (malware, unknown dumps)

on_approval can be auto-allow, auto-deny, or ask (which exposes a pending approval for dsh_approval_decide to adjudicate).

🪟 Workspace visibility in the DSH GUI

Two different things — don't confuse them:

Depends on

Status

Workspace effectiveness

the conversation's cwd

✅ Always correct. Process working directory, file placement and sandbox write root all follow it

GUI grouping

sessionIds in ~/.dsh/storages/workspace.json

⚠️ This server registers it, but the running DSH server caches the registry in memory — external file edits only take effect after a DSH restart

Verified experimentally (test/workspace-effect.mjs): two conversations in two desktop folders, given no path at all, each wrote its file into its own workspace, self-reported the correct working directory, and leaked nothing into the server's cwd / home / temp / the other workspace (24/24 checks).

Maintenance CLI:

node bin/dsh-any-agent-workspaces.mjs --list                  # inspect the registry
node bin/dsh-any-agent-workspaces.mjs --backfill              # register every session found on disk
node bin/dsh-any-agent-workspaces.mjs --prune-temp            # drop temp-directory entries
node bin/dsh-any-agent-workspaces.mjs --prune-empty           # drop empty workspaces whose path is gone
node bin/dsh-any-agent-workspaces.mjs --purge-test-sessions   # delete test sessions (strict rules)

⚠️ Before restarting DSH, don't touch workspaces in the GUI — the server will write its in-memory state back over the file.

🕶️ Logging and privacy

Silent by default — not one byte is written.

  1. MCP's stdout is the protocol channel; anything mixed in corrupts the stream.

  2. This server is launched by Claude/Codex, so its stderr lands in their logs.

  3. DSH's own stderr may carry model output including reasoning, so it is never forwarded by default.

Level

Behavior

silent (default)

nothing at all

info

this server's own lifecycle events only — never any DSH output

debug

more of our own events; still no DSH output

DSH_MCP_LOG_STDERR=1

additionally forward DSH's raw stderr (may contain reasoning)

Reasoning never reaches disk — with exactly one deliberate exception:

  • Not in logs (silent by default), and the state file holds metadata only (id, cwd, permissions, counters) — a smoke-test assertion guards this.

  • The async completion sentinel does persist the answer (it must, so a caller can collect offline) but strips result.thinking and marks thinking_omitted: true. A test asserts that even when reasoning=full is requested, no reasoning text appears in the sentinel — while dsh_get(run_id) still returns it from memory. Set DSH_MCP_SENTINEL_INCLUDE_REASONING=1 to break this on purpose.

  • Sentinels are pruned after 7 days by default (DSH_MCP_SENTINEL_TTL_MS; 0 keeps them forever).

(DSH itself writes its own session log under ~/.dsh/sessions; that is what makes resume possible and is outside this server's control.)

⚙️ Environment variables

Variable

Default

Purpose

DSH_BIN

auto-detected

Path to @deepseek-ai/dsh/lib/bin.js

DSH_MCP_PROFILE

dsh-mcp

DSH profile to drive. ⚠️ The stale name is deliberate — see Why is the profile still called dsh-mcp?

DSH_MCP_BOOTSTRAP

on

Whether this server may create/sync the DSH profile on first start. 0 makes it drive an existing profile only and never write a file

DSH_MCP_ROUTE_SOURCE

unset

Pin where model routes are copied from during bootstrap: a profile name (desktop, web, …) or __home__ for $DSH_HOME/cordis.patch.yml. Unset = try home → desktop → web → headless → acp

DSH_MCP_STATE

<repo>/.state/conversations.json

Conversation registry, survives restarts

DSH_MCP_RUNS_DIR

<state file dir>/runs

Root for async completion sentinels (<this>/<conversation_id>/<run_id>.json)

DSH_MCP_LOCKS_DIR

<state file dir>/locks

Holder registrations for the write lock. ⚠️ All instances must agree on this value, or they cannot see each other's registrations and will misread each other as an unidentifiable holder (a GUI) and refuse to preempt

DSH_MCP_LOCK_STALE_MS

90000

How long without a heartbeat before a holder counts as lost (and becomes auto-preemptible by dsh_takeover)

DSH_MCP_IDLE_TTL_MS

0 (never reap)

How long a conversation may sit idle before its DSH process is reaped. ★★ 0 means never reap (the default): the server keeps holding the write lock, so the GUI cannot take it — opening the conversation there is read-only and still works. Cost: one resident DSH process per conversation (measured 300 MB and 476 MB in two cold starts). Set a millisecond value (e.g. 300000) to save memory and accept that the lock can be taken

DSH_MCP_MAX_LIVE

8 (≈2.5–4 GB worst case; see note 8)

★ How many conversation processes to keep alive at once (0 = unlimited). Going over the cap reaps the least recently used one (a running turn is never reaped) — "bounded memory, locks kept where it matters". Use 4 when memory is tight, 0 on a big machine. To free everything at once: dsh_release(all=true)

DSH_MCP_LIST_PROBE_TTL_MS

10000

Cache lifetime for dsh_list's on-disk probe (the probe spawns a DSH process and takes ~1 s)

DSH_MCP_SENTINEL_TTL_MS

604800000 (7 days)

Startup pruning age for sentinels; 0 disables pruning

DSH_MCP_SENTINEL_INCLUDE_REASONING

unset

1 writes result.thinking into the sentinel (breaks the never-on-disk guarantee)

DSH_MCP_PERMISSION

danger-full-access

Default tier for dsh_start

DSH_MCP_REASONING_EFFORT

max

Default reasoning effort

DSH_MCP_PROMPT_TIMEOUT_MS

0 (no timeout)

Wait bound for wait=true only; on expiry the turn merely moves to the background

DSH_MCP_APPROVAL_TIMEOUT_MS

300000

How long a pending approval waits

DSH_MCP_REGISTER_WORKSPACE

project

project / all / 0

DSH_MCP_REQUEST_TIMEOUT_MS

60000

Per-request timeout on the ACP channel (initialize, session/new, …)

DSH_MCP_REAP_INTERVAL_MS

30000

How often the idle reaper checks for conversations to reclaim. Values below the safe floor fall back to the default

DSH_MCP_LOG

silent

silent / info / debug

DSH_MCP_LOG_STDERR

unset

1 forwards DSH's raw stderr

🧪 Tests

node test/run.mjs          # smoke only (no LLM calls)
node test/run.mjs --all    # everything
node test/cleanup.mjs      # suite teardown on its own (temp dirs only; --dry-run to preview)

The suite cleans up after itself: run.mjs always ends with cleanup.mjs, which removes test sessions and workspace registrations inside the OS temp directory only. Real project sessions and the repo's .state/ (your live conversation registry and sentinels) are never touched. To also remove Desktop\dsh-any-agent-test-* artifacts, run node bin/dsh-any-agent-workspaces.mjs --purge-test-sessions explicitly.

Suite

Checks

Covers

smoke

29

handshake, tool table, conversation creation, config, registry purity

boundary

42

protocol edges (double initialize, malformed lines, unknown method), argument validation, unknown ids, cursor edges, lifecycle idempotence, unicode

integration

30

resume-with-memory across processes, interrupt, both interject modes

async

16

fire-and-forget + later collection

sentinel

36

completion sentinel: atomicity, latch semantics, per-conversation namespacing under concurrency, cancelled runs still land it, and reasoning never reaching the file

prune

11

sentinel retention: prunes over-age and crash-leftover files, keeps fresh ones, removes empty shells, TTL=0 disables pruning, and never touches the conversation registry beside it (no tokens, runs every time)

guards

31

environment-variable guards: odd numbers always land on the safe side — IDLE_TTL_MS values like -1 or garbage mean "never reap" (never give the write lock away), a too-small reap interval falls back to the default (no busy loop), and the lost-holder threshold is floored at 30 s (so another instance cannot "legitimately" preempt and kill our turn) (no tokens, runs every time)

approval-details

24

approval review context: the pending-approval snapshot carries the command, reason, cwd and original approval ID — not just a bare ID — so a caller can actually adjudicate; credential-shaped fields and common inline literals are redacted in both the structured context and the displayed title. Verified end-to-end over real stdio MCP

timeout

38

timeout semantics: wait=false is unaffected by timeout_ms; a wait=true expiry merely downgrades to background (turn not cancelled, result not lost, busy never lies); timeout_ms<=0 waits forever; a failed resume invalidates the process instead of wedging the conversation; an empty prompt yields a clear error

lock

79

write lock and preemption: four holder classifications, writeMarker never overwriting a live holder, malformed/missing registration edge cases; end-to-end with two real MCP instances fighting over one conversation → clear error → refusal → force takeover; a crash releases the lock automatically, a wedged holder is preempted without force; plus the "one turn, one sentinel" invariant (inline turns and idle interjects included)

cycle

36

the async dispatch lifecycle: dispatch → collect the sentinel → idle reap → dispatch again, three rounds with no lock error; re-dispatch right on the reap boundary (widening the race); an immediate re-dispatch after dsh_release; and an assertion that reaping leaves no unattributable lock (this suite caught the reaper collecting a freshly spawned process as if it were idle)

concurrency

31

three simultaneous conversations + live incremental reads

capability

22

writing code, running scripts, spawning its own subagents (verified on disk via child session headers)

workspace-effect

24

workspace actually effective when no path is given

acceptance

36

two folders × two conversations doing a read-only IDA Pro analysis

multi

19

several instances and non-ASCII paths: reproduces "two instances share the registry and the later save drops the earlier instance's conversation", then proves that conversation is recoverable by id alone from the session store and actually usable; recovery never silently escalates privileges (a read-only conversation stays read-only); no .tmp residue; and CJK / emoji workspace paths create sessions, do real work, and place files in the right directory

permission

11

★ whether the permission tiers actually take effect (a safety property): it ignores our own return values (the very thing that used to lie) and reads the session's own record (permissions.preset / sandboxMode in the projection cache), checking all three tiers and that their recorded values differ. This suite caught a silent privilege escalation: a defaultPreset in the profile overrides DSH_PERMISSION_MODE at session creation

list

13

the cost of dsh_list (measured): a default call with the on-disk probe takes ~1 s (it spawns a DSH process) while repeated calls hit a cache and drop to single-digit milliseconds; only_running=true and include_closed=false skip the probe with equivalent semantics (unopened on-disk sessions are simply excluded)

Total: 528 checks, all green. (492 in-suite + 36 acceptance)

Counts are the numbers each suite prints itself. smoke/prune/guards/approval-details/boundary run without LLM calls; the rest need a working provider.

🔒 The write lock: one writer per conversation at a time

DSH conversations carry a cross-process write lock: it never expires while the holder lives, and no API can take it from a live holder — so whoever gets there first decides everything. This server turns that into "you can look, we can write":

Situation

Outcome

This server holds the lock (the default)

✅ Opening it in the GUI is read-only: you still see the content, but you cannot take the lock. The default DSH_MCP_IDLE_TTL_MS=0 (never reap) keeps the lock from ever going free

You open it in the GUI while the lock is free

❌ The GUI keeps it permanently (navigating away, waiting and archiving all fail to release it) and this server can never reconnect — only restarting dsh web helps

Recommended

Watch progress with dsh_read (cursor-based incremental reads) or the sentinel file; if you must use the GUI, dsh_release first (that interrupts a running turn, so it fits the gaps between tasks)

Who holds it is reported by dsh_status.lock_holder; dsh_takeover handles the four cases:

lock_holder

Meaning

Action

self

this process holds it

nothing to preempt

stale-mcp

the holder is alive but silent (no heartbeat for 90 s — typically that MCP is wedged)

✅ dsh_takeover kills and takes over

live-mcp

another live dsh-any-agent instance is using it

⚠️ refused by default; force=true takes it

none

no registration → most likely your own DSH GUI

❌ never killed (GUI conversations live inside the single dsh web process, so killing it takes down your whole UI and every GUI conversation with it), reported only

Two measured footnotes. ① A crash leaves no orphan lock — SIGKILL the MCP and its children exit with it (their stdio pipe closes), releasing the lock automatically ✓; so stale-mcp really means "alive but wedged". ② Several instances: the registry is last-writer-wins (a later save drops conversations it never saw), but the session store is authoritative — any session on disk is recoverable by id alone; if you give instances different DSH_MCP_STATE files, point DSH_MCP_LOCKS_DIR at one shared directory, or they cannot see each other's holder registrations.

The full measurements (the A/B probe method, the evidence that the GUI opens read-only, why even archiving does not release the lock, and crash-vs-wedge) are in Usage Notes §3.5–3.12.

⚠️ Known limitations

  1. No token-level streaming into model context — an MCP limitation, not DSH's. Callers get per-step results; humans can follow progress via stderr logs.

  2. True mid-turn steering is impossible — ACP rejects concurrent prompts (a prompt is already in flight for this session). dsh_interject is the practical equivalent: cancel, then immediately start a new turn, history preserved.

  3. Image prompts are unsupported — ACP advertises promptCapabilities: {image: false}.

  4. Restarting the MCP server takes its children with it — when this service is restarted or killed, the DSH children it spawned exit too (their stdio is a pipe to us): in-flight turns are interrupted and no sentinel lands (judge by inspecting the workspace, don't just wait for the file). The good news: the write lock is released automatically, so a restart always gets it back. The service also pushes no MCP notifications; "completion notification" is the sentinel file plus the caller's background waiter.

  5. A conversation that dies before its first successful turn may never have materialized on disk — resume then fails with a clear error. Safe after the first message.

  6. No renaming — DSH's title subsystem has no external rename API (SessionTitleService.rename requires a live in-process session). Titles are auto-generated from the first message.

  7. session/list returns only {sessionId, cwd} and excludes already-open sessions — titles are filled in by this server from DSH's projection cache.

  8. "Never reap" costs memory (measured 300–480 MB per conversation) — that is what buys "the GUI can never take the write lock". Two fresh-start measurements of one active conversation gave 476 MB and 295 MB of added resident memory (the DSH child plus this server); expect the higher figures on a cold process. Two ways to spend less: ① DSH_MCP_MAX_LIVE (default 8 — over the cap, the least recently used process is reaped → bounded memory, locks still held where it matters); ② set DSH_MCP_IDLE_TTL_MS to a millisecond value (at the cost of the lock being takeable). To drop to zero right after a batch of work: dsh_release(all=true) ✓ (lossless, everything resumes on demand).

❓ Why is the profile still called dsh-mcp?

Because renaming it would break things that already exist on disk.

When you first start this server it creates a DSH profile under ~/.dsh/profiles/dsh-mcp/. That directory name is referenced by three other persisted things:

Persisted identifier

Where

Breaks if renamed

the profile directory

~/.dsh/profiles/dsh-mcp/

existing sessions can no longer resolve their provider (no adapter registered for provider "…")

DSH_MCP_* environment variables

your client config

every deployment would need editing at once

the # dsh-mcp:managed marker line

first line of the generated cordis.patch.yml

the bootstrap would stop recognising the file as its own and never sync it again

So the product is dsh-any-agent; the persisted identifiers keep the old name on purpose. This is a deliberate compatibility decision, not an oversight. If you are setting this up from scratch and want a different profile name, set DSH_MCP_PROFILE — the server will use whatever you give it.

💬 Support

  • Bug reports, feature requests, setup questions — open an issue; all of it is welcome there

  • Before reporting a failure — check the troubleshooting table and the Usage Notes; most setup failures are one of a handful of known causes

This is a one-maintainer project, so a clear reproduction (the exact call, the error text, your DSH version) gets a much faster answer than a description.

📜 License

MIT — see LICENSE.

Available Tools

11 tools
dsh_approval_decideA

裁决一个待决审批(仅当会话的 on_approval=ask 时会出现)。用于把 DSH 的越权/提权请求交给调用方决定,既不一律放行也不一律拒绝。

NameRequiredDescriptionDefault
decisionYes放行或拒绝。
approval_idYes来自 dsh_get 的 pending_approvals。
conversation_idYes会话 id,来自 dsh_start 或 dsh_list。

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It discloses that this is a consequential privilege-escalation gate whose outcome is 'allow' or 'deny' and that neither default is applied automatically, which is useful context. However, it says nothing about what happens to the underlying request after the decision, whether a decision is final/idempotent, or what occurs if approval_id is stale or already resolved.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences: the operation and its trigger condition first, then the rationale. Nothing is padded and the actionable constraint is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The call shape is small and fully documented in the schema, and the description covers what the tool is for. For a decision tool with no output schema and no annotations, though, it leaves the agent without any expectation of the result or of failure modes when the approval is missing or already decided.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and every parameter already carries a description, including the enum on decision and the provenance hints for approval_id (from dsh_get pending_approvals) and conversation_id (from dsh_start or dsh_list). The prose adds no additional parameter semantics, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('裁决一个待决审批' / adjudicate a pending approval) and adds the scoping condition that it only surfaces when the conversation's on_approval=ask. No sibling among dsh_start/dsh_send/dsh_interject handles approvals, so the operation is unambiguous, though the description never explicitly contrasts itself with those siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a clear triggering context — use it only when on_approval=ask produced a pending escalation — and frames the intent as routing the decision to the caller rather than auto-allowing or auto-denying. It stops short of stating exclusions (e.g. what to do if the approval is no longer pending) or naming any alternative tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dsh_getA

查看单个会话的详情;带 run_id 时取那一次派活的最终结果(异步模式的验收入口)。不传 run_id 则取最近一次回合。返回:标题、工作区、权限档、状态、轮数、token 用量、上下文压力、待决审批,以及该回合的完整结果(正文/工具/用量/错误)。

NameRequiredDescriptionDefault
run_idNodsh_send 返回的 run_id;不传则取最近一次回合。
conversation_idYes会话 id,来自 dsh_start 或 dsh_list。

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

There are no annotations, so the description carries the full burden, and it does substantial work: it discloses the return payload (title, workspace, permission tier, status, turn count, token usage, context pressure, pending approvals, full turn result). It omits explicit read-only confirmation and any error behavior for invalid ids.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action; the run_id variant is emphasized before the return-value inventory. Dense but every clause carries information, with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the description appropriately enumerates the return fields, which is the main missing piece an agent would otherwise lack. Only error/edge-case behavior and an explicit read-only statement are absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description reinforces run_id's meaning as the async verification handle and restates the default-to-latest-turn behavior, but adds little the schema does not already say.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('查看' / retrieve) and resource ('单个会话的详情'), then splits the behavior into two modes (with vs. without run_id). This is clearly distinct from dsh_list (enumeration) and dsh_send (dispatch), though no sibling is named explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit conditional guidance: with run_id it is the acceptance/verification entry for async dispatches; without run_id it returns the most recent turn. It does not name alternatives (e.g., dsh_list for discovery), but the selection conditions between the two modes are unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dsh_interjectA

插话:会话正在跑某个回合时往里补一句话。两种模式——interject(默认)打断当前回合并立刻改口;queue 不打断,等本轮自然结束后紧接着说。会话空闲时等同于一次普通 dsh_send。说明:ACP 明确拒绝并发 prompt,所以做不到"让模型在同一个回合里即时改向";interject 是它的实用等价物(取消 + 立刻开新回合,历史完整保留)。

NameRequiredDescriptionDefault
modeNointerject(默认)= 打断并改口;queue = 等本轮跑完接着说。
messageYes要插进去说的话。
reasoningNo思考内容展示档位,默认 hide。
timeout_msNo新回合的**等待**上限(毫秒),默认 30 分钟;0 = 一直等。到期转后台,不取消回合。
conversation_idYes会话 id,来自 dsh_start 或 dsh_list。

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the load well: it discloses that interject cancels the in-flight turn and starts a new one, that history is preserved, and that ACP's no-concurrent-prompt rule makes true mid-turn steering impossible. It omits what the caller actually receives back (the full turn result vs. a background acknowledgement) and any permission/auth expectations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core definition, then modes, then the ACP caveat that justifies the design. Every clause carries information; the closing note on why true in-turn redirection is impossible preempts a likely agent misconception.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 5-parameter mutation tool with no annotations and no output schema, the description covers the semantics and the important side effect (current turn cancelled). The missing piece is return shape — whether interject is synchronous with the reply or returns after handoff, which matters given timeout_ms can push work to the background.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and both enums are documented inline, so the description's restatement of mode semantics adds little beyond structured data. The one genuinely additive point is the idle-conversation equivalence for mode, but the timeout_ms background-expiry behavior is only in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb+resource: injecting a message into a conversation that is mid-turn, and immediately enumerates the two behavioral modes. It implicitly demarcates itself from dsh_send by stating the idle-conversation equivalence, so an agent can place it against siblings without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clear conditions are given: use it when a turn is running, pick interject to cancel-and-redirect, pick queue to append after the turn, and fall back to dsh_send when idle. The gap is that it never explicitly contrasts with dsh_interrupt or dsh_takeover, which are the closest competing behaviors for the 'cancel the current turn' case.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dsh_interruptA

中断会话当前正在进行的回合(ACP 原生 session/cancel,走的就是用户手动按停止那条路径)。会话本身保持完好,可以立刻继续派新任务。

NameRequiredDescriptionDefault
conversation_idYes会话 id,来自 dsh_start 或 dsh_list。

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry the full burden, and it does disclose key behavior: it is a cancellation of the current turn, it maps to the native session/cancel path, and the session survives intact and is immediately reusable. It does not say what happens to partial output or whether anything is lost, which keeps it short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the action, with the clarifying mechanism in parentheses and the non-destructive guarantee second. Nothing is wasted and nothing important is buried.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter mutation-style control tool with no annotations and no output schema, the description covers purpose, mechanism, and the critical after-state. The remaining gap is return behavior and what happens to in-flight partial results, which the agent might want to know.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single conversation_id parameter is fully documented in the schema, including where its value comes from (dsh_start or dsh_list). The description adds no further parameter semantics, so the baseline of 3 for high schema coverage applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

It states a specific verb and resource: interrupting the currently running turn of a session. It also identifies the underlying mechanism (ACP native session/cancel, the same path as pressing stop), which makes the purpose unambiguous and distinguishable from siblings like dsh_interject or dsh_takeover.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear context for when to use the tool — when the session has a turn in progress that should be stopped — and states the post-condition that new tasks can be dispatched immediately. It does not, however, explicitly contrast this with dsh_interject or dsh_takeover, so the choice among interruption-style siblings still requires inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dsh_listA

列出会话及其运行状态,用来掌控多个会话实例。state: running(正在跑回合)/ idle(进程活着但空闲)/ detached(进程没开,可 resume 复活)。running 的会话会带上已跑时长、当前正在执行的工具、本轮已产出字数。数据来自两路合并:本服务持有的 + DSH 磁盘上尚未打开的(DSH 的 session/list 会排除已打开的会话)。想只看正在跑的,传 only_running=true;想看某个会话当前吐出的内容,接着用 dsh_read。

NameRequiredDescriptionDefault
cwdNo只看某个工作区下的会话。
only_runningNo只返回正在跑回合的会话,默认 false。**此模式自动跳过磁盘探测,毫秒级**(正在跑的会话必然在内存里)。
include_closedNo是否包含磁盘上未打开的会话(含别的实例 / GUI 建的),默认 true。**代价:要 spawn 一个 DSH 进程去探测磁盘,实测约 1 秒**(结果缓存 10 秒)。只列本服务已打开的会话就传 false,毫秒级。

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses the two-source data merge (in-memory sessions + not-yet-opened sessions on DSH disk), notes that DSH's session/list excludes already-open sessions, and gives concrete cost figures (~1s disk probe, 10s cache) with the only_running fast path. It omits ordering/pagination and any permission requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose and state list are front-loaded, then filters, then the next-step pointer. Dense but every sentence carries operational information; the cached-duration detail is slightly more than needed for selection.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema and no annotations exist, yet the description tells the agent what running sessions return (elapsed time, currently executing tool, words produced this turn) and where data originates. Sufficient to call and interpret the result; minor gaps around ordering and result limits.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all three parameters including their performance tradeoffs. The description adds the state enumeration and the data-merge context useful for interpreting include_closed, but repeats the only_running guidance rather than adding new parameter semantics. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource: list sessions and their run state, with the stated purpose of managing multiple session instances. It defines the state vocabulary (running/idle/detached) so the agent knows exactly what 'run state' means, distinguishing it from siblings like dsh_status or dsh_read.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: use only_running=true to see only active sessions, and use dsh_read next to see a session's output. It does not address when to prefer dsh_get/dsh_status over this tool, so it falls short of full alternative coverage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dsh_readA

读会话的运行期输出(游标式增量)——用它可以看到"正在跑的会话"当前吐出了什么,不必等回合结束。典型用法:dsh_list(only_running) 找到在跑的会话 → 反复 dsh_read 并回传上次的 cursor,就能像切换窗口一样轮流观察多个会话。输出缓冲只在内存里且有界(环形,满则丢最旧),服务/进程退出即消失;思考内容不进缓冲——只有 include_reasoning=true 时才会附带"当前正在跑那一轮"的实时思考。

NameRequiredDescriptionDefault
limitNo单次最多返回多少条,默认 200。
cursorNo上次返回的 cursor;首次不传或传 0。
conversation_idYes会话 id,来自 dsh_start 或 dsh_list。
include_reasoningNo附带当前回合的实时思考内容,默认 false。

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: the buffer is in-memory, bounded, ring-shaped, drops the oldest entries when full, and disappears when the service/process exits. It also discloses that reasoning is excluded from the buffer unless include_reasoning=true, and that only the currently running turn's reasoning is attached.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose and the cursor/polling model, with buffer caveats after. Dense but every clause carries information; the parenthetical bolding and workflow sentence are slightly heavy but not wasteful.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers the behaviors that determine correct invocation: polling with cursors, bounded retention, and the reasoning flag. With no output schema, it does not describe the shape of returned entries or the cursor field in the response, a minor remaining gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description adds genuine meaning beyond it by explaining the cursor round-trip pattern (pass the previous cursor) and the exact semantics of include_reasoning (only the in-flight turn's live reasoning). Limit and conversation_id are left to the schema, which is acceptable at this coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (read a conversation's runtime output), plus the key qualifier that it is cursor-based incremental. Explicitly frames itself against the alternative workflow of waiting for a turn to finish, so an agent can tell why this tool exists versus dsh_get or a blocking call.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a concrete step-by-step scenario: run dsh_list(only_running) to find live conversations, then poll dsh_read repeatedly, passing back the previous cursor, to rotate across multiple sessions. That is explicit when-to-use guidance naming the sibling tool to start from.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dsh_releaseA

释放会话占用的 DSH 进程(释放写锁,让用户能在自己的 DSH GUI 里打开同一个会话)。会话本体留在磁盘上,之后仍可 resume;forget=true 才会从注册表里摘掉。★ 用完了想一次性腾内存就传 all=true(默认"永不回收"下每个会话约 300-480MB,全放掉即立刻归零,之后随时 resume)。

NameRequiredDescriptionDefault
allNo忽略 conversation_id,释放**所有**活着的会话进程(腾内存用)。默认 false。
forgetNo同时从本服务的注册表里移除,默认 false。
conversation_idNo会话 id,来自 dsh_start 或 dsh_list。

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses the write-lock release, that the session persists on disk, that forget=true mutates the registry, and quantifies the memory footprint (~300-480MB per session) plus the 'never reclaim' default. It is silent on what happens to in-flight work in a running session at release time and on error behavior for an invalid id.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Each clause earns its place — semantics, persistence, forget, then the all=true memory tip — and the core action is front-loaded before the flags. It is somewhat dense and the ★/quote styling is decorative rather than structural, but nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-param mutation tool with no annotations and no output schema, the description covers the essential decision points: what is destroyed, what survives, and when to use each flag. Missing only edge-case behavior (invalid conversation_id, active-session handling) and return shape.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline is 3; the description adds real meaning beyond the schema by explaining the memory rationale for all=true, the 'ignores conversation_id' interaction, and the registry-mutation consequence of forget=true. The interaction semantics (all overrides conversation_id) are the valuable addition.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (release the DSH process / write lock held by a session) and immediately clarifies the scope: the session body stays on disk and remains resumable. The distinction between process release, registry removal (forget=true), and mass release (all=true) lets an agent separate this from siblings like dsh_interrupt or dsh_takeover.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use guidance for each option: forget=true only when you want the registry entry gone, all=true when you want to reclaim memory all at once, otherwise the session stays resumable. It does not explicitly contrast with dsh_interrupt/dsh_takeover, which would have made the routing unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dsh_sendA

向会话派一个任务。默认不阻塞(wait=false):立即返回 run_id + sentinel_file,回合在后台跑,跑完自动通知你 —— 你继续做别的事,也永远不用担心任务被"超时"打断。想原地等结果就显式传 wait=true(可配 timeout_ms;到期只会转后台,不会取消回合)。【超时不会毁掉任务】wait=true 到达 timeout_ms 仍没跑完时,本工具不会报错、也不会取消回合,而是自动降级为后台:返回 run_id + sentinel_file,回合继续跑完,结果稍后由哨兵文件通知。(timeout_ms=0 表示一直等。)所以任何时候你都能拿到结果,不用怕"超时了白干"。等待模式与后台模式都在回合结束时原子落地一个含最终结果的小文件——推荐用后台任务(Bash run_in_background + until [ -f sentinel_file ])等它,跑完自动通知你,无需轮询;文件出现后读它即得结果,或用 dsh_get(conversation_id, run_id) 验收,dsh_read 可中途看输出。默认隐藏思考内容(只返回正文 + 思考统计)。若会话正在忙,本工具会报错——改用 dsh_interject 插话,或用 dsh_interrupt 先打断。

NameRequiredDescriptionDefault
waitNo**默认 false**:立即返回 run_id + sentinel_file,回合在后台跑,跑完由哨兵文件通知(推荐用法)。true = 原地阻塞到回合结束再返回完整结果。
promptYes任务描述。可以引用工作区里的文件路径。
reasoningNo思考内容展示档位。默认 hide(只返回正文,思考只给统计)。full 会占用大量上下文。
timeout_msNo仅 wait=true 时的**等待**上限(毫秒),默认 30 分钟;0 = 一直等。到期不会取消回合,只会转成后台并给你 run_id。
conversation_idYes会话 id,来自 dsh_start 或 dsh_list。

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden and does so: it discloses that a timeout does not error and does not cancel the turn but degrades to background, that results land atomically in a sentinel file, that reasoning is hidden by default with a context-cost warning for `full`, and that a busy conversation produces an error. These are non-obvious failure-mode behaviors an agent could not infer from the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The default non-blocking behavior is correctly front-loaded, but the same point — that timeouts never destroy the task — is made three separate times with heavy bolding, and the result-retrieval mechanics appear twice. Substantial length is spent on emphasis rather than new information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a dispatch tool with no annotations and no output schema, the description covers everything needed: what is returned (run_id + sentinel_file), how to obtain the final result (sentinel file, dsh_get, dsh_read mid-run), default reasoning visibility, and the error path. An agent has no unanswered question before calling it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the per-parameter descriptions already cover the wait default, timeout_ms degradation semantics, reasoning levels, and conversation_id provenance. The prose largely restates those same facts rather than adding new meaning, so the baseline 3 applies; the only marginal addition is the cross-parameter framing that timeout_ms matters solely when wait=true.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence names a specific verb and resource (dispatch a task to a conversation) and immediately distinguishes the tool's two modes. It also names the siblings it is not for the busy case (dsh_interject, dsh_interrupt) and the verification path (dsh_get, dsh_read), so an agent can place it in the family without opening other schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use: default wait=false for fire-and-forget, explicit wait=true to block in place, and a recommended Bash run_in_background + `until [ -f sentinel_file ]` pattern instead of polling. It also states the negative case — if the conversation is busy the tool errors and you should use dsh_interject or dsh_interrupt — which is exactly the alternative routing an agent needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dsh_startA

新建一个 DSH 会话(一个可长期共事的编码 agent)。会话活在磁盘上,进程只是缓存;进程被回收或崩溃后会自动用 ACP session/resume 复活,对话不丢。返回 conversation_id 与对话标题。建好后用 dsh_send 派活。

NameRequiredDescriptionDefault
cwdYes工作区绝对路径。决定 agent 的工作目录、AGENTS.md/技能的发现范围、以及沙箱可写根。
modelNo模型名(如 deepseek-flash)。指定时必须同时给 provider。
providerNo模型提供方名(如 deepseek-official)。留空用 profile 默认。
permissionNo权限档。默认 danger-full-access。read-only 适合分析不信任样本(如恶意软件);workspace-write 越界操作会冒泡为待决审批。**档位在创建时定下**:DSH 设计上"resume 的会话保留它自己记录的权限",所以事后改这个参数不会改变已有会话,要换档需新建。(本档位是**真实生效**的 —— 由启动时传入的 env 决定,并有 test/permission.mjs 对着会话自己的记录逐档核验。)
title_hintNo可选的标题提示(DSH 的标题由首条消息自动生成,这里只是兜底显示)。
on_approvalNo审批处理策略。默认 read-only → auto-deny,其余 → auto-allow。ask 会把审批挂起,需用 dsh_approval_decide 裁决。
reasoning_effortNo思考深度,默认 max。注意 ACP 自身的默认是"Provider default"(不设),本服务会显式设成 max。想省钱可调低。

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and discloses significant behavioral traits: the session lives on disk, the process is just cache, and auto-resurrection via ACP session/resume prevents conversation loss. It does not cover failure modes, resource cleanup needs, or auth requirements, but the core persistence behavior is well explained.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and front-loaded: it opens with the core purpose, then adds relevant behavioral details and next steps in a few short clauses. Every sentence earns its place and there is no wasted text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the absence of annotations and an output schema, the description provides a solid overview of what the tool does, its persistence behavior, return values, and next steps. However, it lacks guidance on when not to use it and omits mention of cleanup (e.g., dsh_release) or error handling, leaving minor gaps for full contextual completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already fully documents all seven parameters. The description adds no parameter-specific meaning beyond what is in the schema, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('新建') and resource ('DSH 会话'), and distinguishes it from the sibling tool dsh_send by explaining that after creation you use dsh_send to assign tasks. It also clarifies the return values (conversation_id and title), making the tool's purpose immediately clear.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear usage context: it is for creating a new session, and it explicitly points to dsh_send for the next step. However, it does not state when not to use it (e.g., for resuming an existing session instead of creating a new one) or mention alternatives like dsh_get or dsh_list, leaving some guidance implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dsh_statusA

轻量状态查询:会话是否活着、是否在忙、待决审批数、最近一次回合的结果。

NameRequiredDescriptionDefault
conversation_idYes会话 id,来自 dsh_start 或 dsh_list。

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden; it does signal a read-only query and tells what the payload contains, which is the most important behavioral fact here. However, it says nothing about latency, whether the status is cached, or how recently-it-is-fresh the 'recent turn result' is, which matters for an agent polling state.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no waste — the qualifier '轻量' leads and the returned fields follow immediately. Nothing could be removed without losing information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description usefully enumerates the returned fields, which is exactly the gap it needs to fill for a one-parameter query tool. Only the freshness/latency dimension of the status snapshot is left unstated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is only one parameter and schema coverage is 100%; the schema already documents that conversation_id comes from dsh_start or dsh_list. The description adds no further syntax or format guidance, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('轻量状态查询') and enumerates the exact fields returned (liveness, busy flag, pending approval count, last turn result), which is more precise than the name alone. It does not explicitly contrast with close siblings like dsh_get or dsh_read, so an agent still has to infer the difference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The word '轻量' implies this is the cheap first-look call, but there is no explicit statement of when to prefer it over dsh_get or dsh_read, and no prerequisites or exclusions. Usage is only implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dsh_takeoverA

抢占会话的写锁(DSH 的写锁是跨进程内核信号量,持有者活着就不过期,没有 API 能直接夺走——唯一手段是杀掉持有者进程,所以本工具会先判定持有者是谁):① 若锁其实空着,正常 resume 即可,不会杀任何东西;② 若持有者是本服务自己留下的孤儿进程(MCP 崩了/重启了,子进程还活着握着锁)→ 自动杀掉并接管,安全;③ 若持有者是另一个活着的 dsh-any-agent 实例 → 默认拒绝,传 force=true 才夺;④ 若持有者无法识别(多半是你自己的 DSH GUI 正开着这个会话)→ 绝不杀(那会连你的界面和正在看的对话一起杀掉),只报告,请你在那个窗口里关掉该会话后重试。注意:杀掉正在跑回合的持有者会中断那一轮(会话日志记为 interrupted),会话本体不受损。

NameRequiredDescriptionDefault
forceNo仅用于"另一个活着的 dsh-any-agent 实例持有"这一种情况;对 GUI/未知持有者无效(永远不杀)。
conversation_idYes会话 id,来自 dsh_start 或 dsh_list。

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: it discloses that the tool is destructive (kills a process), that killing a holder mid-turn interrupts that turn and marks the log as interrupted while leaving the conversation intact, and that unidentified/GUI holders are never killed. The safety boundary of force=true is also spelled out, which is exactly the kind of context annotations would otherwise supply.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The numbered case structure (①②③④) is genuinely functional and every clause carries decision-relevant information. It is dense and the long parenthetical in the opening sentence delays the concrete action slightly, so it is not maximally front-loaded, but nothing is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive, no-annotation, no-output-schema tool, the description covers the mechanism (kernel semaphore, no revoke API), the failure modes (orphan vs. foreign instance vs. unknown holder), the side effects (interrupted turn, intact conversation), and the user-recovery path. An agent has everything needed to invoke it or correctly abstain.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and both params are already documented, hitting the baseline 3, but the description adds real behavioral meaning for force: it applies only to case ③ (another live dsh-any-agent instance) and has no effect on GUI/unknown holders because those are never killed. conversation_id gains nothing beyond the schema, keeping this below a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific action on a specific resource: seize the conversation's write lock, explaining that the lock is a cross-process kernel semaphore and that the only takeover mechanism is killing the holder process. It explicitly contrasts itself with the ordinary 'resume' path (case ①), so an agent can tell it is an escalation tool rather than the normal way to attach to a session.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It enumerates four exhaustive situations with explicit decisions: free lock → do nothing and resume; orphaned child of this service → kill and take over; another live dsh-any-agent instance → refuse unless force=true; unidentified holder (likely the user's own DSH GUI) → never kill, report and ask the user to close it. When-to-use, when-not-to-use, and the only override are all stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 11 tool updatesv0.1.0
    • First observeddsh_approval_decide
    • First observeddsh_get
    • First observeddsh_interject
    • First observeddsh_interrupt
    • First observeddsh_list
    • First observeddsh_read
    • First observeddsh_release
    • First observeddsh_send
    • First observeddsh_start
    • First observeddsh_status
    • First observeddsh_takeover

TDQS

A4.1/5.0

Scored across 11 tools

Disambiguation4/5

Most tools target clearly distinct operations (start, send, interrupt, release, takeover, approval_decide), but there is real overlap between dsh_get, dsh_status, and dsh_read — all report on a session's state/output — and between dsh_interject (interrupt mode) and dsh_interrupt. The descriptions do disambiguate these (read=cursor-based live output, status=lightweight check, get=full detail/run result), so confusion is limited but not zero.

Naming Consistency4/5

All names use a consistent dsh_ prefix with snake_case, and 9 of 11 follow a clean verb style (start, send, release, list, read, get, interject, interrupt, takeover). The deviations — dsh_approval_decide (noun_verb) and dsh_status (noun) — are minor and still readable.

Tool Count5/5

11 tools is well within the sweet spot for a session-management server, and each tool maps to a distinct lifecycle concern: create, dispatch, observe, steer, cancel, reclaim, and approve. Nothing feels redundant in count.

Completeness4/5

The lifecycle is well covered: create, send, list, read, get, interject, interrupt, release (with forget), takeover, and approval handling, plus auto-resume via ACP. Minor gaps: no explicit resume/open tool, no way to change a session's permission profile or on_approval setting after creation, and no delete/purge beyond release+forget.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Lets any MCP-capable coding harness (Cursor, Claude Code, Codex, Gemini CLI, VS Code/Copilot, opencode, and others) hand a self-contained task to a real, isolated DeepSeek Harness process that works in a chosen workspace with its own context window, model, and toolchain, then returns the final answer. Exposes task submission with time estimates and acceptance criteria, status polling with live progress and process-tree telemetry, graceful cancel and forced kill of whole process trees, and a health probe reporting concurrency, deadlines, and recent jobs.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Lets Claude Code, Codex, or any MCP client drive DeepSeek Harness as a long-lived coding agent: starting and resuming conversations, dispatching tasks in the background, streaming live output and reasoning stats, interjecting or interrupting mid-turn, handling approval prompts, and collecting results later. Sessions persist by ID, so work survives process restarts and can be handed back to a DSH GUI when needed.
    325 npm
    15
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables any MCP-capable client (Claude Code, Cursor, VS Code, and others) to deliver messages into a local DeepSeek Harness over stdio or Streamable HTTP. It also exposes a pull-based task queue so producers can publish work that clients claim, complete, or list.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables MCP-compatible clients to delegate complex multi-step programming and research goals to coordinated teams of specialized subagents, with cross-session messaging, swarm and pipeline workflows, sandboxed workspace tools, persistent SQLite state, and a live dashboard.
    1
    MIT