feat(cli): dsh integration — config dsh + skills sync --agent dsh
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
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
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
This commit is contained in:
65
docs/dsh-integration.md
Normal file
65
docs/dsh-integration.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user