# `SuperPi.MCP.Manager`
[🔗](https://github.com/oleksandr/super-pi-beam/blob/v0.1.0/lib/super_pi/mcp/manager.ex#L1)

Starts the MCP servers from `mcp_config.json` and exposes their tools to the agent.

The file is the one that `super-pi mcp add|remove|enable|disable` edits (in the global
config dir). Each enabled server is started once under `SuperPi.MCPClientSupervisor`.
Its tools join the tool catalog as `mcp__<server>__<tool>`, so the model sees which server
a tool belongs to and two servers cannot collide. `SuperPi.Tools.execute/3` routes those
names to `call_tool/3`.

Tool calls pass the same `before_tool` pipeline as built-in tools, so the permission
policy applies. Every MCP tool is registered with an `"effects"` list, which is what the
policy decides by (`SuperPi.Plugins.Permissions`): `["net_read"]` when the server annotates
the tool `readOnlyHint: true`, else `["net_write"]`. A read-only tool runs without a prompt
under `accept-edits` and in `plan` mode. Any other tool needs `auto` mode, a prompt answer,
`permissions.allowTools`, or `dangerouslySkipPermissions`. The `network` policy (`deny`,
`allowlist`) applies to every MCP tool, a local stdio one included, because the server may
do anything.

## Configuration

    config :super_pi,
      mcp_clients: true,           # start servers when a conversation starts
      mcp_tool_exposure: :direct,  # or :deferred (found through `tool_search`)
      mcp_startup_timeout_ms: 10_000,
      mcp_call_timeout_ms: 60_000

## Server entries

Each entry under `"mcpServers"` is one of two shapes. Both accept `"disabled": true`.

A local process, over stdio:

    {"command": "npx", "args": ["-y", "some-server"], "env": {"TOKEN": "..."}}

A hosted server, over Streamable HTTP (`url`, or `serverUrl` as other hosts write it):

    {"url": "https://mcp.example.com/mcp",
     "headers": {"Authorization": "Bearer ${EXAMPLE_TOKEN}"}}

`${NAME}` in a header value is replaced from the environment at start-up. A name that is not
set fails that server with `{:missing_env, names}`. OAuth is not supported. See
`SuperPi.MCP.Transport.StreamableHTTP` for the session and fallback behaviour. An entry with
both `command` and `url` runs the command.

## Scopes

A server belongs to a scope. The servers from `mcp_config.json` are `:global`: every
conversation sees their tools. The servers that a client hands over at run time
(`add_servers/2`) belong to one conversation. Their tools stay out of the shared catalog, only
that conversation declares them, and only a call from that conversation reaches them. Two
conversations that hand over a server with the same name get two processes, each with its own
`env` and `headers`.

# `add_servers`

```elixir
@spec add_servers(term(), [map()]) :: [map()]
```

Starts MCP servers that a client hands over at run time, in the ACP `mcpServers` shape:

- stdio: `%{"name", "command", "args", "env" => [%{"name", "value"}]}`
- HTTP: `%{"type" => "http" | "sse", "name", "url", "headers" => [%{"name", "value"}]}`

The servers belong to `conversation_id` (see "Scopes"), and their tools are declared in its
agent document. A server that is already running for that conversation is left alone, and one
that fails to start is logged and skipped. Returns the same list as `ensure_started/1`.

# `call_tool`

```elixir
@spec call_tool(String.t(), map(), term()) :: map()
```

Runs an MCP tool by its exposed name and returns a `SuperPi.Tools` result map. `scope` is the
calling conversation: its own servers come first, then the `:global` ones. A tool of another
conversation's server is not available.
An unknown name, a stopped server, or a protocol error is an `is_error` result, never a raise.

# `declare_tools`

```elixir
@spec declare_tools(term()) :: :ok
```

Adds the MCP tools that `conversation_id` may use, and does not declare yet, to its agent
document: the `:global` tools that the catalog shows the model, and the tools of its own servers.

# `ensure_started`

```elixir
@spec ensure_started(keyword()) :: [map()]
```

Starts every enabled stdio server that is not running yet and registers its tools.
Returns `[%{server: name, status: :started | :running | {:error, reason}, tools: count}]`.
Servers that fail to start are logged and skipped. The call is safe to repeat.

# `exposed_name`

The exposed tool name for a server and tool: `mcp__<server>__<tool>`, characters limited to `[A-Za-z0-9_-]`.

# `stop_all`

Stops every MCP server and removes their tools from the catalog.

# `stop_scope`

```elixir
@spec stop_scope(term()) :: :ok
```

Stops the servers of one conversation and forgets their tools. The tools stay declared in its
agent document, so a call fails as "not available" until the client hands the servers over again.

# `tool_names`

Names of the tools that the servers of `scope` provide (`mcp__<server>__<tool>`).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
