Files
mcpctl/docs/dsh-integration.md
Michal 2ed6d38784
Some checks failed
CI/CD / lint (pull_request) Successful in 1m11s
CI/CD / test (pull_request) Successful in 1m25s
CI/CD / typecheck (pull_request) Successful in 3m10s
CI/CD / smoke (pull_request) Failing after 1m58s
CI/CD / build (pull_request) Successful in 5m20s
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 23:08:40 +01:00

66 lines
2.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```