Skip to content
dsh.fish
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__&lt;serverName&gt;__&lt;tool&gt;</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

  • 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.
Source