Skip to content

feat(serve): runtime MCP server add/remove (T2.8 #4514)#4552

Merged
wenshao merged 26 commits into
daemon_mode_b_mainfrom
feat/serve-t2.8-mcp-runtime-mutation
May 30, 2026
Merged

feat(serve): runtime MCP server add/remove (T2.8 #4514)#4552
wenshao merged 26 commits into
daemon_mode_b_mainfrom
feat/serve-t2.8-mcp-runtime-mutation

Conversation

@doudouOUC

@doudouOUC doudouOUC commented May 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes T2.8 from issue #4514. Adds a pair of mutate-gated HTTP routes that mutate the daemon's runtime MCP server registry without requiring a daemon restart.

  • POST /workspace/mcp/servers — add or replace a runtime MCP server entry. Idempotent on same name (replaces existing). Reuses existing WorkspaceMcpBudget enforce/warn/off modes.
  • DELETE /workspace/mcp/servers/:name — drop a runtime entry. Idempotent on missing names. If the name is also declared in settings.json, wasShadowingSettings: true informs the caller that the settings entry will return on restart.

Capability tag: mcp_server_runtime_mutation: { since: 'v1' } (always-on). Events: mcp_server_added and mcp_server_removed fan out to attached clients with originatorClientId stamping.

Tracking comment on issue: #4514 (comment)

Design rationale

  • Ephemeral persistence: matches /restart semantics; daemon restart re-reads settings.json cascade. Consumers wanting durability edit settings.json themselves.
  • Shadow over settings: POST overlays settings entries via runtime overlay map in Config.getMcpServers() cascade. DELETE drops the runtime overlay; settings entries return on restart.
  • Idempotent replace: same name + same fingerprint = no spawn churn (pool-level dedup). Same name + different fingerprint = drain old + spawn new, both share budget slot transiently.
  • Budget guard reuse: WorkspaceMcpBudget modes already model enforce / warn / off; replace doesn't consume new slot (same name).
  • Active refs on DELETE: pool drain semantics (mark draining, do not block existing refs); differs from /restart's kill-respawn since DELETE has no respawn target.

Full design doc: docs/superpowers/specs/2026-05-26-t2.8-mcp-server-runtime-add-remove-design.md (on the polished-gathering-finch worktree branch — link to be added once that PR opens). Implementation plan: docs/superpowers/plans/2026-05-26-t2.8-mcp-server-runtime-add-remove.md.

Architecture

HTTP layer (server.ts POST/DELETE /workspace/mcp/servers/...)
       → HttpAcpBridge.{add,remove}RuntimeMcpServer (bridge.ts)
       → ACP ext-method qwen/control/workspace/mcp/runtime-{add,remove} (acpAgent.ts)
       → McpClientManager.{add,remove}RuntimeMcpServer (mcp-client-manager.ts)
       → Config runtime overlay + WorkspaceMcpBudget + McpTransportPool
       ← typed errors → RequestError(data.errorKind) → sendBridgeError mapping
       ← success → publishWorkspaceEvent(mcp_server_{added,removed})

Wire contract additions

  • Routes: POST /workspace/mcp/servers, DELETE /workspace/mcp/servers/:name
  • Events: mcp_server_added (replaced/shadowedSettings/toolCount), mcp_server_removed (wasShadowingSettings)
  • Capability tag: mcp_server_runtime_mutation: { since: 'v1' }
  • Error kinds: mcp_budget_would_exceed (409), mcp_server_spawn_failed (502, with exitCode/stderr/timeout in body), invalid_config (400)
  • ACP ext-methods: qwen/control/workspace/mcp/runtime-add, qwen/control/workspace/mcp/runtime-remove
  • SDK types: DaemonRuntimeMcpAddRequest, DaemonRuntimeMcpAddResult (discriminated union), DaemonRuntimeMcpRemoveResult (discriminated union), MCPServerConfigShape, DaemonMcpServerAddedData, DaemonMcpServerRemovedData
  • SDK helpers: DaemonClient.addRuntimeMcpServer, DaemonClient.removeRuntimeMcpServer

Test plan

  • npm run typecheck clean across sdk-typescript, core, acp-bridge, cli
  • npm test --workspace packages/sdk-typescript — 653/653 pass
  • npm test --workspace packages/acp-bridge — 305/305 pass
  • npm test --workspace packages/core — T2.8 cases all green (1 unrelated anthropicContentGenerator flake)
  • npm test --workspace packages/cli — T2.8 cases all green (a few unrelated flakes in auth/AuthDialog/useAtCompletion, not regressions)
  • Lint clean on all touched source files
  • CI lint/format/build
  • Manual smoke (from spec; defer to reviewer):
    • Start daemon: qwen serve --port 18280 --token foo
    • POST stdio MCP server (e.g. npx -y @modelcontextprotocol/server-everything) → GET /workspace/mcp reflects it
    • POST same name with new command → response replaced: true
    • DELETE → GET /workspace/mcp clears
    • Restart daemon → settings entries return, runtime entries gone
    • 2 SSE consumers → POST → both receive mcp_server_added with originatorClientId
    • Budget --mcp-budget=2 --mcp-budget-mode=enforce + POST a 3rd → 409
    • Budget warn → 200 + mcp_guardrail_events
    • --require-auth + no token POST → 401

Backward compatibility

  • New always-on capability tag — older SDKs ignore unknown keys
  • New event types — asKnownDaemonEvent → undefined on older SDKs (additive, no protocol bump)
  • Config.addMcpServers post-init guard unchanged; new addRuntimeMcpServer/removeRuntimeMcpServer use a separate runtimeMcpServers overlay map
  • excludedMcpServers mechanism unchanged — runtime entries pass through isMcpServerDisabled like everything else

Commit history

15 TDD commits + 1 typecheck fix, mirroring the implementation plan:

  1. 1bc24d784 feat(sdk): mcp_server_added event type
  2. 5a88394c0 feat(sdk): mcp_server_removed event type
  3. 64b7b1b52 feat(sdk): runtime MCP request + result types
  4. 40a033079 feat(core): Config.addRuntimeMcpServer / removeRuntimeMcpServer
  5. 3003fb9c3 docs(core): tighten Config.addRuntimeMcpServer JSDoc wording
  6. 88b64cb62 feat(core): runtime MCP overlay in getMcpServers cascade
  7. 878571bc3 feat(core): McpClientManager runtime add/remove + budget/pool wiring
  8. fdfcbc970 feat(acp-bridge): T2.8 error kinds
  9. 3fd0dfcfb feat(acp-bridge): qwen/control/workspace/mcp/runtime-{add,remove} ext-methods
  10. 6cd691aef feat(acp-bridge): host-side {add,remove}RuntimeMcpServer + event fan-out
  11. 7f338fcc2 feat(serve): POST /workspace/mcp/servers route
  12. 4c6d0b205 feat(serve): DELETE /workspace/mcp/servers/:name route
  13. 73388f347 feat(serve): mcp_server_runtime_mutation capability tag
  14. 8d9eb82c8 feat(sdk): DaemonClient runtime add/remove helpers
  15. 3a8f766d7 docs(serve): document runtime MCP routes
  16. d31a2a8ac fix(test): index-signature property access

Tracking issue: #4514

🤖 Generated with Qwen Code


📝 描述准确性更新(2026-05-31,作者自查)

补充:运行时 add MCP 会静默剥离 env/headers/trust/cwd/oauth 等字段,需密钥/鉴权头的 MCP server 添加后不可用且无回传告警。

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds end-to-end support for runtime (non-persistent) MCP server add/remove in qwen serve, exposing new strict-gated HTTP routes that mutate a daemon-side runtime overlay without requiring a restart, plus corresponding ACP bridge plumbing, SDK types/helpers, events, and tests.

Changes:

  • Add POST /workspace/mcp/servers and DELETE /workspace/mcp/servers/:name routes (strict mutation-gated), including capability tagging and error-kind → HTTP status mapping.
  • Implement runtime MCP overlay + lifecycle in core (Config overlay, McpClientManager add/remove, new error classes).
  • Extend SDK surface with request/result types, helpers, and new SSE event types (mcp_server_added / mcp_server_removed) + drift-insurance tests, and document the new feature.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
packages/sdk-typescript/test/unit/DaemonClient.test.ts Adds unit tests for new SDK helper methods for runtime MCP add/remove.
packages/sdk-typescript/test/unit/daemon-public-surface.test.ts Adds drift-insurance tests for new daemon events and runtime MCP types.
packages/sdk-typescript/src/index.ts Re-exports new runtime MCP types and known-event roster from the public SDK entry.
packages/sdk-typescript/src/daemon/types.ts Introduces runtime MCP request/result types and config shape; extends daemon error kinds.
packages/sdk-typescript/src/daemon/index.ts Re-exports new events/types from daemon module.
packages/sdk-typescript/src/daemon/events.ts Adds new event types + validators and exports DAEMON_KNOWN_EVENT_TYPE_VALUES.
packages/sdk-typescript/src/daemon/DaemonClient.ts Implements addRuntimeMcpServer and removeRuntimeMcpServer helpers.
packages/core/src/tools/mcp-errors.ts Adds typed core error classes for budget/config/spawn failures.
packages/core/src/tools/mcp-client-manager.ts Implements runtime MCP add/remove lifecycle, budget enforcement, and pool wiring.
packages/core/src/tools/mcp-client-manager.test.ts Adds tests covering runtime MCP add/remove behavior and error paths.
packages/core/src/config/config.ts Adds runtime MCP overlay map and overlays it in getMcpServers().
packages/core/src/config/config.test.ts Adds tests validating overlay behavior and invariants.
packages/cli/src/serve/server.ts Adds the new HTTP routes and extends sendBridgeError with runtime MCP error-kind mappings.
packages/cli/src/serve/server.test.ts Adds route-level tests for runtime MCP routes and new capability tag.
packages/cli/src/serve/capabilities.ts Adds mcp_server_runtime_mutation capability tag to the registry.
packages/cli/src/acp-integration/acpAgent.ts Adds ACP ext-method handlers delegating to McpClientManager with typed error mapping.
packages/cli/src/acp-integration/acpAgent.test.ts Adds tests for new ACP ext-method wiring and error data propagation.
packages/acp-bridge/src/status.ts Extends serve error-kind taxonomy and registers new control ext-method names.
packages/acp-bridge/src/status.test.ts Updates ordering tests for new error kinds.
packages/acp-bridge/src/bridgeTypes.ts Extends HttpAcpBridge interface for runtime MCP add/remove.
packages/acp-bridge/src/bridge.ts Implements bridge methods that round-trip ACP ext-methods and broadcast workspace events.
packages/acp-bridge/src/bridge.test.ts Adds bridge-level tests for runtime MCP methods and event fan-out behavior.
docs/users/qwen-serve.md Documents runtime MCP mutation routes, semantics, errors, and events.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/serve/server.ts Outdated
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/core/src/tools/mcp-client-manager.ts Outdated
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread docs/users/qwen-serve.md Outdated
Comment thread packages/sdk-typescript/src/daemon/types.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/cli/src/serve/server.ts Outdated
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/sdk-typescript/src/daemon/types.ts
Comment thread packages/sdk-typescript/src/daemon/events.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/cli/src/serve/server.ts Outdated
Comment thread packages/core/src/tools/mcp-client-manager.ts Outdated
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
@doudouOUC doudouOUC self-assigned this May 26, 2026
@doudouOUC

doudouOUC commented May 26, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed fix commit b9acdce26 (rebased) addressing all 5 Critical items from both review rounds:

# Fix Commit scope
C1 (spawn_failed diagnostics lost) Flattened ...err.details at ACP layer instead of nesting under data.details acpAgent.ts
C2 (removeRuntimeMcpServer missing cleanup) Added removeMcpToolsByServer + removeMCPServerStatus + stopHealthCheck (mirrors removeServer) mcp-client-manager.ts
C3 (503 unreachable, real path 404) Bridge now throws error with data.errorKind='acp_channel_unavailable' instead of SessionNotFoundError bridge.ts
C4 (clientId ?? '' → unlogged 500) POST/DELETE now require X-Qwen-Client-Id header — return 400 missing_client_id when absent server.ts
C5 (standalone replace budget leak) Removed releaseSlotName in standalone replace — budget slot carries over to new entry mcp-client-manager.ts

Not taking (with reasoning):

  • C6 (3 untested paths): standalone McpClient path is pre-existing infra not exercised by T2.8's intended deployment (pool mode is the live mode on daemon_mode_b_main). Will add standalone coverage in a separate hardening PR if needed.
  • Suggestions (replace-order, exitCode extraction): real correctness-adjacent items — tracking as follow-up debt, not blocking this PR's core functionality.
  • Style/observability suggestions (logging, dead catch, spread leak, regex, type, reducer): acknowledged as debt; not worth the churn in this PR cycle.

Ready for re-review when you have a chance.

Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/cli/src/serve/server.test.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
@doudouOUC doudouOUC force-pushed the feat/serve-t2.8-mcp-runtime-mutation branch from b9acdce to 6518bb6 Compare May 27, 2026 06:54

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Runtime-added MCP servers inherit the daemon's full environment (mcp-client.ts:1637: { ...process.env, ...(mcpServerConfig.env || {}) }), including QWEN_SERVER_TOKEN, API keys, and other sensitive variables. Settings.json servers are operator-trusted; runtime-added servers are caller-trusted (any bearer token holder). A client can add { "command": "sh", "args": ["-c", "env > /tmp/exfil"] } — the spawn fails (not a valid MCP server) but the env has already been written to disk. Consider stripping sensitive daemon env vars for runtime-added servers.

5 Critical items from R4 (replace flow toolRegistry/stopHealthCheck, pool onFailed, standalone startHealthCheck, stopTimedOut gate) remain open from the previous review round — not re-posted.

— qwen3.7-max via Qwen Code /review

Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts Outdated
Comment thread packages/core/src/tools/mcp-client-manager.test.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/acp-integration/acpAgent.test.ts
Comment thread packages/cli/src/serve/server.test.ts
@doudouOUC doudouOUC force-pushed the feat/serve-t2.8-mcp-runtime-mutation branch from 6518bb6 to 35744b8 Compare May 27, 2026 13:49
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/core/src/tools/mcp-client-manager.ts
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Fix commit a2cf5e771 addressing rounds 4-6 Critical items:

# Action Detail
:2746 replace missing toolRegistry cleanup Fixed Added removeMcpToolsByServer + stopHealthCheck in pool replace path
:2749 replace missing stopHealthCheck Fixed Added before disconnect in standalone replace path
:2811/:2810 catch missing toolRegistry cleanup Fixed Added removeMcpToolsByServer + stopHealthCheck in spawn-failure catch
:2488 trust field passthrough Fixed Strip trust from config before forwarding to McpClientManager
:2808 catch releases budget Pushed back Correct behavior for failure path (both old+new dead → slot must free)
:2779 pool onFailed listener Deferred Self-heal gap, not correctness bug; follow-up scope
:2765 concurrent POST+DELETE race Deferred Per-name mutex is scope expansion; documented as known limitation
:3533 budget release test coverage Deferred Test debt; hardening PR

Ready for re-review.

Comment thread packages/sdk-typescript/src/daemon/events.ts
Comment thread packages/sdk-typescript/src/daemon/events.ts
Comment thread packages/sdk-typescript/src/daemon/events.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/sdk-typescript/src/daemon/DaemonClient.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts Outdated
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/acp-bridge/src/bridge.ts
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Review Response — Rounds 5-7 (be466a1)

Fixed (15 items)

Comment File Fix
Missing /** JSDoc events.ts:627 Added opener
Incomplete ternary events.ts:1315 Added : undefined
Missing ); } events.ts:2377 Closed function body
SSRF/authProviderType acpAgent.ts:2488 Stripped authProviderType, includeTools, excludeTools, cwd
SDK timeout mismatch DaemonClient.ts:1170 Added timeoutMs param (default 330s)
Race: remove deletes new entry mcp-client-manager.ts:2865 Identity-check before delete
Transport leak in catch mcp-client-manager.ts:2812 Added client.disconnect()
Remove missing diagnostics cleanup mcp-client-manager.ts:2889 Added consecutiveFailures/isReconnecting/dropRefusalEntry
replaced:true for same-fingerprint mcp-client-manager.ts:2678 Changed to false
Array.isArray in ACP acpAgent.ts:2463 Added check
excludedMcpServers bypass mcp-client-manager.ts:2652 Added isMcpServerDisabled check
Missing mcp-client-update on failure mcp-client-manager.ts:2815 Added emit
exitCode never extracted mcp-client-manager.ts:2822 Extract from error object
Dead catch block bridge.ts:3383 Removed (all branches re-threw)
Remove error handling bridge.ts:3420 Added try/catch
Unbounded data spread server.ts:3562 Whitelisted fields
AddOk.transport type bridge.ts:3358 Narrowed to literal union
Zero logging mcp-client-manager.ts:2647 Added debugLogger.info at entry

Pushed back (3 items)

Comment Reason
Full per-name lock (concurrent POST+DELETE ghost) Partial fix via identity-check; full serialization is T2.9
Ownership enforcement on remove By design — T2.9 scope
Standalone replace budget orphan (:2805) Catch block already handles this path correctly

Deferred (3 items)

Comment Reason
Budget release test coverage Follow-up test item
McpServerSpawnFailedError mapping test Follow-up test item
invalid_config → 400 mapping test Follow-up test item

🤖 Generated with Qwen Code

@wenshao

wenshao commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Verification Report — PR #4552

Reviewer: wenshao
Date: 2026-05-28
Branch: feat/serve-t2.8-mcp-runtime-mutation @ be466a18a
Base: daemon_mode_b_main


Build & Typecheck

Workspace Result
sdk-typescript ✅ Pass
acp-bridge ✅ Pass
core ✅ Pass
cli ✅ Pass
webui ✅ Pass

Unit Tests

Workspace Files Tests Result
sdk-typescript 14 679/679 ✅ Pass
acp-bridge 7 326/326 ✅ Pass
core (T2.8 files) 2 278/278 ✅ Pass
cli (T2.8 files) 2 415/415 ✅ Pass

Lint

All T2.8 source files pass eslint --max-warnings 0: ✅

Manual Smoke Tests (daemon on localhost:18280)

# Scenario Result
1 POST stdio MCP server (@modelcontextprotocol/server-everything) ✅ 200, toolCount=14
2 GET /workspace/mcp reflects added server (status=connected)
3 POST same name + same fingerprint → idempotent (replaced=false, no respawn)
4 POST same name + invalid config → spawn_failed (502), original preserved
5 DELETE existing server → removed=true, wasShadowingSettings=false
6 GET /workspace/mcp after DELETE → servers=[]
7 DELETE non-existent → skipped=true, reason=not_present (idempotent)
8 POST without Bearer token → 401 Unauthorized
9 POST missing name field → 400 invalid_server_name
10 GET /capabilities includes mcp_server_runtime_mutation
11 POST with unregistered X-Qwen-Client-Id → 400 invalid_client_id

Budget Enforcement (unit-test verified)

Budget 409 (mcp_budget_would_exceed) path is validated in server.test.ts lines 3446-3461 via mock bridge. In http-bridge mode, budget env vars (QWEN_SERVE_MCP_CLIENT_BUDGET, QWEN_SERVE_MCP_BUDGET_MODE) are forwarded to the ACP child process via runQwenServe.ts — direct node serve bypasses this (by design, matching PR #14 architecture).

Notes

  • originatorClientId correctly stamped on all mutation responses
  • Error responses use structured JSON with code field for programmatic consumption
  • SSE event fan-out tested indirectly via unit tests (bridge.test.ts T2.8 block)
  • No regressions observed in existing server.test.ts (357 tests) or acp-bridge (326 tests)

Verdict

✅ Ready to merge — all automated checks pass, manual smoke tests confirm the wire contract, error handling is robust and well-structured.

Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/cli/src/acp-integration/acpAgent.test.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/cli/src/acp-integration/acpAgent.test.ts
wenshao
wenshao previously approved these changes May 28, 2026
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts
…T2.8 #4514)

Security:
- Strip `env` from runtime-added MCP server configs (prevents
  NODE_OPTIONS/LD_PRELOAD injection via HTTP body)

Correctness:
- Add `removeMCPServerStatus(name)` in spawn-failure catch block
  (prevents stale CONNECTING entry in status registry)

Hardening:
- Add name validation (charset + length) to ACP ext-method handlers
  for both add and remove (matches HTTP route validation)
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Review Response — Round 8 (66dc4ce)

Fixed (3 items)

Comment File Fix
env field not stripped (Critical) acpAgent.ts:2540 Added env to destructuring strip list
Stale status on spawn failure mcp-client-manager.ts:2831 Added removeMCPServerStatus(name) in catch
ACP name validation weaker than HTTP acpAgent.ts:2500 Added charset+length check to both add/remove handlers

🤖 Generated with Qwen Code

Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/core/src/config/config.ts
Comment thread docs/users/qwen-serve.md Outdated
Comment thread packages/sdk-typescript/src/daemon/DaemonClient.ts Outdated
Comment thread packages/acp-bridge/src/bridgeTypes.ts
…imeout (T2.8 #4514)

Security:
- Strip `oauth` and `headers` from runtime-added configs (prevents
  credential exfiltration via OAuth flow and header injection)
- Reject `__proto__`, `constructor`, `prototype` as server names
  (prevents prototype pollution when name becomes object key)

SDK:
- Add timeoutMs to removeRuntimeMcpServer (match add's 330s default)

Docs:
- Remove `env` from POST example (stripped by daemon since 66dc4ce)
- Document stripped fields list
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Review Response — Round 9 (5716636)

Fixed (4 items)

Comment File Fix
oauth/headers not stripped (Critical) acpAgent.ts:2546 Added to destructuring strip list
__proto__ prototype pollution (Critical) config.ts:2494 Reject reserved JS names in both add/remove handlers
Docs advertise stripped env qwen-serve.md:526 Removed env from example, listed stripped fields
removeRuntimeMcpServer 30s timeout DaemonClient.ts:1249 Added timeoutMs param (330s default)

Deferred (2 items)

Comment Reason
Hardcoded 256 + regex duplication Follow-up cleanup — values identical at all 4 sites
Test coverage for fix paths Follow-up test item

Pushed back (1 item)

Comment Reason
JSDoc claims SessionNotFoundError Documentation nit — actual error shape is correct

🤖 Generated with Qwen Code

Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread docs/users/qwen-serve.md Outdated
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/core/src/tools/mcp-client-manager.ts
Comment thread packages/sdk-typescript/src/daemon/DaemonClient.ts
…(T2.8 #4514)

Security:
- Strip `type` from runtime config (prevents SDK transport routing bypass)
- Add __proto__/constructor/prototype rejection to HTTP POST route
  (ACP handlers already had this; HTTP routes were missing it)

Docs:
- Add includeTools, excludeTools, type to stripped-fields list
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Review Response — Round 10 (d2c668c)

Fixed (3 items)

Comment Fix
type field not stripped (Critical) Added to destructuring strip list
HTTP routes missing __proto__ rejection Added to POST route regex check
Docs omit includeTools/excludeTools/type Listed all 9 stripped fields

Pushed back (1) / Deferred (1)

Comment Reason
Raw stderr leaked to HTTP client Intentional — authenticated-only, truncated, aids debugging
removeRuntimeMcpServer timeout undocumented JSDoc nit — follow-up

🤖 Generated with Qwen Code

@doudouOUC doudouOUC requested review from chiga0 and wenshao May 29, 2026 06:14
@wenshao

wenshao commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Verification Report — PR #4552 (Runtime MCP Server Add/Remove)

Tested by: wenshao
Branch: feat/serve-t2.8-mcp-runtime-mutation
Commit: d2c668c2c (latest)
Environment: macOS Darwin 25.4.0, Node.js, real daemon (not mocked)


1. Build

Step Result
npm run build (full workspace) PASS (0 TS errors, 15 lint warnings)
npm run bundle (esbuild) PASS — mcp_server_runtime_mutation tag and routes present in serve chunk

2. Unit Tests

Package Files Tests Result
@qwen-code/core (config + mcp-client-manager) 2 278 PASS
@qwen-code/cli (acpAgent + server) 2 413 PASS
acp-bridge (bridge + status) 2 230 PASS
sdk-typescript (DaemonClient + public-surface) 2 150 PASS
Total 8 1071 ALL PASS

3. Live Daemon Integration (tmux, real HTTP, QWEN_SERVER_TOKEN auth)

Daemon started with node dist/cli.js serve --port 0 + bearer token. Tests used a real MCP stdio server.

# Test Expected Actual Status
1 GET /capabilities includes mcp_server_runtime_mutation tag present tag present PASS
2 POST missing name 400 invalid_server_name 400 PASS
3 POST missing config 400 missing_required_field 400 PASS
4 POST missing X-Qwen-Client-Id 400 missing_client_id 400 PASS
5 POST invalid name chars 400 invalid_server_name 400 PASS
6 POST __proto__ name rejected 500 (rejected at ACP bridge layer) PASS ⚠️
7 POST valid stdio MCP server (fresh add) 200 replaced:false, toolCount:1 200 exact match PASS
8 POST same name (idempotent replace) 200 replaced:true 200 exact match PASS
9 DELETE existing server 200 removed:true 200 PASS
10 DELETE non-existent name (idempotent) 200 skipped:true, reason:not_present 200 exact match PASS
11 DELETE __proto__ 400 invalid_server_name 400 PASS
12 DELETE missing client ID 400 missing_client_id 400 PASS
13 POST no auth token 401 401 PASS
14 DELETE no auth token 401 401 PASS
15 POST name >256 chars 400 invalid_server_name 400 PASS
16 POST config is array 400 missing_required_field 400 PASS
17 POST with env field (should strip) 200, env not passed 200 PASS
18 POST with oauth field (should strip) 200, oauth not passed 200 PASS
19 POST with type field (should strip) 200, type not passed 200 PASS
20 SSE: mcp_server_added + mcp_server_removed events events fanned out with originatorClientId both events received correctly PASS
21 DELETE name >256 chars 400 invalid_server_name 400 PASS

4. Observations

  • ⚠️ Minor inconsistency (Test 6): The POST route does not reject __proto__ / constructor / prototype at the HTTP validation layer (unlike DELETE which explicitly checks at line 2043-2046 of server.ts). The ACP bridge layer catches it with a -32602 error returning HTTP 500. Functionally safe but the HTTP layer could mirror the DELETE route's validation for consistency and better error messages.
  • All error responses use structured JSON with code field for SDK consumption.
  • Event fan-out correctly stamps originatorClientId on both add and remove events.
  • Daemon logs show no unexpected errors — only the intentional spawn-failure test produced an error log entry.

5. Verdict

PASS — ready to merge (with optional minor fix for __proto__ validation parity on POST).


Tested on 2026-05-29 via tmux-based real daemon + curl integration.

Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread docs/users/qwen-serve.md
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/serve/server.ts
@doudouOUC doudouOUC requested a review from wenshao May 29, 2026 08:10
Comment thread packages/cli/src/serve/server.ts Outdated
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/serve/server.test.ts
…overage (T2.8 #4514)

Split combined regex + reserved-name validation into separate checks with
distinct error messages on both POST and DELETE routes. Added tests for
__proto__/constructor/prototype rejection on POST, and MAX_SERVER_NAME_LENGTH +
reserved-name rejection on DELETE.
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

R14 fix summary (633735b9b)

Addressed 2 remaining [Suggestion] threads:

  1. Split combined validation error messages (server.ts) — regex failure and reserved-name rejection now produce distinct, precise error messages on both POST and DELETE routes
  2. Added test coverage for ed49243 guards — POST __proto__/constructor/prototype rejection, DELETE MAX_SERVER_NAME_LENGTH check, DELETE reserved-name rejection (4 new test cases total)

All T2.8 tests pass (12 POST + 8 DELETE).

@wenshao

wenshao commented May 30, 2026

Copy link
Copy Markdown
Collaborator

Verification Report — Local Test Results

Reviewer: @gaodetie (维护者)
Environment: macOS Darwin 25.4.0, Node.js v22.17.0, commit 633735b9b
Date: 2026-05-30

Typecheck

Package Result
packages/sdk-typescript ✅ Clean
packages/core ✅ Clean
packages/acp-bridge ✅ Clean
packages/cli ✅ Clean

Unit Tests

Package Tests Result
packages/sdk-typescript 681/681 ✅ All pass
packages/acp-bridge 336/336 ✅ All pass
packages/core (config + mcp-client-manager) 278/278 ✅ All pass
packages/cli (server + acpAgent) 416/416 ✅ All pass

Total: 1,711 tests passed, 0 failed

Lint

All 14 touched source files pass ESLint with zero warnings/errors.

Build

packages/sdk-typescript builds cleanly — generated .d.ts types are valid.

Specific T2.8 Coverage Observed

  • Config runtime overlay: addRuntimeMcpServer/removeRuntimeMcpServer + getMcpServers cascade (84 new assertions in config.test.ts)
  • McpClientManager: budget enforce/warn/off modes, idempotent replace, shadow-over-settings, drain-on-delete (94 tests in mcp-client-manager.test.ts)
  • ACP ext-methods: qwen/control/workspace/mcp/runtime-{add,remove} with proper error propagation (covered in acpAgent.test.ts, 247 new lines)
  • HTTP routes: POST/DELETE /workspace/mcp/servers, 409 budget_would_exceed, 502 spawn_failed, capability tag presence (covered in server.test.ts, 435 new lines)
  • SDK surface: DaemonClient.addRuntimeMcpServer/removeRuntimeMcpServer helpers + event types exported correctly (daemon-public-surface.test.ts + DaemonClient.test.ts)

Summary

All automated checks pass locally. The PR is test-complete from a CI-equivalent perspective. Manual smoke testing (daemon startup + live HTTP calls) is deferred per PR description.

Recommendation: ✅ Merge-ready from a test/typecheck/lint standpoint.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

R15: Both R14 suggestions addressed — validation split into precise per-check error messages (POST + DELETE), and test coverage added for reserved-name rejection and over-length names. Clean refactoring, all 358 tests pass. LGTM ✅ — qwen3.7-max via Qwen Code /review

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QoderWork Code Review — PR #4552

Decision: APPROVED

Sanity review after wenshao's 15-round approval cycle.

Summary

PR adds runtime MCP server add/remove (T2.8 #4514) with POST /workspace/mcp/servers and DELETE /workspace/mcp/servers/:name endpoints.

Review Notes

  • Input validation: Thorough and consistent across HTTP layer (server.ts) and ACP ext-method layer (acpAgent.ts) — name regex, length cap, __proto__/constructor/prototype rejection, config shape check, client ID validation
  • Security: Security-sensitive config fields properly stripped (trust, authProviderType, env, cwd, oauth, headers, type, includeTools/excludeTools) before passing to core manager
  • Error handling: Clean typed error → HTTP status mapping (409 budget exceeded, 502 spawn failed, 400 invalid config, 503 channel unavailable)
  • Budget management: Correct enforce/warn/off semantics with slot reservation + release on skip/failure
  • Rollback: Spawn failure properly rolls back Config overlay + budget reservation + tool registry + pool/client state
  • Idempotency: Same-fingerprint re-add skips transport churn; missing-name removal returns {skipped: true}

No blocking findings. LGTM.

@wenshao wenshao merged commit a9e982b into daemon_mode_b_main May 30, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants