# Manifest Allowlist vs MCP Server Registry

> **Status:** documents the current state of the runtime.
> **Crates:** `librefang-types`, `librefang-kernel`, `librefang-extensions`, `librefang-api`.

The split between `AgentManifest.mcp_servers` (per-agent allowlist) and
`KernelConfig.mcp_servers` (global server registry) reads as duplication
on first encounter. The two fields hold different things and live at
different layers. This page records the boundary, the resolution rule,
and which crate is allowed to mutate which side.

## The three values

| Where | Type | What it holds |
|---|---|---|
| `AgentManifest.mcp_servers` | `Vec<String>` (server **names**) | Per-agent **allowlist**. Empty = "every configured server is visible to this agent". Lives in `agent.toml`. |
| `KernelConfig.mcp_servers` | `Vec<McpServerConfigEntry>` (full configs) | Global **registry** of installed servers — transport, env, OAuth, taint policy, headers. Lives in `~/.librefang/config.toml`. |
| `kernel.mcp.effective_mcp_servers` | `RwLock<Vec<McpServerConfigEntry>>` | Hot-reloadable runtime mirror of `KernelConfig.mcp_servers`. Bumped together with `mcp_generation` so cached prompt summaries invalidate atomically. |

References:

- `crates/librefang-types/src/agent.rs:803` — manifest field.
- `crates/librefang-kernel/src/kernel/subsystems/mcp.rs:63` — runtime mirror.
- `crates/librefang-kernel/src/kernel/mcp_setup.rs:324` — `reload_mcp_servers` copies `cfg.mcp_servers` into `effective_mcp_servers` on hot-reload.

## Resolution

The registry mirror is **not** pre-intersected with any agent's
allowlist. Intersection happens at the prompt boundary, per agent, per
turn:

```rust
render_mcp_summary(tool_names, configured_servers, mcp_allowlist)
```

— `crates/librefang-kernel/src/kernel/prompt_context.rs:281`. The
`configured_servers` argument is the registry snapshot
(`effective_mcp_servers`); the `mcp_allowlist` argument is the agent's
manifest field. An empty allowlist means every configured server is
visible to this agent.

The same pattern is used elsewhere any time a per-agent view of MCP is
needed (tool list rendering, route handlers): pull the registry
snapshot, then filter through the manifest allowlist. **Never store the
intersection.**

## Why the split is intentional

- **Different change frequency.** The registry is rewritten when an
  operator installs / uninstalls / reconfigures a server (rare, global,
  hot-reloaded). The allowlist changes when an operator decides which
  agent gets which server (frequent, per-agent, no reconnection).
- **Different blast radius.** Removing a server from the registry tears
  down its connection. Removing it from one agent's allowlist only
  hides it from that agent's prompt — connection stays up for other
  agents.
- **Different ownership.** The registry is what `librefang-extensions`
  manages (catalog → installer → `[[mcp_servers]]` write). The
  allowlist is what the agent author edits in `agent.toml`. Conflating
  the two would put extension installers into the agent-authoring
  surface and vice versa.

## Control-plane boundary for `librefang-extensions`

The extensions crate is the **only** code path that should write the
registry. It owns:

- `catalog::McpCatalog` — read-only set of templates loaded from
  `~/.librefang/mcp/catalog/*.toml`.
- `installer::install_integration` — pure transform from a catalog
  entry + provided credentials to a fresh `McpServerConfigEntry`. Does
  not write — caller persists the entry into `config.toml` and triggers
  a kernel reload.
- `credentials::CredentialResolver` — pulls secrets out of the vault /
  env / dotenv before they're baked into the registry entry.
- `vault`, `oauth`, `health`, `dotenv`, `http_client` — leaf modules
  consumed by the above.

The agent-side allowlist never flows through this crate. Mutating it is
a kernel API operation:
`KernelApi::set_agent_mcp_servers(agent_id, Vec<String>)` —
`crates/librefang-kernel/src/kernel_api.rs:232` — which writes back to
the agent's manifest on disk and bumps the per-agent reload generation.

## HTTP surfaces

- `GET /api/agents/{id}/mcp_servers` — returns the agent's allowlist
  plus the resolved effective list (registry filtered through
  allowlist). `routes/agents.rs:4091`.
- `PUT /api/agents/{id}/mcp_servers` — replaces the agent's allowlist.
  `routes/agents.rs:4162`. Does **not** touch the registry.
- `GET /api/mcp/servers` — registry view. `routes/skills.rs:3972`.
  Used by the dashboard's Integrations page to render install /
  uninstall.

## What this means for new code

- **Adding an MCP server to an agent.** Write to the allowlist via
  `set_agent_mcp_servers`. Do **not** mutate the registry.
- **Installing a new server template.** Go through
  `install_integration`, persist the returned `McpServerConfigEntry`,
  then call `reload_mcp_servers`. Do **not** write to any agent
  manifest.
- **Reading "what MCP tools does agent X see right now".** Snapshot
  the registry from `effective_mcp_servers`, filter through the
  agent's manifest allowlist. The filtered view is never cached
  across turns beyond the `mcp_summary_cache`. That cache is a
  `DashMap<String, (u64, String)>` keyed on the sorted allowlist;
  each entry stores `(mcp_generation, rendered_string)`. A registry
  hot-reload bumps `mcp_generation`, so the next read sees the stored
  generation no longer matches and re-renders, overwriting the stale
  entry — `prompt_context.rs:252-285`.
