You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The builder (:329-335) makes a Set of installedIds, then maps only input.servers (the user-config store slice), stamping installed. env is deliberately excluded as secret-bearing — asserted at storeProjections.test.ts:244-245.
Types resolved:
McpServer (src/features/mcp/lib/mcpServers.ts:14-25) — {id, name, enabled, projects, transport: "stdio"|"http", command?, args?, url?, env?}. There is no version field anywhere in the model.
What "version" actually means desktop-side: McpStat = checking|current|outdated|needs-build|downloading|building|updating|error (mcpVersioning.ts:8-10), derived per-server-name by deriveCheckStat (:21-23) from the Tauri mcp_check_update invoke (useMcpInstallStatus.ts:37-42). It is ephemeral React state, keyed by name, Tauri-bound, never in the store, and only computed for downloadable first-party servers. The pure projector (useStoreProjector.ts:133-138) structurally cannot read it. No version string exists to send, now or plausibly.
Built-ins — BUILTIN_MCP_SERVERS (mcpServers.ts:38), from src-tauri/data/mcp/builtin-servers.json. Exactly two: builtin-research (Research) and builtin-compliance (Compliance), both enabled: true, transport: "stdio". They are structurally identical to McpServer — real ids, names, transport — so they map onto McpCard with no synthesis.
withBuiltins (mcpServers.ts:42-45) prepends them, skipping any a user entry shadows by name, case-insensitively. resolveAllInstalledMcp (:67-70) is withBuiltins(all).filter(...), and built-ins are always runnable, so installedIdsalways contains both built-in ids.
The desktop's own page surfaces them richly: a dedicated "Built-in tools" section with the hint "always available — no install" and a per-card built-in chip (src/features/mcp/index.tsx:115-124, :135, :162).
Drift
#
Desktop sends
Mobile reads
Consequence
M1
McpCard has no builtin; the builder maps only the user store slice (storeProjections.ts:331)
readBool(r.builtin, false) (mcpView.ts:58)
McpView.builtins is permanently empty, so the Built-in tools section (McpSegment.tsx:40-45) never renders. The phone shows an incomplete tool inventory: two always-enabled, always-installed servers that every session gets by default are invisible.
M2
installedIds includes both built-in ids, but servers[] does not
n/a — mobile only reads the pre-resolved installed boolean
Dead ids on the wire. Harmless, but it means the payload already carries the built-in knowledge in a form nothing can use — fixing M1 is a small edit to the same builder.
version permanently null, so installTag's version branch (McpSegment.tsx:15) is dead and every installed server shows the bare "installed" literal. Unlike M1 this is not fixable by a projection change — there is no version string to project.
M4
installed is always a real boolean
installState: 'unknown' when non-boolean (:52-54)
Purely defensive, unreachable against a real desktop. Keep.
M5
id/name/enabled/transport/projects/url
same, via tolerant readers (:46-57)
No drift.
M6
env never crosses (asserted)
never read
Correct on both sides.
Acceptance criteria
Mobile-side, needs no desktop change:
Remove McpServerVM.version and the r.version read (mcpView.ts:55); simplify installTag (McpSegment.tsx:12-21) to drop the version branch. Update the module doc at mcpView.ts:9-11, which currently promises a version will arrive.
Delete the "carries a version through when present" test case in mcpView.test.ts.
Record explicitly in the PR that MCP install/update status (McpStat) is out of scope for the mirror — it is Tauri-invoke-derived, name-keyed, ephemeral, and only defined for downloadable first-party servers. This is a decision, not a follow-up.
Desktop projection change (needs a base-studio-code issue):
McpCard gains builtin?: boolean.
buildMcpPayload maps withBuiltins(input.servers) instead of input.servers, so both built-ins ride as cards with builtin: true, installed: true. Must preserve the case-insensitive name-shadowing rule (mcpServers.ts:42-45) — otherwise mobile renders a duplicate when a user server is named Research.
Optional but valuable: carry the built-ins' catalog desc (via builtInCatalog, mcpCatalogView.ts:8-11) so the phone can explain what Research and Compliance do, matching the desktop card.
storeProjections.test.ts gains cases asserting built-ins appear with builtin: true, that shadowed built-ins do not, and that env still never appears.
Additive only; an older mobile build sees them as ordinary servers — acceptable degradation, not a crash.
Mobile-side, gated on the desktop PR:
Keep the builtin read and the servers/builtins split (mcpView.ts:58, :70-71) — they become live rather than dead.
Built-in tools section renders with an "always available — no install" hint mirroring mcp/index.tsx:117, and without an off/not-installed affordance (built-ins are enabled and installed by construction).
mcpView.test.ts gains a fixture matching the real builtin-research / builtin-compliance cards.
npx tsc --noEmit clean; mirror suite green.
Bespoke-frame retirement: mobile is READY
tunnel_set_fleet_state and tunnel_set_mcp_state can be retired desktop-side now, with zero mobile changes and no coordinated release.
Mobile's server-to-client union (src/lib/types.ts:255-317) contains no mcp_state, fleet_state, automations, or automation_failed member. A repo-wide grep for mcp_state|fleet_state returns zero hits.
MCP reaches the phone only via store_state domain mcp.
Two adjacent findings for the same desktop cleanup:
tunnel_set_automations and tunnel_automation_failed are equally unconsumed.tunnelClient.ts:188 still calls them "bespoke and live", but mobile reads automations exclusively through store_state. Wire-compatibility is not what is keeping them alive.
However — retiring the automations pair deletes the only desktop-accepted mobile control path (useTunnelAutomations.ts:66-71, with cron validation behind it). Mobile has never had a client frame for it and the UI is read-only by explicit product rule. So retiring it is a product decision — does tap-to-arm ever come to the phone, or does arming stay a chat action? — not a compatibility one. Flag it as such rather than deleting silently.
hook_telemetry is a separate case and should not be retired blindly: mobile explicitly recognises and no-ops it (src/lib/tunnel.ts:719-722) and pins its byte shape in the shared fixture suite (tunnelProtocol.fixtures.test.ts:121-123, :260). Removing the frame would require deleting those fixture assertions in lockstep.
Part of #234.
Verdict
Two dead reads. One is fixable with a small desktop projection change; the other is not fixable at all and should be deleted rather than waited on.
Desktop surface (base-studio-code@develop)
src/features/tunnel/lib/storeProjections.tsThe builder (
:329-335) makes aSetofinstalledIds, then maps onlyinput.servers(the user-config store slice), stampinginstalled.envis deliberately excluded as secret-bearing — asserted atstoreProjections.test.ts:244-245.Types resolved:
McpServer(src/features/mcp/lib/mcpServers.ts:14-25) —{id, name, enabled, projects, transport: "stdio"|"http", command?, args?, url?, env?}. There is noversionfield anywhere in the model.McpStat=checking|current|outdated|needs-build|downloading|building|updating|error(mcpVersioning.ts:8-10), derived per-server-name byderiveCheckStat(:21-23) from the Taurimcp_check_updateinvoke (useMcpInstallStatus.ts:37-42). It is ephemeral React state, keyed by name, Tauri-bound, never in the store, and only computed for downloadable first-party servers. The pure projector (useStoreProjector.ts:133-138) structurally cannot read it. No version string exists to send, now or plausibly.BUILTIN_MCP_SERVERS(mcpServers.ts:38), fromsrc-tauri/data/mcp/builtin-servers.json. Exactly two:builtin-research(Research) andbuiltin-compliance(Compliance), bothenabled: true,transport: "stdio". They are structurally identical toMcpServer— real ids, names, transport — so they map ontoMcpCardwith no synthesis.withBuiltins(mcpServers.ts:42-45) prepends them, skipping any a user entry shadows by name, case-insensitively.resolveAllInstalledMcp(:67-70) iswithBuiltins(all).filter(...), and built-ins are always runnable, soinstalledIdsalways contains both built-in ids.src/features/mcp/index.tsx:115-124, :135, :162).Drift
McpCardhas nobuiltin; the builder maps only the user store slice (storeProjections.ts:331)readBool(r.builtin, false)(mcpView.ts:58)McpView.builtinsis permanently empty, so the Built-in tools section (McpSegment.tsx:40-45) never renders. The phone shows an incomplete tool inventory: two always-enabled, always-installed servers that every session gets by default are invisible.installedIdsincludes both built-in ids, butservers[]does notinstalledbooleanversionexists inMcpServeror on the wirereadString(r.version, '').trim() || null(mcpView.ts:55)versionpermanently null, soinstallTag's version branch (McpSegment.tsx:15) is dead and every installed server shows the bare "installed" literal. Unlike M1 this is not fixable by a projection change — there is no version string to project.installedis always a real booleaninstallState: 'unknown'when non-boolean (:52-54)id/name/enabled/transport/projects/url:46-57)envnever crosses (asserted)Acceptance criteria
Mobile-side, needs no desktop change:
McpServerVM.versionand ther.versionread (mcpView.ts:55); simplifyinstallTag(McpSegment.tsx:12-21) to drop the version branch. Update the module doc atmcpView.ts:9-11, which currently promises a version will arrive.mcpView.test.ts.McpStat) is out of scope for the mirror — it is Tauri-invoke-derived, name-keyed, ephemeral, and only defined for downloadable first-party servers. This is a decision, not a follow-up.Desktop projection change (needs a base-studio-code issue):
McpCardgainsbuiltin?: boolean.buildMcpPayloadmapswithBuiltins(input.servers)instead ofinput.servers, so both built-ins ride as cards withbuiltin: true, installed: true. Must preserve the case-insensitive name-shadowing rule (mcpServers.ts:42-45) — otherwise mobile renders a duplicate when a user server is named Research.desc(viabuiltInCatalog,mcpCatalogView.ts:8-11) so the phone can explain what Research and Compliance do, matching the desktop card.storeProjections.test.tsgains cases asserting built-ins appear withbuiltin: true, that shadowed built-ins do not, and thatenvstill never appears.Mobile-side, gated on the desktop PR:
builtinread and the servers/builtins split (mcpView.ts:58, :70-71) — they become live rather than dead.mcp/index.tsx:117, and without an off/not-installed affordance (built-ins are enabled and installed by construction).mcpView.test.tsgains a fixture matching the realbuiltin-research/builtin-compliancecards.npx tsc --noEmitclean; mirror suite green.Bespoke-frame retirement: mobile is READY
tunnel_set_fleet_stateandtunnel_set_mcp_statecan be retired desktop-side now, with zero mobile changes and no coordinated release.src/lib/types.ts:255-317) contains nomcp_state,fleet_state,automations, orautomation_failedmember. A repo-wide grep formcp_state|fleet_statereturns zero hits.store_statedomainmcp.app/has no(fleet)or(live)directory. (OurCLAUDE.mdfolder listing is stale on this point and should be corrected.) Fleet data arrives as part of theglanceprojection, which glance domain: status split into health+activity (#2541) — every node renders idle; fleets/personaRoles unread #238 covers.Two adjacent findings for the same desktop cleanup:
tunnel_set_automationsandtunnel_automation_failedare equally unconsumed.tunnelClient.ts:188still calls them "bespoke and live", but mobile reads automations exclusively throughstore_state. Wire-compatibility is not what is keeping them alive.useTunnelAutomations.ts:66-71, with cron validation behind it). Mobile has never had a client frame for it and the UI is read-only by explicit product rule. So retiring it is a product decision — does tap-to-arm ever come to the phone, or does arming stay a chat action? — not a compatibility one. Flag it as such rather than deleting silently.hook_telemetryis a separate case and should not be retired blindly: mobile explicitly recognises and no-ops it (src/lib/tunnel.ts:719-722) and pins its byte shape in the shared fixture suite (tunnelProtocol.fixtures.test.ts:121-123, :260). Removing the frame would require deleting those fixture assertions in lockstep.