feat(tui): manage client-owned MCP servers with /mcp
- Dominant language
- TypeScript
- Stars
- 5.4k
- Forks
- 502
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 715
Description
## Problem
Maka now supports `legacy | auto | 2026-07-28` MCP protocol preferences for both remote and stdio servers, but the TUI has no MCP management surface. Desktop owns an `McpConfigStore` and `McpClientManager`; the CLI only exposes the low-level `runtime-host capability-provider serve` command, which reads an existing config and must be managed as a separate process.
As a result, a TUI user cannot discover, add, enable, disable, remove, or reconnect MCP servers, and cannot tell whether configured tools are connected and published to the selected Runtime Host.
There is also an authority constraint to preserve: MCP configuration, credentials, transports, and child processes belong to the client machine. Runtime Host remains the authority for Run composition, execution, provider binding, and recovery after a Client Capability is published. A `terminal-client` credential intentionally cannot publish Client Capabilities.
## Desired outcome
Add `/mcp` to the TUI slash-command catalog. It should open an interactive management view over the current client workspace `mcp.json`:
```text
MCP servers
● filesystem stdio connected 12 tools legacy
○ docs remote disabled 0 tools auto
a Add Enter Details Space Enable/disable r Reconnect d Remove Esc Close
```
The add flow should offer both:
1. **Guided setup**
- stable server ID
- stdio or Streamable HTTP transport
- command and arguments, or URL
- `legacy | auto | 2026-07-28` protocol preference
- optional cwd/environment or request headers
2. **Paste JSON**
- accept either a wrapped `mcp.json` document or a direct server map through the existing import normalizer
- preview which entries will be added or replaced before committing
Sensitive values must not enter the conversation transcript or diagnostics. Persistence should continue through the existing atomic, mode-`0600` config store. After a committed mutation, the same TUI-side owner should synchronize `McpClientManager` and refresh the published Client Capability snapshot so the UI cannot report a config change as live before publication has caught up.
The view should distinguish at least: disabled, connecting, connected, reconnecting, error, and configured-but-not-published. Errors should stay attached to the affected server instead of failing the whole `/mcp` view.
## Ownership and design constraints
- Keep MCP config, credentials, transports, child processes, reconnects, and tool discovery client-owned. Do not move them into Runtime Host.
- Keep Runtime Host as the execution authority for published Client Capability calls.
- Reuse the existing deep modules: `McpConfigStore` for durable config, `McpClientManager` for MCP connection/discovery/call truth, and the existing Client Capability channel/publication lifecycle for Host registration.
- Add one TUI-owned MCP control module that renders snapshots and serializes user intent across those existing modules. Do not make the `/mcp` handler reproduce lifecycle state.
- Do **not** introduce a repository-wide provider abstraction up front. The standalone CLI provider, TUI, and Desktop have different lifecycle and composition requirements. Extract shared orchestration only after the vertical slice proves an identical invariant in at least two callers.
- Do not migrate Desktop onto the TUI control module. Desktop combines MCP with Browser, Computer Use, Settings, and other native Client Capabilities in one registration lifecycle.
- For a Local Host, publish on the same authenticated TUI connection that initiates the Session. This lets the Host select the initiating provider exactly and avoids inventing a detached companion identity.
- Re-publish the TUI snapshot when that connection is replaced, without creating a parallel connection owner or replaying unrelated commands.
- Do not assume that a detached remote `capability_provider` is automatically associated with a TUI Session. Runtime Host currently selects the initiating provider, or the only provider for a contract; two detached providers advertising the same contract are ambiguous. Remote publication therefore has an explicit design gate in PR 3.
- Bind publication to the selected Host profile/root. Closing the TUI or switching targets must unregister/close the old publication before publishing to another target.
- Reuse the existing MCP schema, normalizer, endpoint policy, OAuth credential storage, dual-era negotiation, and Client Capability projection. Do not introduce a TUI-only config format or a second publication authority.
- `/mcp` is a local management command and should be refused while a turn is active, matching other state-changing TUI commands.
## Proposed rollout
### PR 1: local vertical slice — `/mcp` status and publication
- Register `/mcp` in the shared slash-command catalog and add localized help/autocomplete copy.
- Add a TUI-owned MCP control module that composes the existing config store, manager, and Client Capability channel.
- Load the existing workspace `mcp.json`, connect enabled servers, and publish their tools on the same Local TUI connection that creates/controls Sessions.
- Re-publish after a successful connection replacement and close/unregister deterministically with the TUI context.
- Add a read-only list/details view with configured protocol, negotiated protocol, tool count, per-server diagnostics, and publication state.
- Exercise one real local path from saved config through manager discovery, Host-visible publication, tool invocation, reconnect, and shutdown.
- Keep Desktop and the standalone `runtime-host capability-provider serve` command unchanged. If this slice reveals genuinely identical orchestration, extract only that proven duplication within the same PR.
### PR 2: TUI management actions
- Add guided and JSON import flows.
- Add enable/disable, edit, remove, test, and reconnect actions.
- Serialize `McpConfigStore` mutation, manager synchronization, and capability refresh through the TUI control module.
- Cover write failure, connection failure, stale completion, cancellation, and shutdown without duplicating tests already owned by the store, manager, or publication modules.
### PR 3: remote Host provider association and publication
- First settle one exact association from the selected TUI connection/Session to its least-privilege MCP provider. A detached provider that is merely connected to the same Host is not sufficient when another provider offers the same contract.
- Preserve the rule that `terminal-client` cannot publish Client Capabilities; do not solve association by silently widening that preset.
- If the selected design uses a separate `capability_provider` credential, keep it out of profile JSON, arguments, transcripts, and logs, and bind it to the exact profile/root and TUI-owned provider identity.
- Surface missing/revoked credentials, provider ambiguity, and reconnects as explicit publication states.
- Verify two concurrent providers, exact Session binding, invocation routing, reconnect, root mismatch, revocation, and cleanup against a real remote Runtime Host before enabling remote `/mcp` publication.
These can remain separate reviewable PRs, but every PR must leave one TUI lifecycle owner and one exact Host provider binding, with no parallel configuration or publication path for the same Session.
## Confirmed adjacent MCP pain points
These are recorded here because `/mcp` must respect the same ownership boundaries, not because they should all be folded into the TUI rollout.
| Pain point | Current evidence | Tracking / next unit of work | Relationship to this issue |
| --- | --- | --- | --- |
| Desktop main-process OOM during prolonged Host flapping | Real Windows crash after about 12 hours, about 3.6 GB V8 heap, and hundreds of candidate generations | #3458; #3462 only reduced retry churn | Separate P0 root-cause fix. Do not close #3458 based on slower retries. |
| One invalid optional MCP tool can poison a Desktop capability candidate | Current capability projection validates the aggregate offer at the Host boundary | #3366 | Do not duplicate the active fix. `/mcp` should expose omitted/error tools per server. |
| JSON Schema 2020-12 compatibility is narrower than MCP's contract | Host projection still uses an explicit keyword allowlist; #3559 and #3560 fixed concrete annotation cases | Gather real server schemas before opening a broader compatibility change | Do not widen the allowlist speculatively inside a TUI PR. |
| Modern MRTR `input_required` results are rejected | `McpClientManager.callTool()` rejects `task`, `inputRequests`, and `requestState` markers | Separate RFC and Host interaction design | Not part of `/mcp`; continuation must enter the Host-owned interaction path rather than create UI-side execution authority. |
| Standalone provider recovery and config freshness are coarse | Config is read once; disconnected/error servers are retried by one fixed 5-second scan | Two narrow follow-ups: config reload and bounded per-server retry/backoff | Do not add a second retry timer or lifecycle policy in the TUI view. |
| OAuth consent does not disclose issuer/scopes before opening the browser | The main-process controller has an explicit disclosure TODO | #2921 | Reuse the eventual consent contract; do not copy the current omission into TUI guided setup. |
| Large MCP tool catalogs consume prompt/tool budget | Existing design work is active | #1382 | Contribute there rather than adding a second tool-search design here. |
### #3458 closure requires a retained-root fix
#3462 deliberately mitigated the regeneration storm by escalating the retry delay. It did not prove that memory is bounded. If an object graph survives every failed Runtime Host candidate, slower retries only postpone the same OOM.
The root-cause work should therefore:
1. Reproduce the flapping lifecycle under an instrumented Electron main process and capture comparable heap snapshots across multiple failed candidate generations.
2. Identify the retaining path and its production owner. Candidate maps, capability publication queues, IPC handlers, subscriptions, observers, process handles, and diagnostic buffers are starting hypotheses, not conclusions.
3. Repair that owner's disposal/generation boundary so a closed candidate is unreachable. Add circuit-breaking only if a genuinely external resource cannot be released; it must not replace deletion of an avoidable retained graph.
4. Verify both object-count convergence and a bounded heap plateau during prolonged flapping, then verify that a later healthy candidate can still connect and publish capabilities.
5. Keep #3458 open until those memory measurements exist. Reduced retry frequency, passing unit tests, or fewer log lines alone are not closure evidence.
PR 1 may proceed independently, but it must not add another cross-generation retention path. Its controller should have deterministic disposal, one registration per active connection generation, and a focused churn test showing that listeners, subscriptions, publication queues, and stdio children return to the active-generation baseline after replacement.
### Follow-up boundaries
- Treat MCP alignment errors during Host flapping as a symptom/amplifier until a heap retaining path proves otherwise.
- Validate schema compatibility against observed server inventories before relaxing the Host admission boundary.
- Model MRTR user input as a typed continuation into the existing Host interaction authority; the MCP manager should decode/normalize it, not own the conversation.
- Give reconnect/config reload one production owner. Desktop, TUI, and the standalone provider must not each grow different uncoordinated policies.
- Keep resources/templates UI, MCP Apps, Tasks extensions, catalogs, and cache policy as discovery items until a concrete user scenario and authority boundary justify separate work.
## Acceptance criteria
- `/mcp` is discoverable from TUI help/autocomplete and has localized copy.
- Existing valid `mcp.json` entries appear without migration or duplication.
- A user can add and use both stdio and remote MCP servers from the TUI after the corresponding rollout slice lands.
- All three protocol preferences round-trip for both transports.
- Config changes refresh the Host-visible tool set without restarting the TUI.
- One broken server does not hide healthy servers or their tools.
- TUI exit, Host switch, reconnect, and config refresh leave no orphan stdio child and no stale Client Capability registration.
- Repeated connection replacement leaves only the active generation's listeners, subscriptions, queues, and publication state.
- The TUI does not introduce another periodic reconnect authority.
- Local Sessions select the MCP provider on their initiating TUI connection.
- Remote Sessions retain one exact provider binding even when another Client publishes the same MCP contract.
- Remote publication never reuses or broadens a `terminal-client` credential.
- Desktop behavior and the standalone capability-provider command remain compatible.
- Any new shared module passes the deletion test: removing it would reintroduce the same non-trivial invariant into at least two production callers.
## Non-goals
- Closing #3458 through retry throttling without retained-heap evidence.
- Fixing MRTR continuation, schema compatibility, OAuth consent, or large-catalog search inside the `/mcp` PR series.
- Moving MCP ownership into Runtime Host.
- Adding a second MCP schema or copying the Desktop renderer form into the TUI.
- Migrating Desktop to a new provider lifecycle as part of this work.
- Treating an arbitrary detached provider as the TUI provider without an exact Host binding contract.
- Generalizing this work into a plugin/extension framework.
- Hiding remote capability-provider credential provisioning behind privilege escalation.
## References
- Desktop main-process OOM: #3458
- Reconnect-churn mitigation: #3462
- Optional MCP tool isolation: #3366
- OAuth consent UX: #2921
- Large-catalog tool search: #1382
- Dual-era MCP RFC: #1650
- Stdio dual-era implementation: #3807
- Trusted standalone capability provider: #2625
- [MCP 2026-07-28 changelog](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2026-07-28/changelog.mdx)
- [TUI slash-command catalog](https://github.com/apache/maka/blob/f6bc66a46bb7549e3214fcd52b7f13624c231265/packages/core/src/slash-command-catalog.ts)
- [TUI command adapter](https://github.com/apache/maka/blob/f6bc66a46bb7549e3214fcd52b7f13624c231265/packages/cli/src/pi-tui-runner.ts)
- [Current standalone capability-provider adapter](https://github.com/apache/maka/blob/f6bc66a46bb7549e3214fcd52b7f13624c231265/packages/cli/src/runtime-host-capability-provider-command.ts)
- [Current terminal-client publication policy](https://github.com/apache/maka/blob/f6bc66a46bb7549e3214fcd52b7f13624c231265/packages/cli/src/runtime-host-access-command.ts#L239-L260)
- [Host provider selection](https://github.com/apache/maka/blob/f6bc66a46bb7549e3214fcd52b7f13624c231265/packages/runtime-host/src/server/client-capability-coordinator.ts#L333-L405)
- [Shared MCP config store](https://github.com/apache/maka/blob/f6bc66a46bb7549e3214fcd52b7f13624c231265/packages/storage/src/mcp-config-store.ts)
- [Shared capability publication lifecycle](https://github.com/apache/maka/blob/f6bc66a46bb7549e3214fcd52b7f13624c231265/packages/runtime-host/src/client/capability-provider-service.ts)
- [Desktop client-owned MCP assembly](https://github.com/apache/maka/blob/f6bc66a46bb7549e3214fcd52b7f13624c231265/apps/desktop/src/main/runtime-host-boot.ts)
Contributor guide
Research direction
Begin with the shared slash-command catalog and the existing McpConfigStore, McpClientManager, and Client Capability channel. Scope the first reviewable slice to local `/mcp` status and publication on the TUI connection, then trace the lifecycle through discovery, reconnect, replacement, and shutdown. Done means the saved configuration is reflected in a read-only view and published deterministically without changing Desktop or the standalone provider.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, cli
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100