Skip to content

mcp domain: built-in servers invisible; version read is unfixable and should be deleted #240

Description

@kevinthelago

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.ts

interface McpCard { id, name, enabled, transport, projects, url?, installed }   :309-318
interface McpPayload { servers: McpCard[] }                                     :320-322
buildMcpPayload({ servers, installedIds })                                      :324-336

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 installedIds always 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.
M3 No version exists in McpServer or on the wire readString(r.version, '').trim() || null (mcpView.ts:55) 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.
  • Fleet: mobile has no fleet route at allapp/ has no (fleet) or (live) directory. (Our CLAUDE.md folder listing is stale on this point and should be corrected.) Fleet data arrives as part of the glance projection, 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:

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions