dsh-mcp-manager
MCP server manager for DeepSeek Harness: OAuth/stdio servers, workspace isolation, and an opt-in on-demand tool broker
同名パッケージの別リポジトリ
インストール
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:hyqhyq3/dsh-mcp-managerドキュメント
README 全文を読む ↗Usage
- Open Settings → MCP in the DSH web UI.
- + Add MCP server (and later 编辑 / Edit to change it):
- Scope (作用域):
user— a global server available in every workspace; orworkspace— a server bound to one workspace (its config lives in that workspace's.dsh/dshmm/mcp.json). Pick the workspace from the second dropdown. - HTTP: name (becomes the
mcp____*prefix), URL, auth mode (OAuth, static token, or no auth), and optional headers (headersdirect values,headerEnvvalues read from env vars). In OAuth mode you can additionally pre-register the client: Client ID, Client secret environment variable, and Scopes (all optional; leave Scopes empty to use what the server advertises). Client name sets theclient_nameused for dynamic registration (defaultdsh-mcp-manager-) — needed when the provider only registers pre-approved names. - stdio: name, command (e.g.
npx), args (one per row), env vars (key/value rows), and optional working directory.
- Scope (作用域):
- OAuth servers: click 去认证 (Authenticate) → the browser opens the server's login page → after consent you are redirected back and the tools are registered immediately. If the authorization server advertises a
registration_endpointthe plugin registers itself (RFC 7591) and you can leave Client ID empty. If it does not — GitHub'shttps://github.com/login/oauthis the common case — register an OAuth App with the provider, paste its Client ID (and the name of the environment variable holding its secret, if it is a confidential client), set Scopes to what the MCP endpoint needs (e.g.repo read:org), and whitelisthttp://127.0.0.1: /mcp-manager/callback/as the redirect URI there. Without a Client ID the attempt fails with an explanation rather than a bareHTTP 404, and the Logs panel lists every URL that was tried. - Static-token servers: enter the name of an environment variable that holds the token (e.g.
MCP_BEARER_TOKEN) — the token itself is never written to disk; stdio servers spawn and connect immediately on save. - No-auth servers: pick No auth (server needs no credentials) — the plugin sends no
Authorizationheader and connects on save. Use it for an endpoint that authenticates nothing (for example a localhttp://127.0.0.1:9316/mcp). - Optional: turn on On-demand MCP tool calls at the top of the page. The setting is profile-wide, persists across restarts, and affects existing sessions on their next request.
Status badges: connected (N tools) / needs-auth / authorizing / error / disabled. Buttons: authenticate, edit, enable/disable (switch), delete. A non-empty error is also shown as a one-line summary under the server name while the card is collapsed, and the expanded card offers 日志 / Logs. Disable unregisters that server's tools and drops its connection (config and OAuth tokens persist); Enable reconnects without re-authenticating. Disabled servers stay dormant across restarts. The toggle is global: it affects every session in this profile; the workspace view's per-row switch turns a server off for one workspace only. State persists at ~/.dsh/mcp-manager.json (server configs + OAuth client registrations + tokens; static tokens are referenced by env-var name, not stored).
Tool-call timeouts
Since v0.14.0, the add/edit form has an optional Tool-call timeout (ms) for
both HTTP and stdio servers, in user and workspace scope. For example, 600000
allows a simulation tool to run for ten minutes. Clear the input to inherit the
default. The same field works in /.dsh/dshmm/mcp.json:
{
"mcpServers": {
"simulation": {
"type": "http",
"url": "http://127.0.0.1:9316/mcp",
"authMode": "none",
"timeout": 600000
}
}
}
Resolution order: server timeout, then the plugin row's
config.toolCallTimeoutMs, then 60000 milliseconds. Values must be finite and
at least 1000; numeric strings are accepted and fractions are floored. Invalid,
empty, or missing values are treated as unset. PUT clears the server override
when timeout is omitted or invalid. Disabling a server preserves its override.
The override applies only to tools/call over stdio, Streamable HTTP, and legacy
HTTP-with-SSE. Initialization, tool listing, and notifications keep their existing
60-second defaults. Timeout errors include the elapsed budget; a client timeout
does not guarantee that a remote server has stopped its work. A surrounding
Code/PTC runtime may impose its own deadline.
What the agent sees
With on-demand mode off (the default), every connected server's tools appear as first-class tools, e.g. for a server named odin:
mcp__odin__search_tools mcp__odin__describe_tool
mcp__odin__execute_tool mcp__odin__list_tool_scopes
Tool results are projected back as native DSH content blocks; MCP isError results surface through the registry's error path. Image blocks ({ type: 'image', data, mimeType }) are never forwarded raw — a raw MCP image block has no attachment, so it would crash the session on the next turn. Rendering is text-first: when the routed model declares image input, the host stores the image in DSH's durable attachment store and the block becomes a real { type: 'image', attachment }; in every other case (no attachment store, model without image input, non-canonical base64, or a media type outside PNG/JPEG/WebP/GIF) the image degrades to an [image unavailable: …] text placeholder. Text blocks are preserved in order, and the same sanitizing applies to results returned through mcp_execute_tool.
With on-demand mode on, a Native-mode agent sees only these three MCP broker tools:
mcp_search_tools({ query?, server?, limit? })is a pure-lexical ranked search (no embedding model, no network): NFKC normalization, camelCase/CJK tokenization with a small English stemmer, a built-in bilingual alias table (e.g.查日志→log/logs), BM25 over the tool/server/description fields, and a bounded edit-distance fallback for typos. Omitqueryto list the catalog (optionallyserver-filtered). It returns at mostlimitmatches (default 10, clamped to 1-50) plustotal, the number of results before truncation.mcp_describe_tool({ name })returns the exact registered description and input schema for one tool visible in that session.mcp_execute_tool({ name, arguments })executes any currently visible MCP tool through the normal DSH tool pipeline. Callingdescribefirst is recommended but not required.
Raw mcp__* names are kept out of the model request and direct calls to them are denied; only the nested dispatch owned by mcp_execute_tool is allowed. On DSH 0.2.2 and newer the exclusion is the registry's deferLoading marker: the harness preserves it through schema projection and the provider adapter decides whether the request omits the tool (the DeepSeek adapter forwards defer_loading, the pi-ai adapter filters it out, and routes without tool-update support send the schema unchanged). The definitions stay registered and executable, so mcp_execute_tool still dispatches them. 0.2.1-rc.5 and older pass the tool list through the system-prompt waterfall and are filtered there. Search, describe, and execute all resolve the calling agent's live registry view, so workspace isolation and exclude masks remain effective.
Code/PTC/both agents retain their full session-visible MCP catalog, even with
this switch on. On 0.2.2 the broker is not callable from inside a run_code
program (the PTC executor only makes run_code directly callable), so those
programs call the mcp__* tools directly; on 0.2.1-rc.5 the broker stays
callable. Either way the generated SDK is not shrunk. DSH 0.2.0-rc.2 names its modes native/ptc/both; 0.2.1-rc.2 supports
native/ptc. See issue #18 investigation and isolated verification
for the checked interfaces, safe fallbacks, context-cost measurements and test
commands. The local 0.13.3 candidate also keeps this conservative fallback when a custom
prompt empties tools:sdk but still exposes run_code.
Workspace isolation
Global servers (added in Settings → MCP) are visible in every workspace. Use the workspace dropdown at the top of the Settings → MCP page to switch between "global" and a specific workspace; when a workspace is selected you see both its own servers and the global servers, each with a per-workspace Enabled in this workspace switch. Workspace servers live in /.dsh/dshmm/mcp.json (Claude/Codex-style):
{
"mcpServers": {
"filesystem": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] },
"unity-mcp": { "type": "http", "url": "http://localhost:8090/", "authMode": "static", "tokenEnv": "UNITY_MCP_TOKEN" },
"local-mcp": { "type": "http", "url": "http://127.0.0.1:9316/mcp", "authMode": "none" }
},
"disabled": ["github"]
}
- Workspace servers can be added / edited / deleted in the UI for workspaces registered in DSH (the + button while a workspace is selected writes to that workspace's
mcp.json). Hand-editing the file also works — creating or changing it is hot-reloaded. Invalid JSON is shown as an error while the last valid live configuration stays active. typedefaults tohttp; a stdio server'scwddefaults to the workspace root.headers/headerEnv/env/argsfollow the same shapes as the Settings form.- A workspace server's tools register only into sessions whose working directory resolves to that workspace; another workspace's agents never see them. Global servers stay visible everywhere unless masked.
disabledis the per-workspace off switch and covers both global and workspace-local names. A disabled server registers no tools and never connects in this workspace; other workspaces and the profile-wide state are untouched. Toggle it with the switch on any row in the workspace view — a disabled row stays visible (dimmed,disabled in this workspace) so re-enabling is one click. Disabling keeps the declaration and any OAuth token, so re-enabling needs no re-authentication. Hand-editing the list is hot-reloaded; unknown names are ignored (and logged) rather than failing the file. Renaming or deleting a workspace server keeps the list in sync — the new name inherits the off state, and a deleted name is removed so re-adding it starts enabled (a name shared with a global server keeps its entry, because it may be that server's mask).exclude(legacy) still masks global servers in this workspace through the same per-agent restriction; the UI folds a name intodisabledon the next toggle and either list masks it.POST /mcp-manager/api/workspaces/server-state { path, name, enabled }backs the switch and preserves unknown keys in the file.serverNamemust be unique across global + all workspace sources; a later duplicate is flagged as a conflict and skipped (shown in the UI). A conflict is re-derived when the blocking global server is deleted or renamed, so the workspace entry connects on its own instead of waiting for amcp.jsonedit.- Config is re-read on each new session and hot-reloaded via a file watcher.
- Workspace connection failures retry automatically after 3s, 6s, 12s, up to 60s; a successful connection resets the delay. SSE session loss reconnects immediately and refreshes tools for existing agents. Credential-gated servers remain
needs-auth. Editing/deleting a server, closing its last session, or unloading the plugin cancels pending retries. - Workspace Logs work for every row, not just loaded ones: ids are deterministic (
ws-), so a row listed from disk keeps the same id after it is loaded, andGET /mcp-manager/api/servers/:id/logsreturns 200 for loaded, conflict and not-yet-loaded rows alike.POST .../connectreconnects loaded rows, answers 409 for a conflict row (server name is taken by another server), and 404 when nothing is loaded for that id. Logs retain redacted failure causes (such asECONNREFUSED) across config rescans; deleting the server clears them.
Recovery regression tests run with node --test test/workspace-reconnect.test.js. For an opt-in real Loader smoke, install @deepseek-ai/dsh@0.2.1-rc.2 into a disposable npm prefix, then run node scripts/verify-real-dsh.mjs . The script creates and deletes its own temporary profile and workspace, uses loopback MCP only, and submits no model request. Verified on macOS / Node 24.12.0; Windows remains unverified.
- Workspace servers support stdio, HTTP static-token (
tokenEnv), HTTP OAuth (the same discovery + PKCE + dynamic client registration flow as global servers, including the optionalclientId/clientSecretEnv/clientName/scopefields in the hand-written config), and HTTP no-auth (authMode: "none", noAuthorizationheader). Workspace OAuth tokens persist in~/.dsh/mcp-manager.json(never in the declarativemcp.json); each workspace server row exposes a 去认证 / Authenticate button for OAuth servers.