Back to home@felix-lj-ct

dsh-mcp-live-status

Live MCP server status in the DeepSeek Harness conversation composer — see which MCP servers are actually connected before you hit send.

Stars
0
Language
JavaScript
Created
Aug 28, 2026
Updated
Aug 28, 2026

Introduction

dsh-mcp-live-status

English | 中文

See which MCP servers are actually connected — in the composer, before you hit send.

A DeepSeek Harness plugin that puts a live MCP status pill in the conversation composer's tool row, next to the access-mode and model controls.

The MCP status pill in the composer tool row

Why

DSH's settings page can tell you an MCP plugin mounted. It cannot tell you the server answered.

@deepseek-ai/dsh-mcp-client defaults to failOnStartupError: false, so a server whose transport never connected still reaches Cordis fiber state ACTIVE. It looks healthy everywhere in the UI. You find out it is dead when the agent tries to call one of its tools, mid-task.

This plugin closes that gap by checking the one thing that actually proves a handshake happened: tool registration. mcp-client only registers mcp__<serverName>__<toolName> on ctx.tools after connect() and listTools() have both succeeded. So the status is a join of two sources — what the Loader says is configured, against what the tool registry says arrived.

Loader entryRegistered toolsShown as
fiber active> 0🟢 Connected
fiber active0🟡 Up, not connected ← the one nothing else surfaces
fiber pending / loading🔵 Starting
fiber failed🔴 Mount failed
disabled⚪ Disabled (hidden by default)

Install

dsh plugin --profile web add dsh-mcp-live-status

Or straight from source, if you prefer not to go through npm:

dsh plugin --profile web add github:felix-lj-ct/dsh-mcp-live-status

Then restart the profile:

dsh --profile web

The pill appears in the composer tool row. If no MCP servers are configured, it renders nothing at all and costs no layout.

What it looks like

Healthy — no denominator, because a denominator would carry no information
  [+] [⏱ Full access ⌄] [● MCP 4]        [model ⌄] [↑]

Degraded — the denominator appears exactly when something is wrong
  [+] [⏱ Full access ⌄] [● MCP 3/4]      [model ⌄] [↑]

Clicking the pill opens a read-only panel listing every server with its transport, tool count, and — when something is off — the reason:

┌──────────────────────────────────┐
│ MCP servers                   ⟳  │
├──────────────────────────────────┤
│ ● atlassian     stdio    31 tools │
│ ● mongodb-qa    stdio    20 tools │
│ ● broken-probe  stdio  Up, not connected │
├──────────────────────────────────┤
│ 3 configured · 2 connected        │
└──────────────────────────────────┘

Configuration

Optional. Defaults are fine for most people.

- id: dsh-mcp-live-status
  name: dsh-mcp-live-status
  config:
    pollIntervalMs: 10000   # 0 disables polling (mount + manual refresh only)
    showDisabled: false     # include Loader entries that are disabled

Polling only runs while the browser tab is visible.

Permissions and risk

Read-only. This plugin has no way to start, stop, reload, or reconfigure an MCP server — managing servers stays with the settings page.

SurfaceWhat it does
ctx.loaderReads the configured plugin tree (read-only iteration)
ctx.toolsReads registered tool names only; never calls a tool
ctx.webServerServes one JSON route, GET /dsh-mcp-live-status/status
NetworkNone outbound. The browser half fetches only that local route.
StorageNone. No cache, no history, no files written.

On secrets. MCP servers are routinely launched with credentials on the command line (--connectionString mongodb+srv://user:pass@host, --token …). Because the status payload is fetched by a browser, argument values are filtered before they leave the host: anything containing a URI scheme or user:pass@ userinfo, anything following a flag named like a secret (pass/token/key/secret/auth/conn/dsn/uri/url), any opaque blob over 24 characters, and anything over 40 characters is dropped. env is never read at all. HTTP transports keep only origin + pathname, so query strings and userinfo never ship.

The result is recognisable but not exploitable — npx -y mongodb-mcp-server --readOnly rather than the connection string.

This filter is heuristic. If you run a server whose plain arguments are themselves sensitive, set showDisabled: false and review what the route returns before exposing DSH beyond localhost:

curl 127.0.0.1:3080/dsh-mcp-live-status/status

Compatibility

  • DeepSeek Harness 0.1.0-rc.7 (developed and verified against this version)
  • Profile: web (the plugin targets the browser UI; platform: web)
  • Requires webServer. loader and tools are read opportunistically through ctx.reflect.get() — a profile missing either still boots, it just reports less.
  • No dependency on dsh-typert-loader or dsh-api-gateway.

Known limitations

  • Needs a session. conversation.input.left is session-scoped and the shell passes it no zone until a session exists. In practice the pill is visible from the new-session screen onward, because picking a workspace creates the session — but on a truly session-less screen it renders nothing.
  • Polling, not push. mcp-client emits no status events, so there is nothing to subscribe to. The readout can be up to one interval stale.
  • no-tools is inference, not a probe. A server that genuinely publishes zero tools is indistinguishable from one that never connected. Both are amber. The plugin never opens its own MCP connection to check.
  • Counts tools, not health. A server that connected and then went silent keeps its last registered tool generation until mcp-client's reconnect budget runs out.

Development

npm install
npm run build          # tsc — host half only; the browser half is plain JS
dsh plugin --profile web add ./dsh-mcp-live-status
dsh --profile web --dump-config | grep -A2 dsh-mcp-live-status

dist/ is committed on purpose. pnpm blocks a git dependency's prepare script by default, and the allowBuilds key it asks for is pinned to the commit hash — so building on install would make every user paste a new key on every release. Run npm run build before committing a change to src/.

The two halves are independent:

  • src/index.tsdist/index.js — Node side; collects status, serves the route.
  • lib/client.js — browser side; hand-written CJS factory, no build step, no JSX. The module loader discovers it from exports["./client"] + dsh.client and serves it at /plugins/dsh-mcp-live-status/client.js.

To reproduce the amber state, add a server pointing at a command that does not exist and leave failOnStartupError: false.

License

MIT