# MCP Setup Agent

A sub-agent that walks the user through installing, configuring, and
connecting an MCP server from one of the upstream registries
(`mcp_registry::registries`: Smithery, modelcontextprotocol/registry).

**Status: implemented** (issue #3039). The agent archetype, its five
`mcp_setup_*` tools, the opaque-secret request/submit flow, and the
`install_and_connect` commit path are all live. The orchestrator delegates to
it via the `setup_mcp_server` delegate (the `mcp_setup` entry in
`src/openhuman/agent_registry/agents/orchestrator/agent.toml`), so a chat turn
like *"set up the Notion MCP server"* routes here. The sections below describe
how the flow works; paths reflect the shipped layout.

---

## Goal

A non-technical user says *"set up the Notion MCP server for me"*. The
agent:

1. Browses the enabled registries, finds the candidate, summarises it.
2. Asks for any secrets the server requires (API keys, OAuth tokens, …)
   **without ever pulling the values into the LLM context**.
3. Test-connects with the collected secrets, surfaces errors, lets the
   user retry / change values.
4. On success, persists the install and the secrets, runs boot-spawn for
   this one server, returns connection status + the tool list now
   available to the main agent.

The agent owns the conversation; the core owns the secrets, the
subprocess, and the persistence.

---

## Tool surface

Five tools registered behind a `mcp_setup_*` namespace. All tool inputs
and outputs are JSON; secret values **never** appear in either direction.

| Tool | Input | Output | Notes |
| --- | --- | --- | --- |
| `mcp_setup_search` | `{ query?, page?, page_size?, source? }` | `{ servers: [Summary], total_pages }` | Thin wrapper over `mcp_registry::registry::registry_search`. `source` optionally scopes to one upstream. |
| `mcp_setup_get` | `{ qualified_name }` | `{ detail, required_env_keys }` | Wraps `registry_get`; pre-computes `required_env_keys` from the `config_schema` (same logic as `ops::collect_required_env_keys`). |
| `mcp_setup_request_secret` | `{ key_name, prompt }` | `{ ref: "secret://<opaque>" }` | Triggers an out-of-band UI prompt. Returns an opaque ref; raw value is held in a process-local in-memory map keyed by ref. |
| `mcp_setup_test_connection` | `{ qualified_name, env_refs: { KEY: "secret://…" } }` | `{ ok, tools?: [McpTool], error?: string }` | Spawns the candidate subprocess in a **scratch** workspace, resolves refs to values just-in-time, runs `initialize` + `tools/list`, tears it down. No persistence. |
| `mcp_setup_install_and_connect` | `{ qualified_name, env_refs }` | `{ server_id, status, tools: [McpTool] }` | Resolves refs, persists the install + `mcp_client_env` rows, calls `connections::connect`. Refs are always consumed (removed from the in-memory map) regardless of outcome — on failure the agent must re-prompt via `mcp_setup_request_secret`. |

---

## Secret flow — opaque refs

The hard requirement: **raw secret values must not enter LLM context**.
Opaque refs solve this cleanly:

```
agent: mcp_setup_request_secret({ key_name: "NOTION_API_KEY", prompt: "Notion integration token" })
core:  → pushes prompt to UI; user types into a native input box
core:  ← receives value, stores in SETUP_SECRETS: HashMap<RefId, String>
core:  → returns { ref: "secret://7c9f2e" }   ← the agent sees only this
agent: mcp_setup_test_connection({
         qualified_name: "@notion/server",
         env_refs: { "NOTION_API_KEY": "secret://7c9f2e" }
       })
core:  → for each ref, look up the value in SETUP_SECRETS, build the env
         vector, spawn, init, list_tools, tear down
core:  ← returns { ok: true, tools: [...] }   ← still no raw value to agent
```

Lifecycle of `SETUP_SECRETS`:

- Process-local `OnceLock<RwLock<HashMap<RefId, SecretEntry>>>`.
- Entries TTL out after, say, 15 min (defends against stranded secrets if
  the conversation is abandoned mid-flow).
- `mcp_setup_install_and_connect` consumes refs regardless of outcome:
  pulls each value, writes it to the `mcp_client_env` table (existing
  persistence, already keyed by `server_id`), and removes the ref from
  the in-memory map. On failure the agent should re-prompt via
  `mcp_setup_request_secret` to collect fresh refs for a retry.
- On core shutdown the map is dropped — refs do not survive restart.

`RefId` is a short random hex string. **No structure or hint of the
underlying value** so the agent has nothing useful to leak even if it
tries.

### Why not just take key names?

Considered (option 2 in the original AskUserQuestion). Rejected because:

- The agent can't decide between values it just collected — e.g. trying
  two different tokens to pick the one that works requires distinguishing
  them, which requires handles.
- Tying secrets to the `(server_id, key)` pair too early means a failed
  test-connect leaves stale rows in `mcp_client_env` for an
  uninstall-rolled-back server.

Opaque refs give the agent enough handle to iterate without exposing
values.

---

## Where the agent lives

Follows the existing sub-agent pattern (`src/openhuman/agent_registry/`):

- Archetype TOML at `src/openhuman/agent_registry/agents/mcp_setup/agent.toml`
  (loaded by the agent registry loader).
- Prompt + tool allowlist scoped tight: only the five `mcp_setup_*` tools
  plus `ask_user_clarification`. **No** general filesystem, network, or shell
  tools — the agent shouldn't be able to exfiltrate a leaked ref even if one
  shows up. (`submit_secret` is intentionally NOT in the agent allowlist — the
  UI calls it out-of-band via the socket bridge.)
- Triggered by the orchestrator's `setup_mcp_server` delegate (the `mcp_setup`
  entry in `agent_registry/agents/orchestrator/agent.toml`), or directly from
  chat when the user asks to add/install/set up an MCP server.

