dsh (the DeepSeek harness) joins claude, pi, prime-agent and opencode as
a first-class host. `mcpctl config dsh --project X`:
- merges ONE dsh-mcp-client entry (id mcp-mcpctl, mount name mcpctl,
stdio bridge `mcpctl mcp -p X`) into the profile's user patch layer
(~/.dsh/profiles/<profile>/cordis.patch.yml). The stdio bridge keeps
auth in ~/.mcpctl/credentials — the patch file is 0644 and commonly
committed to dotfiles, so the HTTP mount's bearer header is not an
option there.
- edits with the comment-preserving `yaml` Document API and touches only
its own entry: dsh appends runtime UI state to the same file, bundles
and users own the rest, and a corrupt or non-array file aborts instead
of being overwritten. Re-runs re-point the bridge args in place and
keep user-added keys; a same-mount entry under another id is adopted,
a hand-configured server with its own serverName is never touched.
- writes the .mcpctl-project marker with the prime-agent semantics and
syncs skills into ~/.dsh/skills — the user root dsh-skill-filesystem
scans AND watches, so synced skills appear without a restart. `dsh`
becomes a skills sync target with the shared-tree semantics, state in
~/.mcpctl/skills-state-dsh.json.
Verified against dsh 0.2.0-rc.2: the web profile boots, spawns the
bridge as its child within a second, and serves with the mount active;
patch-file comments and dsh's own appended entries survive re-runs.
Completions regenerated for the new subcommand.
Note: tests/commands/prime-agent.test.ts ("does not write a marker when
run from $HOME") fails on main too on this machine and leaks a real
~/.mcpctl-project — pre-existing test-isolation issue, untouched here.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V6HNx6cyr4J4CiMGCy1wkF
2.9 KiB
mcpctl × dsh (DeepSeek harness)
What mcpctl config dsh --project X wires up
| Piece | Where | Purpose |
|---|---|---|
| MCP entry | ~/.dsh/profiles/<profile>/cordis.patch.yml |
one @deepseek-ai/dsh-mcp-client mount named mcpctl, running the stdio bridge mcpctl mcp -p X |
| Project marker | ./.mcpctl-project |
what skills sync reads to know the scope (--skip-marker opts out) |
| Skills | ~/.dsh/skills/ |
the project's SKILL.md bundles, in the user root dsh-skill-filesystem scans and watches |
--profile picks the dsh profile (default web); -o points at a patch file
directly.
Why the stdio bridge, not the HTTP gateway
dsh's MCP client speaks both stdio and Streamable HTTP, but its config is ONE
user-owned YAML patch file — cordis.patch.yml — which is 0644 and commonly
kept in a dotfiles repo. The HTTP mount would need the project bearer token in
a header there; the stdio bridge (mcpctl mcp -p X) authenticates from
~/.mcpctl/credentials instead, exactly like the Claude Code integration, so
nothing secret ever lands in the patch file. The trade-off is the same as
Claude's: switching projects means re-running config dsh and restarting dsh,
where opencode/prime-agent re-point their HTTP mounts live.
One entry, merged in place
The patch file is dsh's own: bundles compose it, the user hand-edits it, and
dsh itself appends UI state to it at runtime. So config dsh parses it with a
comment-preserving YAML editor and touches ONLY its own entry:
- Ours is the entry with id
mcp-mcpctl— or, adopted, anydsh-mcp-cliententry whoseserverNameismcpctl(that mount name is this CLI's; dsh requires unique serverNames, so a hand-configured second gateway with its own name never matches). - A project switch rewrites the bridge args of the existing entry in place and
keeps any keys the user added to it (an
env:block, say). - Everything else — other entries, comments, formatting — survives byte-for-byte. A corrupt or non-array file aborts the run instead of being overwritten.
- Tools surface to the model as
mcp__mcpctl__<tool>, the same namespace Claude Code sees.
Skills
~/.dsh/skills is the "user" root of dsh's filesystem skill provider: a skill
is a <name>/SKILL.md bundle with name + description frontmatter — the
same format every other target uses, so the shared sync writes it unchanged.
dsh watches the root, so synced skills appear without a restart. The tree has
the same shared-tree semantics as prime-agent/pi/opencode: per-project
ownership tracking, untracked hand-authored skills are preserved, state in
~/.mcpctl/skills-state-dsh.json.
Re-sync later with:
mcpctl skills sync --agent dsh --project X
Skipping steps
mcpctl config dsh --project X --skip-skills # don't sync skills
mcpctl config dsh --project X --skip-marker # don't touch .mcpctl-project here
mcpctl config dsh --project X --dry-run # print what would change