# mcpctl × dsh (DeepSeek harness) ## What `mcpctl config dsh --project X` wires up | Piece | Where | Purpose | |-------|-------|---------| | MCP entry | `~/.dsh/profiles//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__`, the same namespace Claude Code sees. ## Skills `~/.dsh/skills` is the "user" root of dsh's filesystem skill provider: a skill is a `/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: ```bash mcpctl skills sync --agent dsh --project X ``` ## Skipping steps ```bash 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 ```