---

## Implementation outline

Following the project's `Specify → Rust → JSON-RPC → UI → tests` flow:

1. **Rust core** (in `src/openhuman/mcp_registry/`):
   - New module `setup.rs` owning `SETUP_SECRETS` (in-memory ref map with
     TTL) and helpers `mint_ref`, `resolve_refs(env_refs) -> Vec<(K,V)>`,
     `consume_refs(env_refs)`.
   - New module `setup_ops.rs` with the four handlers.
   - Wire schemas in `schemas.rs`, controllers in `core/all.rs`.
2. **Tool-side bridge** so the agent harness sees the four tools as
   regular tool defs. Reuse the controller-to-tool generator already
   used elsewhere.
3. **UI**: out-of-band secret prompt component (probably a `chat`-pinned
   modal listening on a new socket event `mcp_setup_request_secret`),
   submit POSTs the value to a Tauri command that calls into core to
   register the ref.
4. **Archetype** + system prompt at
   `src/openhuman/agent_registry/agents/mcp_setup/agent.toml`.
5. **Tests**:
   - Unit: ref lifecycle (mint → resolve → consume → TTL expiry).
   - Integration (`tests/mcp_registry_e2e.rs` style): full flow against
     the existing `test-mcp-stub` binary, asserting refs vanish after
     install + that test-connect failures leave refs intact.

---

## Open questions for the implementer

- TTL value — 15 min is a guess; calibrate against typical install flow.
- Should `test_connection` accept a partial env_refs (some refs, some
  literal-by-name) for iteration? Current design says refs only, which
  forces consistency.
- The official MCP registry returns servers with **multiple package
  ecosystems** (`packages: [{ registry_name: "npm" | "pypi" | … }]`). The
  setup agent needs to either pick one or ask the user. Add a
  `package_choice` step or default to npm?
- Telemetry: log `mcp_setup_*` calls (`tracing::info!` is fine) but
  never log ref values, never log env values, only key names.

---

## Anti-goals

- The setup agent is **not** a generic "ask user for any data" surface.
  Its prompt tool is scoped to MCP env values, full stop.
- It does **not** persist anything until `install_and_connect` succeeds.
  No half-installed rows in `mcp_servers` or `mcp_client_env`.
- It does **not** read back secrets. Once persisted into `mcp_client_env`
  they are write-only from the agent's perspective; only the subprocess
  spawn path in `connections::connect` reads them.
