Skip to content

feat(mc): P8 MCP client — manager/parser/tester + stdio/SSE/HTTP + settings UI#449

Merged
Luis85 merged 50 commits into
nextfrom
feature/mcp-client
May 26, 2026
Merged

feat(mc): P8 MCP client — manager/parser/tester + stdio/SSE/HTTP + settings UI#449
Luis85 merged 50 commits into
nextfrom
feature/mcp-client

Conversation

@Luis85
Copy link
Copy Markdown
Owner

@Luis85 Luis85 commented May 26, 2026

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)

  • Config — vault .claude/mcp.json via McpConfigStorePort (Claude-CLI-readable; ADR-MC-001) + the pure McpConfigParser (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.
  • McpClientPort transports — stdio/SSE/HTTP via @modelcontextprotocol/sdk (SdkMcpClient, coverage-excluded obsidian/**); test 10s timeout + partial-success + node fetch; externalized (build:web 331kB, SDK not bundled).
  • UIMcpSettingsManager+McpServerRow, McpServerModal (paste/parse), McpTestModal (5 states), expanded McpSelector (list+toggle+badge) — modals via the seam (Vue never imports obsidian); mcp-* --sp-* tokens; en+de i18n.
  • Wiring — additive ChatRuntimeQueryOptions.enabledMcpServers? folded on submit (omit when empty → byte-identical P1-P7); MCP tool calls (mcp__server__tool) routed through the unchanged P7 ApprovalManager (no special-case, no providerId); ports + modal hosts provided in AgentSidebarView + main.ts; degrades to P6/P7 when absent.

Gate (local, green)

vue-tsc 0 · eslint 0 · vitest 1772 passed · build (SDK bundled) + build:web (SDK externalized) + docs:api clean · npm audit --audit-level=high clean.

Parity self-review (Stage 9)

review.md approve-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; the ObsidianMcp*/obsidian/mcp/ ESLint ban honoured via VaultMcpConfigStore/SdkMcpClient naming. 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 plugin main.js, externalized from build: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.json round-trip + real Claude MCP turn through the SDK + P7 gate), TEST-MC-M2 (parity screenshots), the real-transport sub-legs, the live npm run dev flow.

🤖 Generated with Claude Code

Symprowire and others added 30 commits May 26, 2026 02:48
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>
Symprowire and others added 20 commits May 26, 2026 07:28
…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>
@Luis85 Luis85 merged commit ae7e955 into next May 26, 2026
6 checks passed
@Luis85 Luis85 deleted the feature/mcp-client branch May 26, 2026 06:36
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +54 to +55
onSetToolDisabled: (tool: string, disabled: boolean) => {
void this.manager.setToolDisabled(this.server.name, tool, disabled);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +109 to +114
next[index] = {
...existing,
config: draft.config,
description: draft.description,
contextSaving: draft.contextSaving,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

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.

2 participants