DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

kvnis /

kvnis/dsh-lazy-mcp

Verified

Lazy MCP client for DeepSeek Harness: registers a server's tools from a cached manifest at boot and connects on the first call.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@c8f13bd0

dsh-lazy-mcp

A lazy-loading MCP client plugin for DeepSeek Harness.

The shipped @deepseek-ai/dsh-mcp-client connects to every configured server and discovers its tools during plugin activation. With several servers (or a large one such as godot-mcp) that makes every harness start wait for all of them, even when the session never uses those tools.

dsh-lazy-mcp keeps the same model-facing tool names (mcp__<server>__<tool>) but moves the connection to the first tool call:

Startup What happens
First ever start (no cache) bootstrap: eager connects once, registers the tools and writes the manifest. background does the same without blocking boot.
Every later start Reads the manifest from disk, registers the same tools/schemas, and starts no process and no socket.
First mcp__… call Connects, refreshes the manifest, swaps stale registrations, then forwards the call.
MCP resource request Connects on demand; resources stay lazy as well.

Measured against godot-mcp (14 tools, ~5 KB of schemas):

  • first run, connect + tools/list + cache write: ~180–220 ms
  • cached start, 14 tools registered, server not started: ~0.5 ms
  • real DSH boot with a warm cache: no [SERVER] Using Godot… line, i.e. the MCP process was never spawned

Install

Install it from the dshmarket catalog: open Plugins → Market, search for dsh-lazy-mcp, and click Install. The market pins the current commit, so the install is reproducible and later updates are offered in the same list.

Without the catalog, add the GitHub source directly:

dsh plugin --profile main add github:kvnis/dsh-lazy-mcp

Electron app (owns the desktop profile): Plugins → Add plugin → Install a third-party plugin, paste github:kvnis/dsh-lazy-mcp, install, then switch the bundle on if it is not already enabled. The CLI refuses dsh plugin --profile desktop … with “profile desktop is managed exclusively by the Electron application”.

