# Connect MCP servers

Attach a [Model Context Protocol](https://modelcontextprotocol.io) server to a
ToolRegistry as an `mcp` handler. The server announces its tools and the runtime
lists them when the agent starts, so you write no `tool` definition.

## Prerequisites

- A ToolRegistry, referenced from the AgentRuntime through `toolRegistryRef`.
- An MCP server that speaks SSE or Streamable HTTP and that the agent pod can
  reach. The runtime refuses an endpoint whose host is a link-local or
  cloud-metadata address (such as `169.254.169.254`), and refuses redirects to
  another scheme, host or port. In-cluster Service names and private addresses are allowed.

## Add the handler

1. Add an `mcp` handler to `spec.handlers` and pick a transport.

   For `sse`, point `endpoint` at the SSE URL:

   ```yaml
   - name: mcp-server
     type: mcp
     mcpConfig:
       transport: sse
       endpoint: http://mcp-server.tools.svc.cluster.local:8080/sse
   ```

   For `streamable-http`, point `endpoint` at the HTTP URL:

   ```yaml
   - name: mcp-http
     type: mcp
     mcpConfig:
       transport: streamable-http
       endpoint: http://mcp-server.tools.svc.cluster.local:8080/mcp
   ```

   The dashboard's ToolRegistry dialog supports both transports. It sets only
   `transport` and `endpoint`; every other step on this page is YAML.

2. Optionally narrow the tools the agent can see with `toolFilter`:

   ```yaml
   mcpConfig:
     transport: sse
     endpoint: http://mcp-github.tools.svc.cluster.local:8080/sse
     toolFilter:
       allowlist: [search_issues, get_pull_request]   # illustrative tool names
   ```

   An `allowlist` exposes only the names it lists. With no `allowlist`, every tool
   is exposed except those in `blocklist`. When both are set, `blocklist` is
   ignored.

3. Optionally retry failed tool calls:

   ```yaml
   mcpConfig:
     retryPolicy:
       maxAttempts: 3          # 1-10; 1 means no retries
       initialBackoff: "100ms"
       backoffMultiplier: "2.0"
       maxBackoff: "30s"
   ```

   Without a `retryPolicy`, each call is attempted once. This policy is separate
   from session recovery. When the session is lost, the runtime reconnects,
   lists the tools again and retries the call once, whatever the policy says.

4. Add authentication if the server needs it. The credential is sent as a
   header on both transports:

   ```yaml
   auth:
     type: bearer
     secretRef:
       name: mcp-credentials
       key: token
   ```

   See [Authenticate tools](/how-to/tools/authenticate-tools/) for the other auth
   types.

5. Declare the tools in the PromptPack. The runtime offers the model only the
   tools that a prompt lists in `tools`, so add each MCP tool name to the prompt
   that should use it and republish the pack. Tool names are one namespace
   across the registry: if two handlers expose the same name, the first handler
   (in name order) keeps it. Use `toolFilter` on either handler to resolve the
   clash.

## Running an MCP server locally

There is no `stdio` transport: every Omnia runtime image is
`distroless/static`, with no shell, `npx` or `uvx` to spawn a subprocess. A
registry that sets `transport: stdio` is rejected.

To give an agent local tooling, use a codegen sandbox. Declare a sandbox in
`spec.sandboxes`, then give the `mcp` handler a `sandbox` block that names it and
the class port to dial. On a sandbox handler, `mcpConfig.endpoint` is a path on
that port, not a URL, and `toolFilter.allowlist` is required. Set
`spec.sandbox.enabled` on the AgentRuntime:

```yaml
spec:
  sandboxes:
    - name: workspace
      class:
        name: codegen-default
        kind: ClusterSandboxClass
  handlers:
    - name: workspace
      type: mcp
      sandbox:
        name: workspace
        port: mcp
      mcpConfig:
        transport: sse
        endpoint: /sse
        toolFilter:
          allowlist: [Read, Write, Edit, Glob, Grep, Bash]
```

The operator creates a `SandboxClaim` from the class for each conversation that
uses the handler. The allowlist is required because the sandbox is not dialled at
start-up, so it is the only list of the handler's tools. The handler's `port` must
be one the class declares. See [Sandboxes](/reference/core/toolregistry/#sandboxes)
for the fields and [Codegen sandbox isolation](/explanation/security/codegen-sandbox/)
for what the sandbox can reach.

To use a third-party server that only speaks stdio, wrap it in a Deployment that
bridges stdio to SSE or Streamable HTTP, and point a handler at that Service.

## Verify

Check the registry and the handler's placeholder tool:

```bash
kubectl -n <namespace> get toolregistry <name> -o yaml
```

`status.discoveredTools` holds one placeholder entry per `mcp` handler, named
after the handler. The individual tools are listed by the runtime when the agent
starts, so they do not appear there. `Available` reflects a valid configuration,
not reachability, unless you enable `spec.probe`, which TCP-dials the endpoint.

For a sandbox handler, also check the `SandboxesValid` condition. It is `False`
when the sandbox names a class that does not exist, or a port the class does not
declare.

Then send the agent a message that needs the tool. To see why the model was not
offered one, read the runtime container logs: it logs the registry tools that no
prompt in the pack declares.

## Troubleshooting

- The registry rejects the handler with a link-local or metadata message: the
  endpoint host is such an address. Use the server's Service name or a private IP.
- An agent never calls an MCP tool: the pack's `tools` list does not name it, or
  `toolFilter` removes it.
- A sandbox handler is rejected: it needs an `endpoint` that starts with `/`, and
  a non-empty `toolFilter.allowlist`.

## Related pages

- [ToolRegistry reference](/reference/core/toolregistry/)
- [Authenticate tools](/how-to/tools/authenticate-tools/)
- [Test tools](/how-to/tools/test-tools/)