如何把 agno AgentOS 暴露成带 PAT 鉴权与工具范围控制的 MCP 服务器?
【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno
当你已经用 AgentOS 跑通了一个或多个 agent,想把它们通过/mcp端点开放给 MCP 客户端(Cursor、FastMCP 程序化客户端、Claude Desktop 等),但又不能把裸的mcp=True直接挂到网络上时,需要完成三件事:让客户端用 PAT(Personal Access Token,前缀agno_pat_的服务账号令牌)而不是根密钥访问/mcp;用标签把暴露的工具面从默认的 8 个收敛到需要的集合;在工具或模型执行前拒绝不在白名单内的调用方。仓库中的 secure_mcp.py 就是这条完整路径的可运行实现,本文按它的真实代码与配套文档组织步骤。
适用前提:Python 环境已通过 demo 环境安装(MCP 额外依赖包含在内),并已配置模型 API 密钥。
准备条件
在仓库根目录执行(来自 14_mcp/README.md 的 Prerequisites 一节):
./scripts/demo_setup.sh export OPENAI_API_KEY=...secure_mcp.py还需要一个 OS 级根安全密钥,用于通过 REST 铸造 PAT:
export OS_SECURITY_KEY=$(openssl rand -base64 32)这个根密钥的作用边界要分清:它能调用POST /service-accounts铸造服务账号,但它本身会被 MCP 的authorize门拒掉(返回 401),这正是该例子里刻意演示的匿名路径。
服务端配置:密钥、标签范围与 authorize 回调
secure_mcp.py的服务端核心配置如下(省略了导入语句与客户端函数):
BASE_URL = os.getenv("AGENTOS_URL", "http://localhost:7777").rstrip("/") OS_SECURITY_KEY = os.environ["OS_SECURITY_KEY"] SERVICE_ACCOUNT_PREFIX = "secure-mcp-client-" ALLOWED_HOSTS = [ host.strip() for host in os.getenv("MCP_ALLOWED_HOSTS", "").split(",") if host.strip() ] db = SqliteDb( id="secure-mcp-db", db_file="tmp/secure_mcp.db", ) secure_agent = Agent( id="secure-assistant", name="Secure Assistant", model=OpenAIResponses(id="gpt-5.5"), db=db, instructions="Answer authenticated callers concisely.", ) def authorize_service_account(user_id: str | None) -> bool: """Allow only service accounts minted for this MCP integration.""" return bool(user_id and user_id.startswith(f"sa:{SERVICE_ACCOUNT_PREFIX}")) agent_os = AgentOS( id="secure-mcp-os", description="PAT-authenticated and explicitly scoped AgentOS MCP server.", db=db, agents=[secure_agent], settings=AgnoAPISettings(os_security_key=OS_SECURITY_KEY), mcp=MCPConfig( include_tags={"core", "session"}, exclude_tags={"session"}, result_mode="full", authorize=authorize_service_account, allowed_hosts=ALLOWED_HOSTS, ), ) app = agent_os.get_app()各配置项的作用,均来自 MCPConfig 定义 与 README:
AgnoAPISettings(os_security_key=...):设置根密钥。客户端用它调用POST /service-accounts铸造 PAT,PAT 明文agno_pat_...只返回一次,AgentOS 只存哈希。include_tags={"core", "session"}后接exclude_tags={"session"}:先纳入core与session两个标签组,再减去session,最终暴露 6 个 core 工具:get_agentos_config、run_agent、run_team、run_workflow、continue_run、cancel_run。裸mcp=True会暴露 8 个默认工具(core 6 个加get_sessions、get_session_runs两个 session 工具),这里显式把 session 只读工具挡在 MCP 面之外。authorize=:每次调用前的门。它收到已验证的调用方 principal(服务账号解析为sa:<account-name>),返回True放行、False以 401 拒绝,发生在任何工具或模型运行之前。authorize_service_account只放行自己铸造的sa:secure-mcp-client-*前缀账号。allowed_hosts=ALLOWED_HOSTS:注意默认值是空列表[],而非None。只要allowed_hosts被设置(哪怕是空列表),AgentOS 就会启用 Host 与 Origin 校验(内置的 localhost 放行保留),其他来源返回 400;部署或隧道场景可设置MCP_ALLOWED_HOSTS=agentos.example.com。若保持None,则不做任何主机校验。result_mode="full":run 工具的structuredContent返回 run 的完整to_dict(),供程序化客户端消费;默认"trimmed"只返回回答内容加run_id、session_id、status等字段。db=用同步的 OS 级SqliteDb:服务账号存在AgentOS(db=...)上,而不是挂在某个 agent 的库上,所以这里必须给 AgentOS 配库。
关于authorize回调看到sa:...而不是None:该文件未启用AgentOS(authorization=True, ...),服务账号校验器会把 PAT principal 填进request.state.user_id,authorize因此收到sa:...;只有匿名路径(例如直接拿根密钥访问)才会以None到达该门——文档说明启动时库会提示 “authorizeis set whileAgentOS(authorization=False)”,对本例不适用,属预期行为。
运行服务器与验证客户端
在两个终端分别运行(服务器在 1 号终端,验证客户端在 2 号终端,均使用 demo 环境的 Python):
.venvs/demo/bin/python cookbook/05_agent_os/14_mcp/secure_mcp.py.venvs/demo/bin/python cookbook/05_agent_os/14_mcp/secure_mcp.py --client客户端流程(run_authenticated_client())内置了四条硬断言,任何一条不满足就抛RuntimeError终止,这就是本场景的验证方式:
- 用
OS_SECURITY_KEY作为 Bearer 直接 POST/mcp,断言状态码为401(根密钥不能代替 PAT 过 MCP 门); - 携带
Host: untrusted.example.com再发一次,断言400(Host 白名单拒绝了不可信主机); - 用根密钥
POST /service-accounts(json={"name": account_name})铸造账号,断言返回令牌以agno_pat_开头; - 用该 PAT 作为 Bearer 连上
http://localhost:7777/mcp,list_tools()的集合必须恰好等于6 个 core 工具(出现get_sessions/get_session_runs即报错),再调用run_agent(agent_id="secure-assistant"),断言structuredContent中status == "COMPLETED"且messages非空(result_mode="full"生效的证据)。
客户端成功后打印的结果形如(文档测试记录中的示例输出,run_id等值随每次运行变化):
Authenticated principal: sa:secure-mcp-client-b0ebb699ad Token display prefix: agno_pat_... Root key at /mcp: 401 Untrusted Host at /mcp: 400 Scoped MCP tools: ['cancel_run', 'continue_run', 'get_agentos_config', 'run_agent', 'run_team', 'run_workflow'] Full run result: 586b12d0-5bc3-47d6-b373-ede496e9d129 -> COMPLETED看到这个输出即说明:PAT 鉴权生效、主机校验生效、工具面被收敛到 core 六个、full 结果格式返回完整消息列表。
可选分支:从 stdio-only 客户端接入
如果你的客户端(如 Claude Desktop)只支持 stdio 方式,README 给出的做法是把 PAT 存在 JSON 文件之外(环境变量),用mcp-remote桥接远端 streamable-HTTP 服务。下面 JSON 中的${AUTH_HEADER}是该文档原有写法,由同段env中的AUTH_HEADER提供,agno_pat_replace_me替换为你铸造的真实 PAT:
{ "mcpServers": { "agentos": { "command": "npx", "args": [ "-y", "mcp-remote", "https://agentos.example.com/mcp", "--header", "Authorization:${AUTH_HEADER}" ], "env": { "AUTH_HEADER": "Bearer agno_pat_replace_me" } } } }支持原生远程 MCP 的客户端可直接发送Authorization: Bearer agno_pat_...头。此分支用于验证接入方式,不改变服务端的鉴权与范围配置。
限制与注意
- PAT 明文只出现一次(创建响应),默认有效期 90 天;成功验证默认缓存 30 秒,撤销在处理该请求的 worker 上立即生效,其他 worker 在缓存过期后收敛(见 07_security/README.md 的 Service Accounts 一节)。
- 服务账号的默认 scope 为
agents:run、teams:run、workflows:run、sessions:read、config:read;铸造更特权 scope 需要allow_privileged_scopes=true。 continue_run/cancel_run随组件暴露而附带;当 core 标签被纳入时,这对工具天然在 6 个工具之中,只对已发布组件的 run 生效。- 若 MCP 面上要发布
Toolkit,而其中数据是按用户隔离的,README 要求先配置AgentOS(authorization=True, ...),否则 RunContext 中的调用方身份为None,所有客户端共享同一身份。 - 声明了
requires_confirmation、requires_user_input或external_execution的工具会在 MCP 面被拒发(构建期失败而不是静默放行),因为 MCP 请求会绕过这些审批路径。
铸造、scope 与撤销的完整 REST 操作参见 service_accounts.py;同目录其余文件(如 agents_as_tools.py、stateless.py)分别覆盖把 agent 直接作为具名工具发布与无状态传输,属于不同的暴露策略,可按需另行阅读。
【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考