hyqhyq3/dsh-mcp-manager ↗★ 31

dsh-mcp-manager

MCP server manager for DeepSeek Harness: OAuth/stdio servers, workspace isolation, and an opt-in on-demand tool broker

패키지
dsh-mcp-manager
호환성
미검증
버전
0.17.1
라이선스
MIT
최근 업데이트
2026. 11. 9.

같은 패키지 이름의 다른 저장소

설치

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:hyqhyq3/dsh-mcp-manager

Usage

  1. Open Settings → MCP in the DSH web UI.
  2. + Add MCP server (and later 编辑 / Edit to change it):
    • Scope (作用域): user — a global server available in every workspace; or workspace — 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 (headers direct values, headerEnv values 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 the client_name used for dynamic registration (default dsh-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.
  3. 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_endpoint the plugin registers itself (RFC 7591) and you can leave Client ID empty. If it does not — GitHub's https://github.com/login/oauth is 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 whitelist http://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 bare HTTP 404, and the Logs panel lists every URL that was tried.
  4. 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.
  5. No-auth servers: pick No auth (server needs no credentials) — the plugin sends no Authorization header and connects on save. Use it for an endpoint that authenticates nothing (for example a local http://127.0.0.1:9316/mcp).
  6. 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. Omit query to list the catalog (optionally server-filtered). It returns at most limit matches (default 10, clamped to 1-50) plus total, 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. Calling describe first 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.
  • type defaults to http; a stdio server's cwd defaults to the workspace root. headers / headerEnv / env / args follow 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.
  • disabled is 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 into disabled on 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.
  • serverName must 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 a mcp.json edit.
  • 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, and GET /mcp-manager/api/servers/:id/logs returns 200 for loaded, conflict and not-yet-loaded rows alike. POST .../connect reconnects 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 as ECONNREFUSED) 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 optional clientId / clientSecretEnv / clientName / scope fields in the hand-written config), and HTTP no-auth (authMode: "none", no Authorization header). Workspace OAuth tokens persist in ~/.dsh/mcp-manager.json (never in the declarative mcp.json); each workspace server row exposes a 去认证 / Authenticate button for OAuth servers.