Files
mcpctl/docs/dsh-integration.md

66 lines
2.9 KiB
Markdown
Raw Permalink Normal View History

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
# 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:
```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
```