# AgentOS as MCP Server (/agent-os/mcp/mcp)



Set `mcp=True` to serve an MCP server at `/mcp` alongside the REST API. ChatGPT and Claude connect as hosted custom connectors. Claude Code, Claude Desktop, Codex, and Cursor connect from your machine through Agno Connect.

```python title="mcp_server.py"
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.anthropic import Claude
from agno.os import AgentOS

db = SqliteDb(db_file="tmp/agentos.db")

assistant = Agent(
    id="assistant",
    name="Assistant",
    model=Claude(id="claude-sonnet-4-5"),
    db=db,
    markdown=True,
)

agent_os = AgentOS(
    agents=[assistant],
    mcp=True,
)

app = agent_os.get_app()

if __name__ == "__main__":
    # MCP server available at http://localhost:7777/mcp
    agent_os.serve(app="mcp_server:app")
```

The MCP server needs `fastmcp` from the `mcp` extra. Building the app without it raises an `ImportError`. Install it along with the example's other dependencies (the `os` extra for AgentOS and `anthropic` for the model):

```bash
pip install -U "agno[os,mcp]" anthropic
```

When MCP is enabled, the startup panel shows the MCP endpoint below the OS address. It uses `MCPConfig.server_card_url` when set; otherwise it combines the serving address with the configured MCP path.

## Connect MCP Clients [#connect-mcp-clients]

The server uses the Streamable HTTP transport. Choose the connection method based on where the client runs.

### ChatGPT and Claude [#chatgpt-and-claude]

Deploy AgentOS to a public HTTPS URL. In ChatGPT or Claude, open **Settings → Connectors → Add custom connector** and enter:

```text
https://<your-domain>/mcp
```

The endpoint must be publicly reachable over HTTPS. For ChatGPT and Claude, a secured MCP endpoint must use OAuth. Their connector UIs do not accept bearer tokens. Hosted apps cannot reach an AgentOS running only on localhost.

Configure MCP OAuth on the AgentOS:

```python
from agno.os import AgentOSBuiltinAuth

agent_os = AgentOS(
    agents=[assistant],
    db=db,
    mcp=True,
    mcp_auth=AgentOSBuiltinAuth.from_env(),
)
```

Set `AGENTOS_URL` to the exact public origin and set `MCP_CONNECT_SECRET`. Leave the connector's optional OAuth client ID and client secret fields empty, then enter `MCP_CONNECT_SECRET` on the consent page. See the [built-in OAuth example](/examples/agent-os/mcp/oauth-builtin) for the complete setup.

### Local MCP Clients [#local-mcp-clients]

Run Agno Connect on the machine where Claude Code, Claude Desktop, Codex, or Cursor is installed:

```bash
uvx agno connect --url http://localhost:7777
```

Agno Connect discovers `/mcp`, configures each detected client, handles the configured authentication flow, and verifies the connection with an MCP handshake. Pass the AgentOS base URL without `/mcp`. Use the deployed base URL to connect local clients to a remote AgentOS. See [`agno connect`](/cli/connect) for client selection, credentials, and project-scoped configuration.

An Agno agent can operate the server through `MCPTools`. See [Enable AgentOS MCP](/agent-os/usage/mcp/enable-mcp-example) for a complete server and client pair.

## Built-in Tools [#built-in-tools]

