Bundle
dsh-echo
Record MCP once. Replay it safely inside DeepSeek Harness.
- Source
- bleakbelladonnals
- License
- Apache-2.0
- Updated
- Updated 2 days ago
Readme
<div align="center">
<img src="media/hero.svg" alt="DSH Echo — Record MCP once. Replay it safely." width="100%">
<p>
<a href="https://github.com/bleakbelladonnals/dsh-echo/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/bleakbelladonnals/dsh-echo/actions/workflows/ci.yml/badge.svg"></a>
<a href="LICENSE"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/license-Apache--2.0-9b87f5.svg"></a>
<a href="https://github.com/marketplace/actions/dsh-echo-mcp-contract-gate"><img alt="GitHub Marketplace" src="https://img.shields.io/badge/Marketplace-MCP%20Contract%20Gate-2ea44f?logo=github"></a>
<img alt="Node.js 22.19+ or 24+" src="https://img.shields.io/badge/node-22.19%2B%20%7C%2024%2B-38d6c7.svg">
<img alt="DSH 0.1.1-rc.2" src="https://img.shields.io/badge/DSH-0.1.1--rc.2-171928.svg">
</p>
<p>
<strong>An unofficial DeepSeek Harness plugin for deterministic MCP record/replay.</strong><br>
Capture a real tool session once, replay it without credentials or network, and inspect every match and contract change inside DSH.
</p>
<p>
<a href="README.zh-CN.md">简体中文</a> ·
<a href="https://bleakbelladonnals.github.io/dsh-echo/">Website</a> ·
<a href="#github-marketplace-action">GitHub Action</a> ·
<a href="#install-into-dsh">Install</a> ·
<a href="#record-and-replay">Quick start</a> ·
<a href="docs/architecture.md">Architecture</a> ·
<a href="docs/security.md">Security</a>
</p>
</div>
> [!NOTE]
> DSH Echo is not affiliated with or endorsed by DeepSeek. Version 0.1 is
> installable from source but has not been published to npm.
## Why DSH Echo?
MCP tools often sit in front of APIs, databases, and services that make agent
tests slow, costly, nondeterministic, or unsafe. DSH Echo puts a cassette
between DSH and the real MCP server: record once against reality, then replay
the exact conversation offline.
| Record reality | Replay safely |
| --- | --- |
| stdio and Streamable HTTP/SSE | deterministic request matching |
| append-only, versioned JSONL | fail-closed on an unrecorded call |
| secrets redacted before disk | no real server on a normal replay |
| duration and response captured | structured nearest-call diff |
| See what happened | Guard the contract |
| --- | --- |
| Session-scoped Web inspector | contract snapshots |
| arguments, results, hit/miss state | classified schema drift |
| compact Trajectory annotations | breaking-change CI gate |
| second redaction pass before UI | scanned fixture export |
## GitHub Marketplace Action
Use the same health, safety, and contract checks as a pull-request gate. The
composite Action runs the selected MCP server, compares it with a committed
snapshot, writes a job summary, and keeps one PR comment updated across reruns.
~~~yaml
- uses: bleakbelladonnals/dsh-echo@v0.1.0
with:
server-command: node dist/server.js
snapshot-file: mcp-contract.snapshot.json
fail-on: breaking
~~~
[Open the DSH Echo MCP Contract Gate in GitHub Marketplace](https://github.com/marketplace/actions/dsh-echo-mcp-contract-gate).
## Install into DSH
Requirements:
- Node.js 22.19+ on the Node 22 line, or Node.js 24+
- DeepSeek Harness compatibility baseline: <code>0.1.1-rc.2</code>
- a fresh or explicitly selected DSH profile
Build a local package and install it into an isolated profile:
~~~bash
git clone https://github.com/bleakbelladonnals/dsh-echo.git
cd dsh-echo
npm ci
npm run build
npm pack --ignore-scripts
export DSH_HOME="$(mktemp -d)"
dsh plugin --profile web add ./dsh-echo-0.1.0.tgz
dsh --profile web --no-open
~~~
Open a Session and choose the **Echo / 录制回放** tab. Installation adds the
plugin with <code>bindings: []</code>, so it does not intercept or change an MCP
server until you explicitly bind one.
To remove it:
~~~bash
dsh plugin --profile web remove dsh-echo
~~~
Omit the temporary <code>DSH_HOME</code> only when you intentionally want to
install into your normal DSH profile.
## Record and replay
Record a stdio MCP server. Redaction is enabled by default:
~~~bash
dsh-echo record -o .dsh-echo/demo.cassette.jsonl -- \
node examples/fixture/server.mjs
~~~
Replay the same interaction offline:
~~~bash
dsh-echo replay .dsh-echo/demo.cassette.jsonl
~~~
An unrecorded request returns JSON-RPC error <code>-32601</code> and exits
non-zero. The real server is not started. Live fallback exists only when you
explicitly pass <code>--on-miss passthrough</code> together with a real server
command.
Streamable HTTP works through a loopback endpoint:
~~~bash
# Record
dsh-echo record -o .dsh-echo/http.cassette.jsonl \
--http http://127.0.0.1:3000/mcp \
--listen 127.0.0.1:6402
# Replay
dsh-echo replay .dsh-echo/http.cassette.jsonl \
--listen 127.0.0.1:6402
~~~
## Connect one DSH MCP server
DSH currently constructs MCP transports directly, so DSH Echo uses a reversible
profile overlay instead of modifying DSH or editing your source profile in
place:
~~~bash
dsh-echo profile patch \
--source ./cordis.yml \
--out ./cordis.echo.yml \
--recovery ./cordis.echo.recovery.json \
--root ./.dsh-echo \
--server-row mcp-demo \
--cassette-id demo \
--cassette demo.cassette.jsonl \
--mode replay
~~~
The generated replay row does not contain the original server command.
Record/passthrough rows use argv arrays, never a shell-concatenated command.
The source file is untouched. Restore the saved row into another file with:
~~~bash
dsh-echo profile restore \
--recovery ./cordis.echo.recovery.json \
--out ./cordis.restored.yml
~~~
Review generated YAML before applying it. HTTP record/replay is supported;
HTTP passthrough is intentionally unavailable in v0.1.
## What appears in DSH
For DSH tool names shaped like <code>mcp__<serverName>__<tool></code>,
the Host plugin adds a compact <code>tool/result.meta.dshCassette</code>
reference. Full tool payloads stay in the cassette rather than being duplicated
into the Session log.
The **Echo / 录制回放** tab shows:
- cassette identity, mode, transport, format, and redaction status;
- each interaction's arguments, result, duration, and origin;
- recorded, hit, miss, passthrough, and error state;
- a structured argument diff for the nearest miss;
- contract and schema drift, plus a snapshot action;
- the annotations attached to the current Session/Trajectory.
Host APIs accept configured cassette IDs rather than caller-provided paths.
All cassette and snapshot paths must resolve below the configured root, and
values are redacted again before reaching the browser.
## Contract gate
Capture a baseline:
~~~bash
dsh-echo snapshot --stdio "node examples/fixture/server.mjs" \
-f mcp-contract.snapshot.json
~~~
Fail CI when a tool is removed or a breaking schema change appears:
~~~bash
dsh-echo snapshot --check --fail-on breaking \
--stdio "node examples/fixture/server.mjs" \
-f mcp-contract.snapshot.json
~~~
The repository's own CI fixture deliberately removes a tool and adds a required
property, then asserts that the gate fails.
## Export a reviewable fixture
Raw recordings live under the ignored <code>.dsh-echo/</code> directory.
Export is a separate, explicit step:
~~~bash
dsh-echo export-fixture \
.dsh-echo/demo.cassette.jsonl \
--root ./fixtures \
--out demo.cassette.jsonl
~~~
Export refuses paths outside the selected fixture root and blocks detected
secrets. A passing scan still requires human review: pattern-based redaction is
defense in depth, not proof that a recording is safe to publish.
## Architecture
~~~mermaid
flowchart LR
DSH["DeepSeek Harness"] --> Adapter["DSH Echo profile adapter"]
Adapter --> Core["record / replay core"]
Core --> Live["real MCP server"]
Core --> Tape[("versioned cassette")]
Tape --> Core
Core --> Session["Session annotations"]
Session --> UI["Echo Web inspector"]
~~~
The transport engine remains usable as a standalone CLI, while the DSH layer
lives under <code>src/dsh/</code>. See
[architecture](docs/architecture.md) for lifecycle, trust boundaries, and
integration decisions.
## Development
~~~bash
npm ci
npm run lint
npm run typecheck
npm test
npm run test:e2e
npm run audit:pack
npm pack --dry-run --ignore-scripts
~~~
The current suite covers 463 tests, including real stdio record/replay, a
no-server replay tripwire, profile recovery, path containment, UI redaction,
contract drift, lifecycle cleanup, and package inspection. Acceptance runs use
temporary homes, loopback listeners, isolated npm caches, and isolated DSH
profiles; they do not read or write the user's DSH configuration.
More detail:
- [Validation evidence](docs/validation.md)
- [Security model](docs/security.md)
- [Upstream audit](docs/upstream-audit.md)
- [DSH integration alternatives](docs/dsh-integration-options.md)
- [Fixture server](examples/fixture/README.md)
## Upstream and license
DSH Echo is derived from
[`ivermin1123/mcp-cassette`](https://github.com/ivermin1123/mcp-cassette)
at commit <code>9e48be26cbf1f7fca5edde142673a9b102a25e86</code> (upstream
version <code>0.4.0</code>). The retained engine provides stdio and Streamable
HTTP/SSE record/replay, matching, redaction, contract diff, safety lint, and
Vitest integration.
See [UPSTREAM.md](UPSTREAM.md) and [NOTICE](NOTICE) for the exact import and
modification record. Licensed under [Apache-2.0](LICENSE).
Install
dsh plugin --profile web add github:bleakbelladonnals/dsh-echo
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-echo from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.