Files
mcpctl/docs/dsh-integration.md
Michal 5f6d054ffa
Some checks failed
CI/CD / typecheck (pull_request) Successful in 1m20s
CI/CD / test (pull_request) Failing after 28s
CI/CD / lint (pull_request) Successful in 2m51s
CI/CD / smoke (pull_request) Has been skipped
CI/CD / build (pull_request) Has been skipped
CI/CD / publish (pull_request) Has been skipped
feat(cli): dsh integration — config dsh + skills sync --agent dsh
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
2026-10-13 22:58:39 +01:00

2.9 KiB
Raw Blame History

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, any dsh-mcp-client entry whose serverName is mcpctl (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