The 8 built-in tools are scoped to what an LLM client needs to operate the instance. Session writes and memory CRUD stay on the REST API; register anything else as a [custom tool](#custom-tools).

| Tool                 | Purpose                                                                                                                                          | Key parameters                                                                                |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| `get_agentos_config` | Discover the agents, teams, and workflows available to run, plus database IDs. Call first.                                                       | None                                                                                          |
| `run_agent`          | Run an agent and return its response.                                                                                                            | `agent_id`, `message`, optional `session_id` and `user_id`                                    |
| `run_team`           | Run a team. Same session and pause semantics as `run_agent`.                                                                                     | `team_id`, `message`, optional `session_id` and `user_id`                                     |
| `run_workflow`       | Run a workflow, reporting progress per step.                                                                                                     | `workflow_id`, `message`, optional `session_id` and `user_id`                                 |
| `continue_run`       | Resume a `PAUSED` run after resolving its requirements (human-in-the-loop).                                                                      | `run_id`, `session_id`, exactly one of `agent_id` / `team_id` / `workflow_id`, `requirements` |
| `cancel_run`         | Request cancellation of a run.                                                                                                                   | `run_id`, `session_id`, exactly one of `agent_id` / `team_id` / `workflow_id`                 |
| `get_sessions`       | List past sessions, newest first. Read-only.                                                                                                     | `session_type`, `component_id`, `user_id`, `session_name`, `limit`, `page`, `db_id`           |
| `get_session_runs`   | Read a session's history: each run's input and answer. Pass `run_id` for that one run in full detail (complete transcript, events, and metrics). | `session_id`, optional `run_id`, `session_type`, `db_id`                                      |

Behavior shared by the run tools:

* Omit `session_id` to start a fresh session; the new ID comes back in `structuredContent`. Pass a `session_id` from `get_sessions` to continue that conversation.
* Long runs send MCP progress notifications: tool calls for agents and teams, step counts for workflows.
* A result with `status=PAUSED` carries its unresolved requirements. Resolve them and pass them to `continue_run`.
* `db_id` on the session tools is only needed when `get_agentos_config` lists multiple databases.

## MCPConfig [#mcpconfig]

Pass `mcp=MCPConfig(...)` to register custom tools, scope the built-ins, gate the server, and add transport protection:

```python
from agno.os import AgentOS, MCPConfig

OWNER_IDS = {"owner@example.com"}

agent_os = AgentOS(
    agents=[assistant],
    mcp=MCPConfig(
        tools=[ask_workspace],                           # register custom tools
        default_tools=True,                              # also serve the built-in tools
        exclude_tags={"session"},                        # drop the read-only session tools
        authorize=lambda user_id: user_id in OWNER_IDS,  # 401 non-owners before any tool runs
        allowed_hosts=["my-app.example.com"],            # DNS-rebinding protection
    ),
)
```

| Option                          | Default     | Description                                                                                                                                                                                                                                                                                                                                           |
| ------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` / `version`              | `None`      | Override the MCP server name/version; otherwise use the AgentOS identity.                                                                                                                                                                                                                                                                             |
| `instructions`                  | `None`      | Instructions advertised to MCP clients about using the server.                                                                                                                                                                                                                                                                                        |
| `server_card`                   | `True`      | Publish the discovery card at `/mcp/server-card`.                                                                                                                                                                                                                                                                                                     |
| `server_card_url`               | `None`      | Explicit public MCP URL advertised by the card, useful behind a proxy.                                                                                                                                                                                                                                                                                |
| `stateless`                     | `False`     | Disable transport sessions for legacy MCP clients (protocol `2025-11-25` and earlier). Requests on protocol `2026-07-28` are always sessionless. See [Multiple replicas](#multiple-replicas).                                                                                                                                                         |
| `tools`                         | `None`      | Publish custom callables, Agno tools/`Function`s, toolkits, agents, teams, workflows, or `component.as_tool(...)` entries. They share the `/mcp` mount, lifespan, and auth with the built-ins.                                                                                                                                                        |
| `default_tools`                 | `False`     | Add the 8 built-in tools to the server. `AgentOS(mcp=True)` always serves them.                                                                                                                                                                                                                                                                       |
| `lifecycle_tools`               | `False`     | Add component-scoped `continue_run` and `cancel_run` tools when `tools` publishes agents, teams, or workflows. Additive: `False` does not remove the pair enabled by `default_tools=True`.                                                                                                                                                            |
| `include_tags` / `exclude_tags` | `None`      | Scope the enabled built-ins by tag: `"core"` covers discovery, run, continue, and cancel tools; `"session"` covers the read-only session tools; `"lifecycle"` selects the continue and cancel pair. Tags do not enable built-ins on their own; set `default_tools=True` first. `exclude_tags={"lifecycle"}` also disables the `lifecycle_tools` pair. |
| `result_mode`                   | `"trimmed"` | How the run tools serialize results. See [Result modes](#result-modes).                                                                                                                                                                                                                                                                               |
| `authorize`                     | `None`      | Per-call gate `fn(user_id) -> bool`. Runs after token verification; returning `False` rejects the request with 401 before any tool or model runs.                                                                                                                                                                                                     |
| `allowed_hosts`                 | `None`      | Host allowlist for DNS-rebinding protection. See [Transport Security](#transport-security).                                                                                                                                                                                                                                                           |
| `allowed_origins`               | `None`      | Extra exact origins to allow when they are served from hosts outside `allowed_hosts`.                                                                                                                                                                                                                                                                 |
| `middleware`                    | `None`      | `starlette.middleware.Middleware` instances added to the MCP app.                                                                                                                                                                                                                                                                                     |

`MCPConfig()` with no `tools` and `default_tools=False` raises a `ValueError` at construction, because it would mount a server with zero tools. Pass `tools=[...]`, set `default_tools=True`, or use `AgentOS(mcp=True)`. Setting `include_tags` without `default_tools=True` logs a warning, because those tags register nothing.

### Choose the Published Tools [#choose-the-published-tools]

An `MCPConfig` publishes only the tools you list unless you opt in to more:

| Configuration                                  | Tools published                                                                                 |
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `MCPConfig(tools=[...])`                       | Listed tools only                                                                               |
| `MCPConfig(tools=[...], lifecycle_tools=True)` | Listed tools, plus `continue_run` and `cancel_run` for the exposed agents, teams, and workflows |
| `MCPConfig(tools=[...], default_tools=True)`   | Listed tools, plus all 8 built-in tools                                                         |
| `MCPConfig(default_tools=True, ...)`           | All 8 built-in tools, with `MCPConfig` settings such as `name` or `stateless`                   |
| `AgentOS(mcp=True)`                            | All 8 built-in tools                                                                            |

Ordinary follow-ups in the same conversation do not need lifecycle tools. Enable them when MCP clients must resume runs paused for human approval or request cancellation. The added pair only acts on runs of the components published in `tools`, and each call is scope-checked like the tool that started the run. Without lifecycle tools, a paused result tells the client to resume the run over the REST API or enable `lifecycle_tools=True`.

### Result Modes [#result-modes]

MCP tool results land in the consuming model's context window, so the default keeps them small.

| Mode                  | Result                                                                                                                                                                                                       | Use when                                             |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- |
| `"trimmed"` (default) | Answer text plus generated media as MCP content blocks. `structuredContent` carries the mirrored answer, `run_id`, `session_id`, `status`, the owning component ID, and unresolved requirements when paused. | The client is an LLM frontend.                       |
| `"full"`              | `structuredContent` is the run's complete `to_dict()`, with media base64-encoded.                                                                                                                            | Programmatic clients that need the whole run object. |

### Custom Tools [#custom-tools]

```python
from agno.tools import tool


@tool(
    name="ask_workspace",
    description="Ask the workspace agent a question and get an answer",
)
async def ask_workspace(question: str, user_id: str) -> str:
    response = await workspace_agent.arun(question, user_id=user_id)
    return response.content or ""
```

Declare a `user_id` parameter and AgentOS fills it with the authenticated caller's ID (the JWT subject). The parameter is hidden from the client-facing tool schema, so callers cannot spoof it. Tools that need the full request can declare a FastMCP `Context` parameter, which FastMCP injects natively.

`MCPConfig(tools=[...])` exposes only the listed tools. Add `default_tools=True` to serve the built-ins alongside them, or `lifecycle_tools=True` to add continuation and cancellation for published agents, teams, and workflows.

## Upgrade from Agno 3.0 [#upgrade-from-agno-30]

Agno 3.1.0 changed both `default_tools` and `lifecycle_tools` to default to `False`. `AgentOS(mcp=True)` is unchanged and still serves all 8 built-in tools.

| Existing configuration                                                             | Change                                                   |
| ---------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `MCPConfig()` with only settings such as `name`, `stateless`, `authorize`, or tags | Add `default_tools=True`. Bare `MCPConfig()` now raises. |
| `MCPConfig(tools=[...])` that relied on the built-in tools                         | Add `default_tools=True`.                                |
| Exposed components whose clients resume paused runs or cancel runs over MCP        | Add `lifecycle_tools=True`.                              |
| `MCPConfig(tools=[...], default_tools=False, lifecycle_tools=False)`               | No change needed. Both flags are now redundant.          |

```python
# Publish exactly this tool.
mcp = MCPConfig(tools=[product_agent.as_tool(name="ask_product_agent")])

# Add continuation and cancellation for exposed components.
mcp = MCPConfig(tools=[approval_agent.as_tool(name="ask_approval_agent")], lifecycle_tools=True)

# Keep the full built-in surface with custom server settings.
mcp = MCPConfig(default_tools=True, name="Support", stateless=True)
```

The deprecated `enable_builtin_tools` alias follows the same default.

## Discovery [#discovery]

With the default `server_card=True`, `GET /mcp/server-card` returns the server's identity, tools, and connection information. Browser-style requests to `/mcp` redirect to this card. The card includes tool descriptions and input schemas and remains public when tool execution requires authentication. Disable it with `server_card=False` if those descriptions should not be published.

Set `server_card_url` to the public MCP endpoint when deploying behind a proxy. This makes the advertised URL explicit instead of deriving it from request headers.

## Multiple replicas [#multiple-replicas]

Requests on MCP protocol `2026-07-28` are sessionless: they need no initialization handshake or `Mcp-Session-Id`, so any replica can answer them. Clients on protocol `2025-11-25` and earlier use transport sessions by default. Set `stateless=True` when those legacy clients may reach different replicas without session affinity.

| Client protocol          | Default                                                | `stateless=True`     |
| ------------------------ | ------------------------------------------------------ | -------------------- |
| `2026-07-28`             | No transport session                                   | No transport session |
| `2025-11-25` and earlier | Transport session, unless FastMCP settings override it | No transport session |

Legacy stateless mode loses server-to-client requests and SSE resumability. Request-scoped progress notifications still work. AgentOS forwards only `stateless=True` to FastMCP, so leaving it `False` keeps `fastmcp.settings.stateless_http`, including the `FASTMCP_STATELESS_HTTP` environment override, in control.

The flag does not change Agno's conversation `session_id`, persisted history, or run state. Keep authoritative application state in shared storage and configure cross-replica execution separately.

See [Public Surface](/agent-os/public-surface) for a public MCP deployment with explicit tools and shared request quotas.

## Authentication [#authentication]

AgentOS applies its configured authentication to the REST routes and MCP server. Without a security key, JWT authorization, or `mcp_auth`, `/mcp` accepts anonymous calls and restricts requests to localhost hosts by default. Configure authentication before deploying the endpoint.

| Mode                   | Server setup                                             | Client credential                      |
| ---------------------- | -------------------------------------------------------- | -------------------------------------- |
| Open local development | `mcp=True` with no authentication configured             | None                                   |
| Security key           | `export OS_SECURITY_KEY="your-key"`                      | `Authorization: Bearer <security key>` |
| Service account token  | Issue via `POST /service-accounts` (requires a database) | `Authorization: Bearer agno_pat_...`   |
| JWT                    | `AgentOS(authorization=True, ...)`                       | `Authorization: Bearer <JWT>`          |
| OAuth                  | `AgentOS(mcp=True, mcp_auth=...)`                        | OAuth access token                     |

With `authorization=True`, each tool call is checked against the scopes of its equivalent REST route: `run_agent` requires the same scopes as `POST /agents/{id}/runs`, `get_sessions` the same as `GET /sessions`. See [Security & Auth](/agent-os/security/overview).

The `authorize` gate on `MCPConfig` layers on top of authentication and can reject callers per request. It receives a verified `user_id` from JWT, service-account, or `mcp_auth` credentials. Open and security-key requests carry no user identity, so the gate receives `None`. Make sure your gate handles `None`, and return `False` to reject callers without an identity.

## Transport Security [#transport-security]

fastmcp's built-in Host/Origin guard is disabled on `/mcp`, so `MCPConfig.allowed_hosts` acts as the transport guard instead:

* Left unset, the default depends on your auth mode. A server with required authentication (a security key, JWT, or `mcp_auth`) skips host validation, because every request already has to prove itself. An open server gets localhost-only validation, since that is exactly the setup DNS rebinding attacks target.
* When set, the request `Host` (and `Origin`, when present) must match your list or the localhost defaults (`localhost`, `127.0.0.1`, `[::1]`). Anything else is rejected with 400 before it reaches the MCP machinery. `*.example.com` wildcard patterns are supported.

This is DNS-rebinding protection: it stops a malicious web page from driving an always-on local MCP server through a rebound DNS name. List only your deploy or tunnel host; localhost works out of the box.

## Developer Resources [#developer-resources]

* [Serve AgentOS over MCP example](/examples/agent-os/mcp/basic)
* [Built-in OAuth example](/examples/agent-os/mcp/oauth-builtin)
* [AuthKit OAuth example](/examples/agent-os/mcp/oauth-authkit)
* [Connect local clients with `agno connect`](/cli/connect)
* [MCPTools within AgentOS](/agent-os/mcp/tools)
* [Security & Auth](/agent-os/security/overview)