For development, a checkout can be installed instead (git clone https://github.com/kvnis/dsh-lazy-mcp ~/GitHub/dsh-lazy-mcp && npm ci --omit=dev, then add ~/GitHub/dsh-lazy-mcp as the plugin). That produces a link: dependency, which the market lists but does not offer updates for.

The Plugins page lists profile bundles, not individual MCP rows: the package shows up under Installed plugins as dsh-lazy-mcp, and the example mcp-godot-lazy row stays disabled until you enable it, so installing starts nothing.

The package declares a bundle patch with one disabled example row, so installing it does not start anything. Do not keep an existing @deepseek-ai/dsh-mcp-client row for the same serverName: both would register the same mcp__<server>__<tool> names. Remove or disable the eager row, then enable and configure this one in the active profile's cordis.patch.yml (~/.dsh/profiles/desktop/cordis.patch.yml for the Electron app, ~/.dsh/profiles/main/cordis.patch.yml for the CLI profile) (a patch replaces the whole config, so include it):

- id: mcp-godot-lazy
  disabled: false
  config:
    serverName: godot
    transport: stdio
    command: node
    args:
      - ~/GitHub/godot-mcp-157/build/index.js
    cwd: ~/GitHub/godot-mcp-157
    env:
      GODOT_PATH: ~/bin/godot
    toolCallTimeoutMs: 90000
    bootstrap: eager

env entries are merged over the scrubbed parent environment and are part of the cache key, so editing them re-discovers the tool list on the next start.

The active configuration points at the 157-tool fork (godot-mcp-157); the older 14-tool godot-mcp checkout is kept only as a rollback target. Build a server checkout first:

git clone https://github.com/Coding-Solo/godot-mcp.git ~/GitHub/godot-mcp-157
cd ~/GitHub/godot-mcp-157 && npm install && npm run build

Then start the harness normally. The first start connects to Godot MCP and writes ~/.dsh/lazy-mcp/godot.json; every start after that is lazy. Restart once after the first boot if you want to observe the warm start.

You can also add a row for a streamable-HTTP server:

- insert:
    - id: mcp-remote-lazy
      name: dsh-lazy-mcp
      config:
        serverName: remote
        transport: streamable-http
        url: https://example.com/mcp
        headers:
          Authorization: !!js `Bearer ${process.env.REMOTE_MCP_TOKEN}`

Configuration

Field Default Meaning
serverName required [A-Za-z0-9_-]{1,32}; prefixes every tool name
transport required stdio or streamable-http
command, args, env, cwd — stdio server; environment is the scrubbed parent env plus env
url, headers — streamable-HTTP server
cache true set false to disable the manifest and always bootstrap eagerly
cacheFile <cacheDir>/<serverName>.json explicit manifest path
cacheDir $DSH_HOME/lazy-mcp manifest directory
bootstrap eager what to do when no manifest exists: eager, background, or off
failOnStartupError true fail activation when the eager first-run bootstrap fails
toolCallTimeoutMs 60000 per tools/call and resource request
maxInstructionBytes 32768 reject oversized server instructions
tools all allowlist of exact MCP tool names to expose; unlisted tools stay reachable through the escape hatch
escapeHatch true with tools, also register mcp__<server>__call to invoke any tool by name
schemaMode full compact strips schema titles, property descriptions, defaults, examples and bounds, and collapses descriptions to one line

A manifest is invalidated automatically when the server name, transport, command/args, cwd, URL, or the set of env/header names changes.

Token budget profiles

Every registered tool costs its name, description and input schema on each model request, whether or not it is called. A server with 157 tools can spend tens of thousands of tokens per turn doing nothing. Two independent levers shrink that:

  • tools registers only the listed tools. Point it at the handful a task actually needs.
  • schemaMode: compact keeps the same tools but trims the schema text roughly in half.

Measured against the 157-tool Godot server: the full profile registers 79,812 characters of name + description + input schema (~20k tokens); compact registers 48,050 (~12k), a 40 % cut with the same 157 callable tools. The allowlist is the bigger lever — an unregistered tool costs nothing at all.

- id: mcp-godot-lazy
  disabled: false
  config:
    serverName: godot
    transport: stdio
    command: node
    args: [~/GitHub/godot-mcp-157/build/index.js]
    cwd: ~/GitHub/godot-mcp-157
    tools:
      - launch_editor
      - run_project
      - get_debug_output
      - stop_project
      - get_scene_tree
      - game_screenshot
      - game_get_logs
    schemaMode: compact
    escapeHatch: true

Unlisted tools stay reachable through one meta-tool, mcp__godot__call, so a narrowed profile is not a dead end — the model can still run any tool by name when the task turns out to need it:

{ "name": "game_set_camera", "arguments": { "position": { "x": 0, "y": 2, "z": 6 } } }

The escape hatch costs one small schema instead of one per tool. Set escapeHatch: false for a hard allowlist. Tool names are matched exactly; get the real list from ~/.dsh/lazy-mcp/<serverName>.json after a discovery, or from the harness tool panel.

Because tools and schemaMode only change how cached tools are presented, they do not invalidate the manifest: editing them costs a restart, not a reconnect.

How it works

  • Manifest cache. After a successful tools/list, the raw tools (name, description, input schema, output schema) and server instructions are written atomically with mode 0600. On the next activation these are registered as thin proxies.
  • Proxy execution. Each proxy waits for one shared connection attempt, then calls tools/call on the real server. A closed transport clears the client, so the next call reconnects and re-syncs.
  • Live refresh. A tools/list_changed notification schedules a serialized re-sync; registrations are added, replaced, or removed before the next model request. The manifest is rewritten.
  • Resources and instructions. Instructions are served from the cache until a connection refreshes them. The MCP resource provider connects only when a resource is requested.
  • Failure containment. A tool whose schema is rejected by the harness is skipped with a warning instead of failing the whole server. A failed cached-start call leaves the cached tools registered for the next attempt.

Troubleshooting

initial connection or tool discovery failed ... Connection closed on first enable almost always means the configured command/args do not start the server. Check that args[0] is the built build/index.js and that the path is absolute. The current version names a missing absolute entry directly; restart the harness (or toggle the component) after fixing the config.

The first successful connection writes ~/.dsh/lazy-mcp/<serverName>.json. To force a fresh discovery, delete that file and restart. With DSH_LAZY_MCP_DEBUG=1, the plugin writes activation decisions to stderr.

Deliberate limits

  • First run is eager by default. A lazy client cannot know tool schemas before connecting, so the manifest has to come from somewhere. Use bootstrap: background to avoid even that first-run wait, at the cost of tools appearing shortly after boot.
  • Cached schemas can be stale. They are refreshed on the first call or on tools/list_changed; a tool removed upstream may fail once before the refresh lands.
  • No reconnect supervisor. Unlike the shipped client, this plugin does not keep retrying in the background. A dropped connection is retried by the next tool call.
  • Image/audio MCP blocks become text markers. The shipped client stores them as durable attachments; this plugin's smaller adapter does not.
  • No per-server duplicate-name reservation across agent scopes. Registering two instances with the same serverName in one composition produces a tool-name conflict warning; use distinct names.
  • Task-based tool execution is rejected, matching the shipped bridge's limitation.

Verification

There is no test suite in this repo. What is checked, and how:

node --check index.js            # syntax
node -e "import('./index.js').then(m => console.log(Object.keys(m)))"
                                 # exports load: apply, normalizeConfig, compactSchema, compactText

An end-to-end smoke test runs the real apply() path against a throwaway stdio MCP server: it asserts that an allowlist exposes exactly the listed tools plus mcp__<server>__call, that the escape hatch forwards arguments and rejects a missing name or a non-object arguments, that schemaMode: compact keeps required while dropping property descriptions, and that the manifest is written under cacheDir. The same harness is what produced the 157-tool numbers above (probe the real server with schemaMode: full, then compact; call the disposer apply() returns so the process exits).

For a real DSH boot check:

# cold: connects once, writes the cache
DSH_LAZY_MCP_DEBUG=1 node apps/cli/lib/bin.js --profile main
# warm: logs "157 tools restored from cache" and no godot server output
DSH_LAZY_MCP_DEBUG=1 node apps/cli/lib/bin.js --profile main

DSH_LAZY_MCP_DEBUG=1 writes activation decisions to stderr; it does not change behavior.

—/ 5

No ratings yet

Verified DSH bundle

Commit c8f13bd05c92

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout