feat(mc): P8 MCP client — manager/parser/tester + stdio/SSE/HTTP + settings UI#449
Conversation
Cut feature/mcp-client off next (P0-P7 merged). Scope = parity-charter §3.7 in-app Claude MCP — McpServerManager/McpConfigParser/McpTester + stdio/SSE/HTTP transports + settings UI (McpServerModal/McpSettingsManager/McpTestModal); backs the P6 MCP-selector seam. Key ADRs: McpClientPort (transport, stdio spawn security) + McpConfigStorePort (config source — vault Claude-CLI-readable vs device-local). Claude-only (non-Claude MCP is P9+). Autonomous full-epic drive. Next: /spec:requirements. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
36 EARS REQ-MC (config/parse · manager lifecycle · transports · tester · settings UI · selector+runtime · security · a11y+additivity) + 12 NFR-MC, each mapped to a claudian path + TEST-MC id. Config source = vault .claude/mcp.json (Claude CLI reads it; diverges from device-local intentionally, CLAR-MC-001). Transports stdio/SSE/HTTP all Claude-backed; McpTester 10s timeout + partial-success. MCP tool calls gated by the P7 ApprovalManager. Additive enabledMcpServers? (was excluded). Bundles @modelcontextprotocol/sdk (CLAR-MC-003, rationale to record). No-servers = byte-identical P1-P7 (REQ-MC-082). CLAR-MC-001..005 resolved-by-recommendation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ADR-MC-001 McpConfigStorePort over vault .claude/mcp.json (Claude-CLI-readable; diverges from device-local intentionally) + pure McpConfigParser (4 paste formats). ADR-MC-002 McpClientPort transport seam (isAvailable/test/connect/listTools/callTool/ disconnect; never throws) over stdio/SSE/HTTP via @modelcontextprotocol/sdk, real impl coverage-excluded obsidian/**, SDK externalized like @codemirror/* (build:web never sees it). ADR-MC-003 additive ChatRuntimeQueryOptions.enabledMcpServers? (fold non-empty) + McpServerManager use case; MCP tool calls route through the unchanged P7 ApprovalManager (mcp__server__tool, no special-case, no providerId). Components: McpSettingsManager/McpServerRow/McpServerModal/McpTestModal + expanded McpSelector. No-servers = byte-identical P1-P7. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 layer groups, 30 SPEC items. Pins McpConfigStorePort (vault .claude/mcp.json, load-or-default), McpClientPort (isAvailable/test/connect/listTools/callTool/disconnect; callTool off the P8 turn-time path), additive ChatRuntimeQueryOptions.enabledMcpServers?, the pure McpConfigParser (4 paste formats + getMcpServerType + validate), McpServerManager use case (await-save), McpTestResult state model (success/partial/timeout/error/unavailable), the 3-bridge impls (SDK externalized, real transports coverage-excluded). EC-MC-1..20. Manual legs TEST-MC-M1/M2 + real SSE/HTTP/stdio. Full coverage table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T-MC-001 baseline+guard-verify; DOMAIN 002..011, INFRA 012..017 (T-MC-012 adds @modelcontextprotocol/sdk + confirms externals), APPLICATION 018..021, UI 022..033, STYLES 034, WIRE-IN 035..037, GATE 038..043. NO guard-relax — but a file-naming directive (VaultMcpConfigStore/SdkMcpClient, NOT ObsidianMcp*, NOT under obsidian/mcp/) avoids the still-active ObsidianMcp* / obsidian/mcp ban. Real SDK transports + stdio spawn coverage-excluded → manual legs T-MC-041/042 (TEST-MC-M1/M2). additive enabledMcpServers? = no implements break. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…g directive Scaffold parity-screenshots.md (claudian-main baseline) + test-plan.md (guard-verify note, Obsidian-infra file-naming directive VaultMcpConfigStore/ SdkMcpClient, manual legs TEST-MC-M1/M2 + TEST-MC-021/022/061/064) + implementation-log.md. Confirm MCP_CONFIG_STORE_PORT/MCP_CLIENT_PORT keys + the new @/domain/chat/mcp, @/application/chat/mcp, @/ui/chat/mcp paths + the two new ports are not caught by DELETED_SUBSYSTEM_BAN/DELETED_INJECTION_KEYS. No src/ change. NFR-MC-009 NFR-MC-005. SPEC-MC-001/003/004/009/015/016/017/018/021/030. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
McpTypes.test.ts asserts the config union / ManagedMcpServer / McpTool / McpTestResult / ParsedMcpConfig / EnabledMcpServers / DEFAULT_MCP_SERVER shapes re-exported from @/domain/chat/mcp. ChatTurn.ts.test.ts grows the _queryKeys exact-keys to eight (appending enabledMcpServers), adds the type leg + the externalContextPaths-stays-EXCLUDED leg + the P7-shaped byte-identical serialisation leg + the empty-Set mcpMentions seam. RED: the McpTypes module + the enabledMcpServers? field do not yet exist. TEST-MC-001 TEST-MC-082. SPEC-MC-001/002/022. REQ-MC-052/082. NFR-MC-001. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rs? + barrel Add src/domain/chat/mcp/McpTypes.ts (config union + ManagedMcpServer + McpTool + McpTestResult + ParsedMcpConfig + EnabledMcpServers + DEFAULT_MCP_SERVER, regrown verbatim from claudian core/types/mcp.ts + McpTester.ts:13-25) + the barrel index.ts. Append enabledMcpServers?: EnabledMcpServers to ChatRuntimeQueryOptions after permissionMode; P0-P7 members byte-identical, externalContextPaths? stays EXCLUDED. Additive-only: no implements ChatRuntimePort break. Greens the TEST-MC-001 type-shape + TEST-MC-082 serialisation legs (15/15). SPEC-MC-001/002/022. REQ-MC-052/082. NFR-MC-001. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
parseClipboardConfig across the four formats (mcpServers wrapper / single un-named needsName:true / single named / multiple named) + the malformed/err cases (Invalid JSON / Invalid MCP configuration format / no valid entry) + getMcpServerType (sse/http/bare-url->http/stdio) + isValidMcpServerConfig per-shape table + the never-throws assertion. RED: McpConfigParser.ts does not yet exist (27 failing). TEST-MC-003/004/005/006. SPEC-MC-004/029. REQ-MC-003/004/005/006. NFR-MC-004. EC-MC-2/3/5/6. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… barrel Add src/domain/chat/mcp/McpConfigParser.ts: parseClipboardConfig (the four Claudian formats -> Result<ParsedMcpConfig>, malformed -> Result.err) + getMcpServerType (sse/http/bare-url->http/stdio, total) + isValidMcpServerConfig (non-empty command OR url). Ported verbatim from claudian McpConfigParser.ts:17 + core/types/mcp.ts:74/81 with throw paths converted to Result.err (ADR-004); JSON.parse wrapped in trySync (domain Result-discipline). Pure + total - never throws. Greens TEST-MC-003/004/005/006 (27/27). SPEC-MC-004/029. REQ-MC-003/004/005/006. NFR-MC-003/004. EC-MC-2/3/5/6. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
deserializeMcpConfig load-or-default (null/empty/unparseable/no-mcpServers/ non-object-mcpServers -> ok([])) + sidecar default application + disabledTools filter + skip-invalid; serializeMcpConfig default-pruning (all-default writes no sidecar) + non-default-only fields + 2-space indent + round-trip + CLI-key preservation (unknown top-level keys + non-servers _claudian keys) + empty _claudian deletion + the never-throws assertion. RED: McpConfigCodec.ts does not yet exist (19 failing). TEST-MC-001/002/007. SPEC-MC-003. REQ-MC-001/002/007. NFR-MC-004. EC-MC-12/19/20. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… + barrel Add src/domain/chat/mcp/McpConfigCodec.ts: deserializeMcpConfig (load-or-default ok([]); DEFAULT_MCP_SERVER defaults; trimmed-non-empty disabledTools filter; skip-invalid) + serializeMcpConfig (mcpServers + ONLY non-default _claudian.servers; preserve unknown top-level + non-servers _claudian keys; delete empty _claudian; 2-space indent). Ported from claudian McpStorage.load:14-56 + save:58-134; JSON.parse via trySync; delete operator replaced by rest-spread rebuild (codec ban). Pure + total. Greens TEST-MC-001/002/007 (19/19; full mcp suite 61/61). SPEC-MC-003. REQ-MC-001/002/007. NFR-MC-004. EC-MC-12/19/20. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sallowedMcpTools
parseCommand.test.ts: providedArgs passthrough, quote-aware split,
empty/whitespace command -> { cmd:'', args:[] }, single/double quote grouping,
never-throws. getActiveServers.test.ts: enabled/disabled/context-saving(∅) filter,
mentioned inclusion, fresh map; collectDisallowedMcpTools enabled-only
pre-register ignoring contextSaving, mcp__server__tool trim/dedupe/sort,
never-throws. RED: parseCommand.ts + getActiveServers.ts do not yet exist (23
failing).
TEST-MC-020a/052/053/054. SPEC-MC-005/006. REQ-MC-020/023/052/053/054/061.
NFR-MC-004. EC-MC-7/9/10.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…owedMcpTools Add src/domain/chat/mcp/parseCommand.ts (parseCommand + splitCommandString, the no-shell quote-aware tokeniser, stepCharacter helper for complexity) + getActiveServers.ts (getActiveServers enabled AND (NOT contextSaving OR mentioned) + collectDisallowedMcpTools enabled-only trim/dedupe/sort). Ported verbatim from claudian utils/mcp.ts:46/59 + McpServerManager.getActiveServers:38 + getAllDisallowedMcpTools:74-94. Pure + total. Greens TEST-MC-020a/052/053/054 (23/23; full mcp suite 73/73). SPEC-MC-005/006. REQ-MC-020/023/052/053/054/061. NFR-MC-002/004. EC-MC-7/9/10. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ys + barrel
McpConfigStorePort.test.ts: exactly load/save/exists (Result-typed) + own
MCP_CONFIG_STORE_PORT key + barrel re-export of the port + ManagedMcpServer.
McpClientPort.test.ts: exactly isAvailable/test/connect/listTools/callTool/
disconnect (test -> McpTestResult never throws, live methods Result-typed) +
McpConnection { readonly id } + own MCP_CLIENT_PORT key + barrel re-exports. RED:
the two ports + the two keys + the barrel re-exports do not yet exist.
TEST-MC-081. SPEC-MC-007/008. REQ-MC-001/002/007/020..023/030..034. NFR-MC-005.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… re-exports Add src/domain/ports/McpConfigStorePort.ts (load/save/exists, Result-typed) + McpClientPort.ts (isAvailable/test/connect/listTools/callTool/disconnect + McpConnection; test -> McpTestResult never throws). Append MCP_CONFIG_STORE_PORT + MCP_CLIENT_PORT InjectionKeys to bridge/ports.ts (own keys, no aggregate). Re-export the two ports + McpConnection + the MCP DTOs from domain/ports barrel. Greens TEST-MC-081 (4/4; domain chat+ports 196/196). Deleted-symbol guard green; no implements break (new ports, no prior impl). SPEC-MC-007/008. REQ-MC-001/002/007/020..023/030..034. NFR-MC-004/005. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Record the DOMAIN-batch (T-MC-001..011) close-out in implementation-log.md (the final whole-project gate: vue-tsc 0, lint 0, domain chat+ports 196/196; additivity proven; deleted-symbol guard green) and advance workflow-state.md to stage 7 implementation (implementation-log.md + test-plan.md = in-progress; INFRA->GATE pending) with the dev hand-off note for the INFRA batch. SPEC-MC-001..008. NFR-MC-001/004/005. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…+ record rationale Implements SPEC-MC-030 / REQ-MC-080 / NFR-MC-010 (ADR-MC-002 §3). The SDK @modelcontextprotocol/sdk@^1.29.0 (MIT, Anthropic-maintained) is in package.json dependencies + the committed lockfile. vite.config.ts ALL_EXTERNALS already covers its node:* entry points (builtinModules + node: forms); the SDK package bundles into the plugin main.js like the agent-sdk; it is imported ONLY under src/infrastructure/obsidian/** so build:web (MockBridge) never sees it. Rationale recorded in implementation-log.md per AGENTS.md §8. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Client Implements SPEC-MC-009 (REQ-MC-001/007/020..023/030..034/061..064/080, NFR-MC-002/006 manual leg). VaultMcpConfigStore = thin Result-typed VaultPort I/O on .claude/mcp.json delegating the round-trip to the pure McpConfigCodec (never data.json/device-local). SdkMcpClient = real stdio (bounded explicit spawn, no-shell parseCommand cmd+args, merged env + enhanced PATH, stderr:'ignore') / SSE / HTTP transports over @modelcontextprotocol/sdk + a Node http(s) fetch shim, 10s AbortController, partial-success/friendly-error mapping, torn down in finally; test never throws. Exposed via get mcpConfigStore / get mcpClient on ObsidianBridge. Files named per the directive (not ObsidianMcp*, not obsidian/mcp/); SDK imported only here; coverage-excluded — behavioural gate = manual TEST-MC-M1 + TEST-MC-021/022/061/064. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ake-ports members RED for SPEC-MC-010. tests/infrastructure/mock/MockMcpConfigStore.test.ts (seedMcpServers + load/save/exists codec round-trip + setMcpStoreFailMode), MockMcpClient.test.ts (isAvailable + setClientMode SPEC-MC-028 matrix success/partial/timeout/error/unavailable + scriptTestResult per-server + connect/listTools/callTool/disconnect canned + never-throws), and the extended fake-ports.test.ts (mcpConfigStore + mcpClient members). Fails — the Mock modules + the fake-ports members do not yet exist (T-MC-015 greens). Traces TEST-MC-001/002/007/030..034/072/080, SPEC-MC-010, REQ-MC-002/004/007/030..033/080, NFR-MC-006. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… fake-ports members Implements SPEC-MC-010 (REQ-MC-002/004/030..033/080, NFR-MC-006). MockMcpConfigStore = scriptable in-memory document store round-tripped through the pure McpConfigCodec (seedMcpServers + load/save/exists + setMcpStoreFailMode). MockMcpClient = scriptable client (isAvailable→true, scriptTestResult per-server, setClientMode driving the SPEC-MC-028 success/partial/timeout/error/unavailable matrix, canned connect/listTools/ callTool/disconnect). Exposed via get mcpConfigStore / get mcpClient on MockBridge + the mcpConfigStore/mcpClient members on tests/__fakes__/fake-ports.ts. Greens the prior RED 39/39; total, never throws; no node:*/obsidian. Traces TEST-MC-001/002/007/030..034/072/080. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…+ bridge getters Completes the P8 INFRA batch (the prior agent timed out after T-MC-015). LocalStorageMcpConfigStore persists the .claude/mcp.json document text in localStorage via the pure McpConfigCodec (load-or-default round-trip); the inert LocalStorageMcpClient reports isAvailable:false + a structured test failure + Result.err live methods + idempotent disconnect (no Node transport in the browser demo). Wired get mcpConfigStore / get mcpClient on LocalStorageBridge. 6/6 green; typecheck + whole-project lint 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…k store Authors the failing unit tests for the lifecycle use case (SPEC-MC-012): load (load-or-default + err-degrades-to-[]), add (default-apply + await-save + empty/duplicate-name reject + rollback-on-save-err), edit/remove/setEnabled/ setToolDisabled (locate-by-name + await-save), getEnabledCount, getActiveServers(∅), getEnabledMcpServers(∅ → undefined when empty), and the never-throws-across-a-port-boundary path. TEST-MC-010..016/050/051/052..054/072. SPEC-MC-012. REQ-MC-010..016/050/051/ 052..054/071/072. NFR-MC-004. EC-MC-4/8/9/10/18. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Authors the failing unit tests for the two pure application transforms: foldEnabledMcpServers (empty→undefined / all-disabled→undefined / all-context-saving(∅)→undefined / non-empty fold with pre-registered disallowedTools) and buildMcpViewModel (supported gate, empty-seam-vs-live kind, McpServerVm transport-type mapping, enabledCount, never-throws). TEST-MC-015/040/050/052/082. SPEC-MC-013/014. REQ-MC-015/040/050/051/052/082. NFR-MC-001. EC-MC-1/8/9/13. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the two pure/total application transforms. foldEnabledMcpServers (SPEC-MC-013) computes getActiveServers + collectDisallowedMcpTools and omits the field (returns undefined) when the active set is empty, so a no-servers / all-disabled / all-context-saving(∅) turn stays byte-identical to P7 (REQ-MC-082, NFR-MC-001). buildMcpViewModel (SPEC-MC-014) derives the empty-seam-vs-live McpViewModel + McpServerVm rows + enabledCount, DTO-only, no providerId branch. SPEC-MC-013/014. REQ-MC-015/040/050/051/052/082. NFR-MC-001/005. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the MCP lifecycle use case over McpConfigStorePort (SPEC-MC-012): load (load-or-default; err degrades to a notice + empty list, never crashes), add (DEFAULT_MCP_SERVER enabled + draft contextSaving; empty/duplicate-name reject leaving the existing server unchanged), edit/remove/setEnabled/ setToolDisabled (locate-by-name, missing → err). Every mutation awaits store.save before resolving and rolls the in-memory list back + surfaces a notice on a save err (open item #4, EC-MC-18). getActiveServers(∅)/getEnabledMcpServers(∅) delegate to the pure SPEC-MC-006/013 helpers. Result-returning, never throws across the port boundary; no secret/config value logged. SPEC-MC-012. REQ-MC-010..016/050/051/052..054/071/072. NFR-MC-003/004. EC-MC-4/8/9/10/18. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hand-off Append the per-task implementation-log entries (T-MC-018/020 RED, T-MC-021/019 green) + the APPLICATION-batch close-out; tick the T-MC-018..021 DoD boxes in tasks.md; advance the Stage 7 row + the implementation-log.md artifact line + last_agent + the dev hand-off note to reflect DOMAIN + INFRA + APPLICATION complete, UI batch (T-MC-022 onward) pending. SPEC-MC-012/013/014. TASKS-MC-001. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TEST-MC-081 composable leg. SPEC-MC-019 REQ-MC-081 NFR-MC-005. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements SPEC-MC-019. REQ-MC-081 NFR-MC-005. Inject-or-throw, one port per composable, no aggregate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TEST-MC-042/044 seam legs. SPEC-MC-023 REQ-MC-042/044 NFR-MC-007. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bles Implements SPEC-MC-023. REQ-MC-042/044 NFR-MC-007. OpenMcpServerModalFn / OpenMcpTestModalFn + keys + auto-dismiss/no-op fallbacks; P3/P4/P5 handles byte-identical. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TEST-MC-013/014/040/041/070 A legs. SPEC-MC-015 REQ-MC-013/014/040/041/070. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements SPEC-MC-015. SPEC-MC-024 REQ-MC-013/014/040/041/070 NFR-MC-005/006/007/008. Presentational list+row; gated/empty/live; accessible-named controls; full agent.chat.mcp.* microcopy in en+de. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TEST-MC-010/011/012/042/043/070 A legs + EC-MC-2/3/4. SPEC-MC-016/023 REQ-MC-010/011/012/042/043/070. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ation Implements SPEC-MC-016/023. SPEC-MC-024 REQ-MC-010/011/012/042/043/070/072 NFR-MC-006/007. parseClipboardConfig-driven; name required/duplicate block; edit pre-fill + replacing draft; Escape cancels; no v-html/window.prompt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TEST-MC-016/030..034/044 A legs. SPEC-MC-017/028 REQ-MC-016/023/030..034/044/070/072. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements SPEC-MC-017/028. SPEC-MC-024 REQ-MC-016/023/030..034/044/070/072 NFR-MC-006/007/008. Probe via injected McpClientPort; running/success/partial/ timeout/error/unavailable; per-tool set-tool-disabled; live region; no secret render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…+badge) TEST-MC-050/051/082 A legs + EC-MC-1/8. SPEC-MC-018 REQ-MC-050/051/070/082. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mpty seam Implements SPEC-MC-018. SPEC-MC-024 REQ-MC-050/051/070/082 NFR-MC-006/007/008. McpViewModel prop; empty-seam kept byte-identical; live list+toggle+enabledCount badge; ToolbarStrip adapts McpWidgetVm to an empty-seam McpViewModel (additive). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records the UI-batch RED+green SHAs, the component/modal/PO inventory, the fold/selector/approval wiring readiness + absent-port degrade, and the P6-selector/P7-approval/tabsStore regression-green gate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ken slice Adds the §4.15 (ASCII `section 4.15` marker) MCP token slice to src/ui/styles/tokens.css: --sp-mcp-row-gap, --sp-mcp-status-ok, --sp-mcp-status-error, --sp-mcp-selector-badge, each a token-layer var(--sp-*) lookup (no hex / no raw Obsidian var / no physical property, ASCII-only comments for lightningcss). Updates the tokens-contract test with the §4.15 presence + leak guard (TEST-MC-045) and bounds the §4.14 slice with the new marker. Implements SPEC-MC-021. NFR-MC-009, REQ-MC-045, TEST-MC-045. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…g + degrade) Adds tests/ui/chat/ChatSurface.mcp.test.ts asserting the surface builds one per-surface McpServerManager over MCP_CONFIG_STORE_PORT, mounts the settings surface + threads the live selector at supportsMcpTools, folds getEnabledMcpServers(empty) into queryOptions.enabledMcpServers only when defined, routes an mcp__<server>__<tool> approval through the UNCHANGED P7 ApprovalManager (no special-case), and degrades gracefully on a store fault / absent ports. Extends ChatSurface.po.ts + the standalone main.ts MCP smoke leg. RED: the production provide + the per-surface manager + the fold do not exist yet (4 failed / 3 passed in ChatSurface.mcp.test.ts). SPEC-MC-020, SPEC-MC-026. TEST-MC-052/065/071/072/082 + TEST-MC-081 wiring leg. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… + fold AgentSidebarView provides MCP_CONFIG_STORE_PORT=ObsidianBridge.mcpConfigStore + MCP_CLIENT_PORT=ObsidianBridge.mcpClient + the OPEN_MCP_SERVER_MODAL / OPEN_MCP_TEST_MODAL launchers that open the new Obsidian Modal hosts (McpServerModalHost / McpTestModalHost in src/plugin/**, the only obsidian + MCP-modal imports in the wiring). src/ui/main.ts provides the Mock ports + browser-safe seam stand-ins. ChatSurface builds one per-surface McpServerManager, mounts McpSettingsManager, threads the live mcpVm + set-mcp-enabled through ChatComposer/ToolbarStrip/McpSelector, and folds getEnabledMcpServers(empty) into queryOptions.enabledMcpServers only when defined (omitted when empty so a no-MCP turn stays byte-identical to P7). An mcp__<server>__<tool> tool call routes through the UNCHANGED P7 ApprovalManager via ApprovalGateRuntime — no special-case, no providerId branch. Greens T-MC-035. SPEC-MC-020, SPEC-MC-026. REQ-MC-052/065/071/072/082. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TEST-MC-040/043/044/050/052/082 dev leg: deterministic mount automated + PASS; interactive live-dev flow deferred to human run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
STYLES + WIRE-IN complete; GATE T-MC-038..043 (invariant gate + human manual legs + final DoD/PR) remain. implementation-log.md stays in-progress. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
REVIEW-MC-001 verdict approve-with-nits (0 P1, 0 P2, 2 P3, 3 P4). Security confirmed: vault .claude/mcp.json never data.json, no plaintext secret, stdio spawn bounded/no-shell, malformed→Result.err, mcp__server__tool gated by the unchanged P7 ApprovalManager (no providerId branch), SDK externalized. Live wiring + per-surface manager + enabled-servers fold + dual provide verified; file-naming ban honoured (VaultMcpConfigStore/SdkMcpClient). TRACE-MC-001 matrix; manual legs TEST-MC-M1/M2 + real transports pending. Nits = Windows PATH (M1), timer-untestable, test-report (Stage-8) — non-blocking. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shipped CSS rebuilt for P8 — McpSettingsManager/McpServerRow/McpServerModal/ McpTestModal/McpSelector styles + the §4.15 mcp-* token slice. Gate green: typecheck 0, lint 0, vitest 1772, build (SDK bundled) + build:web (SDK externalized, not bundled) + docs:api clean, npm audit (high) clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 592d5017c7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| onSetToolDisabled: (tool: string, disabled: boolean) => { | ||
| void this.manager.setToolDisabled(this.server.name, tool, disabled); |
There was a problem hiding this comment.
Serialize tool-toggle saves to avoid dropping disabled tools
The modal emits setToolDisabled without awaiting it, so multiple rapid toggles run concurrently against stale manager state. Because each setToolDisabled computes updated from the current in-memory disabledTools before prior saves resolve, the last completion can overwrite earlier toggles (e.g., disabling two tools quickly can persist only one) in .claude/mcp.json. Queue or await these updates so tool toggles are applied in order.
Useful? React with 👍 / 👎.
| next[index] = { | ||
| ...existing, | ||
| config: draft.config, | ||
| description: draft.description, | ||
| contextSaving: draft.contextSaving, | ||
| }; |
There was a problem hiding this comment.
Apply edited server name when saving an MCP server edit
The edit flow collects an editable draft.name from the modal, but edit() never writes that name back to the server record, so renaming appears to succeed in the UI while the stored server name remains unchanged. This is especially confusing because the modal performs duplicate-name validation for edits, implying renames are supported. Either persist draft.name (with duplicate checks) or make the edit form name immutable.
Useful? React with 👍 / 👎.
P8 — MCP Client (claudian-reboot)
In-app Claude MCP — config + manager + tester + transports + settings UI (charter §3.7). Spec:
specs/mcp-client/(PRD-MC-001, DESIGN-MC-001, SPEC-MC-001..030, ADR-MC-001..003, TASKS-MC-001 = 42 tasks).Delivered (T-MC-001..037 + gate)
.claude/mcp.jsonviaMcpConfigStorePort(Claude-CLI-readable; ADR-MC-001) + the pureMcpConfigParser(4 paste formats,getMcpServerType, validation) + codec (default-pruning + CLI-key preservation).McpServerManager(use case) — add/edit/remove/enable/disable/setToolDisabled (dup-reject, await-save + rollback,Result),getActiveServers/getEnabledMcpServers.McpClientPorttransports — stdio/SSE/HTTP via@modelcontextprotocol/sdk(SdkMcpClient, coverage-excludedobsidian/**);test10s timeout + partial-success + node fetch; externalized (build:web 331kB, SDK not bundled).McpSettingsManager+McpServerRow,McpServerModal(paste/parse),McpTestModal(5 states), expandedMcpSelector(list+toggle+badge) — modals via the seam (Vue never imports obsidian);mcp-*--sp-*tokens; en+de i18n.ChatRuntimeQueryOptions.enabledMcpServers?folded on submit (omit when empty → byte-identical P1-P7); MCP tool calls (mcp__server__tool) routed through the unchanged P7ApprovalManager(no special-case, noproviderId); ports + modal hosts provided inAgentSidebarView+main.ts; degrades to P6/P7 when absent.Gate (local, green)
vue-tsc0 ·eslint0 · vitest 1772 passed ·build(SDK bundled) +build:web(SDK externalized) +docs:apiclean ·npm audit --audit-level=highclean.Parity self-review (Stage 9)
review.mdapprove-with-nits (0 P1/P2). Security confirmed: vault-not-data.json, no plaintext secret, stdio bounded/no-shell, malformed→Result.err, MCP tool gated by P7, SDK externalized. Live wiring + fold + dual provide verified; theObsidianMcp*/obsidian/mcp/ESLint ban honoured viaVaultMcpConfigStore/SdkMcpClientnaming. P3/P4 nits (Windows PATH at M1, timer-untestable, test-report) non-blocking.New runtime dependency
@modelcontextprotocol/sdk@^1.29.0— the official MCP SDK; bundled into the pluginmain.js, externalized frombuild:web. Rationale: the canonical MCP transport/protocol implementation (stdio/SSE/HTTP) — re-implementing the protocol would be far larger surface + drift risk.Deferred — final epic gate
Manual legs TEST-MC-M1 (real stdio/SSE/HTTP transports + real
.claude/mcp.jsonround-trip + real Claude MCP turn through the SDK + P7 gate), TEST-MC-M2 (parity screenshots), the real-transport sub-legs, the livenpm run devflow.🤖 Generated with Claude Code