dsh-mcp-lazy
Lazy MCP gateway for DeepSeek Harness: one stable proxy tool instead of N tool schemas, servers connect on first use and idle out, metadata cached to disk.
- Stars
- 0
- Language
- TypeScript
- Created
- Sep 10, 2026
- Updated
- Sep 11, 2026
Introduction
dsh-mcp-lazy
A lazy MCP gateway for DeepSeek Harness. It puts one
tool in front of the model instead of N MCP tool schemas: servers start on first use, idle
out again, and their tool metadata is cached on disk so search and describe never spawn
anything.
Why
@deepseek-ai/dsh-mcp-client connects every configured server at startup and registers every
one of its tools as a native tool. As its own README puts it, tool descriptions and input
schemas "enter every request while the tools are registered". A handful of servers therefore
costs thousands of tokens on every request and one resident child process each — whether or
not the model ever calls them.
This plugin keeps the model-facing surface at exactly one tool whose schema never changes, discovers tools on demand from a disk cache, and only spawns a server when a call needs it.
The numbers
Measured against chrome-devtools-mcp@1.6.0 (29 tools), both sides rendered the same way —
JSON bytes of the tool definitions, then four bytes per token:
| per request | |
|---|---|
| native registration | 21252 bytes ≈ 5313 tokens |
| this gateway | 1525 bytes ≈ 381 tokens |
| saved | 92.8% |
The gateway figure is constant: configuring ten more servers does not move it, because their schemas are read on demand rather than sent every request.
The honest counterweight. The gateway costs a fixed 1525 bytes, so it wins only when a server's rendered tool definitions exceed that. The bundled fixture offers 7 small tools and the saving drops to 1.7%. A server with a couple of tiny tools would make the gateway a net loss. Measure your own before assuming:
pnpm run measure:savings # local fixture
node scripts/measure-token-savings.mjs --npx <your-server>
Install
dsh plugin --profile <your-profile> add dsh-mcp-lazy
That installs the package into the profile and registers its bundle patch, which inserts the
mcp-lazy row. Then give it your servers in the profile's cordis.patch.yml:
- id: mcp-lazy
config:
servers:
- serverName: chrome
transport: stdio
command: npx
args: ['-y', 'chrome-devtools-mcp@1.6.0']
lifecycle: lazy
- serverName: docs
transport: streamable-http
url: http://127.0.0.1:3000/mcp
Restart the profile. Every field is documented in docs/configuration.md.
Migrating from @deepseek-ai/dsh-mcp-client: move each row's config into one entry of
servers and drop the per-row id. The transport fields keep their meaning.
What the model sees
One tool, always the same 11 parameters:
mcp({ search: "screenshot" }) # find a tool — reads the cache, starts nothing
mcp({ describe: "take_screenshot" }) # full argument schema
mcp({ tool: "take_screenshot" }) # call it — this is what spawns the server
mcp({ tool: "echo", server: "docs" }) # disambiguate a name two servers share
mcp({ connect: "chrome" }) # connect and refresh the cache, without calling
mcp({ instructions: "chrome" }) # the server's own usage notes
mcp({}) # status: tool count, connection state, cache age
Documentation
| docs/configuration.md | every field, the four lifecycle modes, output ceiling |
| docs/troubleshooting.md | failed servers, cold cache, name resolution |
| docs/development.md | build, test, why link-dsh is mandatory |
| docs/design/plan.md | implementation plan and acceptance criteria |
| docs/design/parity-pi-mcp-adapter.md | module-by-module audit against pi-mcp-adapter v2.33.0 |
Known limitations
The v1 boundary, stated plainly:
- Tools only. No MCP resources, prompts, sampling, or elicitation. Only
tools/list_changedis subscribed. - No OAuth. Authentication is a plaintext
headersentry or an environment variable. - Image and audio results are not forwarded. They are projected to a one-line metadata entry (type and byte count). Forwarding pixels needs attachment storage this plugin does not implement.
- No approval gate. MCP calls follow your DSH permission preset.
- No config interop. It reads DSH-native config only; it will not import
.mcp.json, Cursor, Claude Code, Codex, or VS Code server lists. - The regex guard is deliberately narrow. A 256-character cap plus a nested-quantifier
check, which does not catch overlapping alternation like
(a|aa)+or polynomial backtracking likea*a*a*b. A full analyser would mean a second runtime dependency. - No SSE or unix-socket transports.
stdioandstreamable-httponly. npxis not resolved to the underlying binary, so annpx-launched server costs one extra Node parent process.
Development
pnpm install
pnpm test # builds, relinks the peer packages, runs 179 tests
pnpm run check # typecheck (sources and tests) then build
See CONTRIBUTING.md.
Star history
License
MIT — see LICENSE. The connection supervisor, transport factory, and
environment-scrubbing rules derive from @deepseek-ai/dsh-mcp-client; the single-proxy-tool
gateway, metadata cache, and weighted search ranking derive from pi-mcp-adapter. Both are
MIT; their notices are reproduced in THIRD_PARTY_NOTICES.md.