cookbook/05_agent_os/mcp_demo/README.md
Examples for mcp_demo in AgentOS.
enable_mcp_example.py — Example AgentOS app with MCP enabled.custom_mcp_tool_example.py — Expose ONE custom MCP tool routed through an agent, with the built-in tools disabled (uses MCPServerConfig).mcp_tools_advanced_example.py — Example AgentOS app where the agent has MCPTools.mcp_tools_example.py — Example AgentOS app where the agent has MCPTools.mcp_tools_existing_lifespan.py — Example AgentOS app where the agent has MCPTools.test_client.py — First run the AgentOS with enable_mcp_server=True (enable_mcp_example.py), then run this client against it.enable_mcp_server=True serves 8 built-in tools at /mcp — an operator surface for LLM
frontends (Claude, ChatGPT, Claude Code, Cursor), not a database console:
| Tool | Tag | What it does |
|---|---|---|
get_agentos_config | core | Discover agents/teams/workflows (ids + descriptions) and database ids. Call first. |
run_agent / run_team / run_workflow | core | Run a component. Results are trimmed for the consuming model: answer text + media blocks + run_id/session_id/status (set MCPServerConfig(result_mode="full") for the complete run object). Long runs report MCP progress. |
continue_run | core | Resume a PAUSED (human-in-the-loop) run by passing back its resolved requirements. |
cancel_run | core | Request cancellation of a run by run_id. |
get_sessions | session | List past conversations (read-only). |
get_session_runs | session | Read a conversation's history (read-only, auto-detects session type). |
Session writes and memory CRUD live on the REST surface; anything else can be exposed as a custom tool.
Pass mcp_config=MCPServerConfig(...) to register your own tools, scope
the built-ins, gate the server, and protect it — all with data, no middleware classes to write:
from agno.os import AgentOS
from agno.os.config import MCPServerConfig
agent_os = AgentOS(
agents=[my_agent],
enable_mcp_server=True,
mcp_config=MCPServerConfig(
tools=[my_tool], # custom tools (plain callables or Agno @tool / Function)
enable_builtin_tools=False, # ship ONLY your tools; or scope with:
# include_tags={"core"}, # keep only tools tagged "core"
# exclude_tags={"session"}, # drop the read-only session tools
authorize=lambda user_id: user_id in OWNER_IDS, # 401 non-owners before the model runs
allowed_hosts=["my-app.example.com"], # DNS-rebinding protection (localhost is automatic)
# middleware=[Middleware(MyMiddleware)], # escape hatch for anything else
),
)
Built-in tools are tagged core (config + run/continue/cancel) and session (the read-only
session tools). With no mcp_config, all built-ins are registered. Custom tools share the same
/mcp mount, lifespan, and JWT middleware as the built-ins.
Identity in custom tools. Declare a user_id parameter on a custom tool and AgentOS fills it
with the authenticated caller's id (the JWT subject), hidden from the client-facing schema so it
can't be spoofed. Tools that need the full request can declare a FastMCP Context parameter, which
FastMCP injects natively.
Gating. authorize=fn(user_id) -> bool runs after JWT verification and returns 401 before any
tool or model runs — use it for an owner-only or allow-listed server.
Transport security. allowed_hosts=[...] turns on built-in DNS-rebinding protection: the request
Host (and Origin, when present) is validated against your list plus localhost defaults, so an
always-on local server can't be driven by a malicious web page via a rebound DNS name. You list only
your deploy/tunnel host; localhost works out of the box. allowed_origins=[...] is an advanced extra.
Escape hatch. middleware=[...] takes starlette.middleware.Middleware instances for anything
the options above don't cover.
direnv allow (requires .envrc)..venvs/demo/bin/python <path-to-file>.py.