跳到主要内容

MCP 集成:聚合工具、连接管理与端到端 MCP 特性

30 秒导读: fast-agent 号称「MCP 原生(MCP-native)」。这句话的实质是:它不是"顺手支持了 MCP",而是把 MCP 协议的双向能力全都铺开——对外,它能同时连一堆 MCP server 并把它们的工具/提示词/资源聚合成一张统一的表;对内,它把 server 反向调用 client 的两条通道(sampling、elicitation)接到自己的模型和真人身上;最后它还能把整个 agent 倒过来暴露成一台 MCP server 供别人连。本章讲清这三件事各自在哪段代码里。

本章聚焦「MCP 到底原生在哪」。至于 Agent 类如何持有这个聚合器(继承链细节),见 02-agent-class-stack.md;模型字符串怎么解析成一个 LLM,见 04-llm-provider-abstraction.md


1. 这是什么(零基础也能懂)

先说 MCP。 MCP(Model Context Protocol,模型上下文协议)是一个"让 AI 应用和外部工具说同一种话"的标准。一台 MCP server 对外声明"我有这些工具(tool)、这些提示词(prompt)、这些资源(resource)",AI 应用(client)按协议去列举、去调用。你可以把它想成"给 AI 用的 USB 接口":插上一台文件系统 server、一台数据库 server、一台浏览器 server,agent 就多了对应的手脚。

再说 fast-agent 补了什么。 光有协议不够,真实项目里你会同时插好几台 server,于是冒出一堆工程问题:

  • 两台 server 都有个叫 search 的工具,调用时到底找谁?
  • 远程 server 要 OAuth 登录,token 存哪、怎么刷新?
  • 连接断了、session 过期了,一次工具调用该不该自动重连重试?
  • server 想反过来用"我这边的模型"生成一段文字(sampling),或者想弹个表单问真人一句话(elicitation),这条反向通道谁来接?

fast-agent 的 src/fast_agent/mcp/ 目录就是专门回答这些问题的一层。它给每个 agent 配一个 MCPAggregator(聚合器):把多台 server 合成一张工具表;底下再垫一个 MCPConnectionManager(连接管理器)管住真实的传输、断连、OAuth、心跳。

一句话直觉/类比:MCPAggregator 当成一个总机接线员——agent 只管喊"帮我调 fetch__get",接线员负责查这是哪台分机(server)、那条线通不通(连接)、要不要先验身份(OAuth),然后把话接过去。

用起来什么样。 使用者几乎看不到这层,只在 YAML 里声明有哪些 server:

# 示意:fast-agent.yaml
mcp:
servers:
fetch:
transport: stdio # 本地进程,标准输入输出通信
command: "uvx"
args: ["mcp-server-fetch"]
myremote:
transport: http # 远程 Streamable HTTP server
url: http://localhost:8001/mcp
auth:
oauth: true # 默认就是 true,自动走 OAuth

然后 agent 声明 servers=["fetch", "myremote"],调用时工具已经被聚合、命名空间化好了,直接 fetch__get_url 这样点名即可。


2. 顶层全景(它大概怎么转)

怎么读这张图: 中间竖线是 fast-agent 进程边界;左边是"作为 client 连出去",右边是"作为 server 被连进来"。横向箭头是 MCP 消息,注意 sampling / elicitation 是反向(从 server 流回 client)。

fast-agent 进程
┌───────────────────────────────────────┐
│ │
│ Agent ──持有──▶ MCPAggregator │
│ (总机 / 工具表) │
│ │ │
│ 命名空间化路由 ┌─┴─┐ 能力探测 │
│ ▼ ▼ │
│ MCPConnectionManager │ ┌──────────────┐
│ (连接/传输/OAuth/心跳) │ │ 外部 MCP │
│ │ │ │ │ │ server 们 │
└─────────────┼──────┼───────┼────────────┘ └──────────────┘
│stdio │sse │http(Streamable)
正向: tools/list、tools/call、prompts、resources ───────────▶
反向: sampling(借模型)、elicitation(借真人)、roots ◀───────────

── 另一面:把 agent 自己暴露成 server ──
run_server_mode ──▶ FastMCP ──▶ 别的 client(Claude Desktop / 另一个 fast-agent)

部件一句话职责:

部件干什么在哪个文件
MCPAggregator多 server 工具/提示词/资源聚合、命名空间路由、权限/执行钩子mcp/mcp_aggregator.py:302
NamespacedTool一个工具 + 它属于哪台 server + 命名空间全名mcp/mcp_aggregator.py:198
ServerStatus一台 server 的运行时快照(连接/能力/心跳/传输统计)mcp/mcp_aggregator.py:227
MCPConnectionManager持久连接的生命周期、断连重连、OAuth 装配、ping 循环mcp/mcp_connection_manager.py:988
gen_client一次性(非持久)连接的轻量上下文管理器mcp/gen_client.py:17
MCPAgentClientSessionfast-agent 定制的 ClientSession,挂上 sampling/elicitation/roots 回调mcp/mcp_agent_client_session.py:121
sample / create_sampling_llm接住 server 的 sampling 请求,借 client 的模型生成mcp/sampling.py:218:52
elicitation handlers接住 server 的 elicitation 请求(表单/自动取消/禁用)mcp/elicitation_handlers.pymcp/elicitation_factory.py
build_oauth_provider按 server 配置装配 OAuth provider(PKCE + keyring)mcp/oauth_client.py:1232
transport tracking记录每条传输通道的收发,做 Streamable HTTP 诊断mcp/transport_tracking.pymcp/streamable_http_tracking.py
run_server_mode / FastMCP把 agent 反向暴露成 MCP servercore/server_runtime.py:70mcp/server/harness_app_server.py:99
Skybridge / McpUIMixin识别 server 声明的交互式 UI 资源(MCP-UI / OpenAI Apps)mcp/skybridge.pymcp/ui_mixin.py

主线走一遍(高层): agent 第一次要用工具 → MCPAggregator.load_servers() 让连接管理器把每台 server 连起来、tools/list 拉回工具 → 每个工具被贴上 server__tool 的命名空间名存进一张 map → LLM 决定调用某个命名空间名 → 聚合器解析出"哪台 server + 本地工具名" → 过权限钩子 → 经连接管理器把 tools/call 发到那台 server → 结果回传。整条链的入口是 call_tool()(mcp/mcp_aggregator.py:2326)。


3. 核心原理(逐个机制,由浅入深)

3.1 多 server 工具聚合:命名空间化

要解决的小问题: 三台 server,可能各有一个同名工具;LLM 只看到一张扁平的工具列表,怎么保证"点名不歧义 + 能路由回正确的 server"?

思路/直觉: 给每个工具起一个带 server 前缀的全名。分隔符固定是两个下划线 __,前缀是 server 名。fetch server 的 get_url 工具对外就叫 fetch__get_url

真源码把这条规则收在一个共享常量里,连全名要截断到 64 字符(很多模型对工具名长度有限制)都写死了:

# mcp/common.py:6
SEP = "__"

def create_namespaced_name(server_name: str, resource_name: str) -> str:
"""Create a namespaced resource name from server and resource names"""
return f"{server_name}{SEP}{resource_name}"[:64]

聚合的数据结构。 每个工具被包成一个 NamespacedTool——记住"原始工具 + 属于谁 + 命名空间全名"三件事:

# mcp/mcp_aggregator.py:198
class NamespacedTool(BaseModel):
tool: Tool
server_name: str
namespaced_tool_name: str

聚合器维护两张索引:_namespaced_tool_map(全名 → NamespacedTool)和 _server_to_tool_map(server → 它的工具们),在 mcp/mcp_aggregator.py:420-422list_tools()(:1449)对外吐出的就是把每个工具的 name 换成命名空间全名后的列表。

反向路由:调用时怎么找回 server。 _parse_resource_name(mcp/mcp_aggregator.py:2278)是路由的核心,它按优先级降级匹配:

怎么读: 从上到下依次尝试,命中即停。

输入一个工具名 name

├─▶ ① name 直接命中 _namespaced_tool_map? ── 命中 ──▶ 用它的 server + 本地名
│ (既处理全名,也处理已注册的裸名)

├─▶ ② name 含 "__" 且以某个已知 server 名 + "__" 开头?
│ ── 命中 ──▶ 切掉前缀,剩下是本地工具名
│ (专门处理 server 名里带连字符的情况)

├─▶ ③ 是 tool:遍历所有 server,按裸工具名精确匹配
│ ── 命中 ──▶ 那台 server

└─▶ ④ 其它资源类型:落到第一台 server 兜底

这个降级顺序解释了一个易被忽略的行为:LLM 有时会只报裸工具名(没带前缀),第 ③ 步让它仍能被路由到唯一提供者;但如果两台 server 有同名工具而 LLM 没带前缀,谁先被遍历到就是谁——所以命名空间前缀才是消歧的正解。

能力探测。 聚合器还负责问每台 server"你支持什么"(tools/prompts/resources/logging 等),入口是 get_capabilities(mcp/mcp_aggregator.py:1314)。它对非持久连接会缓存结果(_capabilities_cache),并且先在锁外做 I/O、再回锁写缓存,让不同 server 的探测能并发——这是刻意的性能设计(见代码注释 :1323)。

动态挂载/卸载。 server 列表不是只能启动时定死。attach_server(:749)和 detach_server(:961)支持运行时挂载一台新 server(fast-agent go / 交互式 /connect 就走这条路),挂载完会把新工具并进两张索引、卸载时再摘掉。

3.2 连接与传输:三种传输 + 断连自愈

要解决的小问题: "调一个工具"听着简单,底下要处理:本地进程 vs 远程 HTTP 两类完全不同的传输;连接可能断、session 可能被 server 主动终止;远程还可能要 OAuth。这些不该污染上层逻辑。

分层。 聚合器只调 _execute_on_server(mcp/mcp_aggregator.py:1867)这一个通用出口,真正的连接活儿全下沉给 MCPConnectionManager(mcp/mcp_connection_manager.py:988)。两种连接策略:

  • 持久连接(connection_persistence=True):由 MCPConnectionManager 长期持有 ServerConnection(:384),配 task group 跑生命周期、可选 ping 心跳。
  • 一次性连接:gen_client(mcp/gen_client.py:17)这个 @asynccontextmanager,连上、用完、关掉,用于探测这类短命场景。

三种传输的分派都在 create_transport_context(mcp/mcp_connection_manager.py:295),一个 if 树:

transport建的传输适用
stdiotracking_stdio_client(启子进程,标准输入输出)本地 server
ssetracking_sse_client远程(旧式 SSE)
httptracking_streamablehttp_client远程(Streamable HTTP,新式)

注意三个都带 tracking_ 前缀——这不是普通 client,是包了埋点的(见 3.5 诊断)。

断连自愈是本节精华。 _execute_on_server 不是"发出去就完",它把一次操作包成 try_execute,然后对不同异常做不同的恢复策略:

# mcp/mcp_aggregator.py:1907 (节选骨架)
try:
result = await self._execute_initial_server_operation(server_name, try_execute)
except ConnectionError:
recovery = await self._handle_connection_error(...) # 连接断:重连后重试
except ServerSessionTerminatedError as exc:
recovery = await self._handle_session_terminated(...) # session 被终结:重开
except Exception as exc:
if self._should_retry_with_oauth(server_name, exc):
recovery = await self._handle_auth_challenge(...) # 收到 401:触发 OAuth 后重试

这段解释了 fast-agent 的一个体验:远程 server 掉线、或 token 过期弹 401,单次工具调用会自己重连/重新登录再试一遍,而不是把错误直接甩给 LLM。重连逻辑在 reconnect_server(mcp/mcp_connection_manager.py:1703),心跳循环在 _run_ping_loop(:619,ServerStatus 里那一串 ping_* 字段就是它的产物)。

3.3 OAuth:PKCE + keyring

要解决的小问题: 远程 MCP server 常要求 OAuth 登录。命令行工具没有浏览器会话,怎么走授权码流程?token 存哪才安全?

思路。 fast-agent 起一个本地回调 server接授权码,浏览器打开授权链接、回调打到 localhost;拿到 token 后默认存进操作系统钥匙串(keyring)。整个 OAuth 2.1 / PKCE 的协议流程复用 MCP SDK 自带的 OAuthClientProvider,fast-agent 用子类 _ProtectedResourceDiscoveryOAuthClientProvider(mcp/oauth_client.py:464)扩展它,主要为了把受保护资源元数据(PRM)发现的路径范围收窄(让 /api/mcp 能发现发布在 /api 的元数据)。

装配入口 build_oauth_provider(mcp/oauth_client.py:1232):只对远程传输生效、按 server 配置决定开不开,然后组装回调处理器 + token 存储。存储策略persist 决定:

# mcp/oauth_client.py _oauth_token_storage(节选)
if settings.persist_mode == "keyring":
identity = compute_server_identity(server_config)
return KeyringTokenStorage(service_name="fast-agent-mcp", server_identity=identity)
# 否则退回 InMemoryTokenStorage(仅本次会话)
  • KeyringTokenStorage(:971)把 token JSON 写进 OS 钥匙串,key 形如 oauth:tokens:<identity>;identitycompute_server_identity(:407)按 server URL 规范化算出,保证同一台 server 复用同一份 token。
  • 没有钥匙串后端(如无头容器)时退回 InMemoryTokenStorage(:139),token 只活在本次进程。

对应的用户侧配置(README 已印证 PKCE + keyring 默认):

YAML 字段默认含义代码锚点
auth.oauthtrue该 server 是否走 OAuthconfig.py:117
auth.persistkeyringtoken 存钥匙串还是仅内存config.py:131
auth.redirect_port3030本地回调 server 端口oauth_client.py _select_...redirect_port
auth.redirect_path/callback回调路径同上

端口被占时,_CallbackServer(:220)会挑一个可绑定的端口;拿不到端口还有粘贴 URL 回退(手动把回调 URL 贴回终端),由 allow_paste_fallback 控制。

3.4 双向 MCP:sampling 与 elicitation

这是"MCP 原生"最能体现的地方——server 反过来向 client 要东西。MCP 定义了两条反向请求:sampling(server 想要一次模型生成)和 elicitation(server 想问真人一个结构化问题)。fast-agent 把这两条都接到了自己的资源上。

这两条通道是在哪注册的? 就在定制的 MCPAgentClientSession.__init__(mcp/mcp_agent_client_session.py:134)里,构造父类 ClientSession 时把回调塞进去:

# mcp/mcp_agent_client_session.py:148
super().__init__(
read_stream, write_stream, read_timeout, **kwargs,
list_roots_callback=list_roots_cb, # roots:告诉 server 我暴露哪些根目录
sampling_callback=sampling_cb, # sampling:server 借我的模型
sampling_capabilities=sampling_caps,
client_info=fast_agent,
elicitation_callback=elicitation_handler, # elicitation:server 借我的真人
)

sampling —— 让 server 反过来用 client 的模型。 当 server 发来 CreateMessageRequest,sample()(mcp/sampling.py:218)接住它:

  1. _select_sampling_model(:174)按优先级挑模型——server 配置指定的 > agent 自己的模型 > 系统默认;
  2. create_sampling_llm(:52)用 ModelFactory 现场造一个临时 LLM;
  3. 把 server 给的消息喂进去 llm.generate(...);
  4. 结果按 MCP 类型回传。

精华在于它支持"带工具的 sampling":如果 server 在请求里带了 tools 且模型决定用工具,sample 会返回 CreateMessageResultWithToolsstopReason="toolUse",把工具执行的球踢回 server(mcp/sampling.py:201)。这让 server 能借用 client 的模型做多轮工具推理。

_make_sampling_callback(mcp/mcp_agent_client_session.py:192)决定要不要挂这个回调:server 配了 sampling、或全局 auto_sampling 开着(config.py:1877,默认 true)才挂。

elicitation —— 让 server 反过来问真人。 server 发来 ElicitRequest(比如"请填写 API key"),由 elicitation handler 接。fast-agent 用一张模式表把"配置里写的模式"映射到"具体 handler"(mcp/elicitation_factory.py:33):

模式行为handler
forms弹交互式表单让真人填(默认)forms_elicitation_handler(elicitation_handlers.py:295)
auto-cancel声明支持能力但自动取消所有请求(适合生产无人值守)auto_cancel_elicitation_handler(:152)
none不声明 elicitation 能力None(不挂 handler)

选哪个 handler 有优先级(resolve_elicitation_handler,mcp/elicitation_factory.py:95):装饰器直接给的 > server 级配置 > 全局配置 > 默认 forms。此外还有一条特殊的 URL elicitation——server 可以请求"请用户打开某个 URL 完成动作"(_handle_url_elicitation,mcp/elicitation_handlers.py:195),这正是远程 OAuth 授权链接能弹给用户的机制之一。

双向能力总图。 把 client 侧摆一起看,方向感就清楚了:

fast-agent (client) MCP server
──────────────────── ──────────
│ tools/list, tools/call ──────────▶ │ 提供工具/提示词/资源
│ prompts/get, resources/read ──────▶ │
│ │
│ ◀──────── sampling(要一次模型生成) ── │ 想"借脑子"
│ ◀──────── elicitation(要问真人) ──── │ 想"借真人"
│ ◀──────── roots/list(要我的根目录) ─ │ 想知道我暴露哪些目录
▼ │
接住反向请求: │
model / 真人表单 / roots 配置 │

3.5 Streamable HTTP 诊断(fast-agent 的独门卖点)

要解决的小问题: Streamable HTTP 是 MCP 较新的传输,实现细节多、易出兼容问题(GET/POST 通道、SSE 事件、session id 续期)。出问题时你想知道"到底哪条通道收发了什么"。

fast-agent README 明确宣称它是"唯一能让你审视 Streamable HTTP 传输用量"的工具。机制是给传输包一层埋点:ChannelTrackingStreamableHTTPTransport(mcp/streamable_http_tracking.py:67)继承 SDK 的 StreamableHTTPTransport,每收发一条消息就记进 TransportChannelMetrics(mcp/transport_tracking.py:165),最后汇成 TransportSnapshot(:150)——它就挂在 ServerStatus.transport_channels(mcp/mcp_aggregator.py:251)上,可以在诊断界面看每条通道的消息计数、活跃状态。这层对 stdio 也有对应的轻量版(stdio_tracking_simple.py)。


4. 把 agent 反过来暴露成 MCP server

前面都是"fast-agent 当 client 连别人"。反过来,它也能把整个 agent 当成一台 MCP server 让别人连(比如挂进 Claude Desktop,或被另一个 fast-agent 聚合)。

入口 是 server 模式的调度器 run_server_mode(core/server_runtime.py:70)。它按 --transport 分派到三种服务器:

--transport起的服务器干什么
http / stdioMCP server(run_mcp_server)把 agent 暴露为标准 MCP server
acpACP serverAgent Client Protocol
a2aA2A serverAgent-to-Agent

MCP 那条路最终落到 run_harness_mcp_app_server(mcp/server/harness_app_server.py:227),内部用 FastMCP(fastmcp 库)真正起服务:

# mcp/server/harness_app_server.py:99 (节选)
self.mcp_server = FastMCP(
name=...,
instructions=self._instructions(),
...
)

命令行体感就是 README 里那一行——uv run agent.py --transport http --port 8001 就把你的 agent 变成了 http://localhost:8001 上的一台 MCP server。server 模式下还能配 instance_scope(shared / connection)决定多个连接是否共享同一个 agent 实例(core/server_runtime.py:46 resolve_server_instance_scope)。


5. MCP-UI / Skybridge:接住 server 的交互式界面

MCP 生态里有些工具不只返回文本,还返回可交互的 UI 资源(一段网页/一个组件)——两套约定:OpenAI Apps SDK(fast-agent 内部叫 "Skybridge")和新的 MCP Apps。fast-agent 会识别并接住这些资源。

识别侧mcp/skybridge.py:extract_app_tool_metadata(:99)从工具的 _meta 里认出它是否声明了 UI 资源(靠 ui/resourceUriopenai/outputTemplate 这些 key 和 text/html+skybridge / text/html;profile=mcp-app 这些 MIME 类型判别,:7-8),结果汇成 SkybridgeServerConfig(:174),挂在 ServerStatus.skybridge 上。聚合器在 _evaluate_skybridge_for_server(mcp/mcp_aggregator.py:1017)一带把这些探测串起来。

呈现侧McpUIMixin(mcp/ui_mixin.py:42),一个混入类。它覆写 run_tools(:62):工具跑完后,把结果里 URI 以 ui:// 开头的嵌入式资源(_is_ui_embedded_resource,:287)从普通内容里拆出来,塞进单独的 MCP_UI 通道,再在 show_assistant_message 后按 ui_mode(auto/enabled/disabled)决定是否显示链接、是否自动开浏览器。这样 UI 资源不会污染喂给 LLM 的文本上下文。


6. 巧妙之处(可借鉴的技术)

  • 命名空间全名截断到 64 字符(mcp/common.py:8):一个不起眼但救命的细节——很多模型的工具名有长度上限,聚合多 server 时前缀 + 工具名很容易超,直接在造名处截断。
  • 能力探测锁外做 I/O、锁内写缓存(mcp/mcp_aggregator.py:1323):让不同 server 的探测并发,而不是被一把大锁串行化。
  • 权限与执行拆成两个协议钩子(ToolPermissionHandler / ToolExecutionHandler,mcp/tool_permission_handler.py:40mcp/tool_execution_handler.py:14):call_tool 先过 check_permission(mcp/mcp_aggregator.py:2444)决定"准不准",再用执行钩子发 on_tool_start/progress/complete 事件(:2372 起)。默认是 No-Op 版全放行,但 ACP 这类需要"每次工具调用问用户批准"的场景可以无侵入地插进来——聚合器不必知道权限系统长什么样。
  • sampling 模型的三级回退(mcp/sampling.py:174):server 指定 > agent 自身模型 > 系统默认,让"借脑子"既可控又有兜底。
  • 对传输做子类埋点而非改协议栈(mcp/streamable_http_tracking.py:67):诊断能力靠继承 SDK transport 加一层记账实现,不动协议逻辑,这也是它敢说"唯一能审视 Streamable HTTP"的底气。

7. 边界与局限(诚实地说)

  • 裸工具名的消歧:两台 server 有同名工具、而 LLM 只报裸名时,_parse_resource_name 第 ③ 步按遍历顺序取第一个匹配(mcp/mcp_aggregator.py:2314),并非报错——依赖命名空间前缀才是正解。
  • OAuth 的 PKCE 流程本体不在本仓库:fast-agent 复用 MCP SDK 的 OAuthClientProvider,只做子类扩展(PRM 发现范围)、token 存储与本地回调 server。想读 PKCE 交换细节要去 SDK。
  • 无钥匙串环境下 token 不落盘:headless 容器里退回内存存储(mcp/oauth_client.py:139),每次进程重启都要重新授权。
  • 本章不覆盖 Agent 类如何持有聚合器的继承细节(→ 02)、工具循环如何驱动一次 turn(→ 03)、以及把多个 agent 组合成工作流(→ 06)。

8. 代码地图(导航索引)

主题文件路径符号
聚合器主体src/fast_agent/mcp/mcp_aggregator.pyMCPAggregator
工具命名空间包装src/fast_agent/mcp/mcp_aggregator.pyNamespacedTool
server 运行时快照src/fast_agent/mcp/mcp_aggregator.pyServerStatusServerStats
工具调用入口src/fast_agent/mcp/mcp_aggregator.pycall_tool_execute_on_server
命名空间路由/降级匹配src/fast_agent/mcp/mcp_aggregator.py_parse_resource_name
权限检查src/fast_agent/mcp/mcp_aggregator.py_tool_permission_error_result
动态挂载/卸载 serversrc/fast_agent/mcp/mcp_aggregator.pyattach_serverdetach_server
命名空间常量与工具函数src/fast_agent/mcp/common.pySEPcreate_namespaced_name
权限/执行协议钩子src/fast_agent/mcp/tool_permission_handler.pytool_execution_handler.pyToolPermissionHandlerToolExecutionHandler
连接管理器src/fast_agent/mcp/mcp_connection_manager.pyMCPConnectionManagerServerConnection
传输分派(三种)src/fast_agent/mcp/mcp_connection_manager.pycreate_transport_context
断连重连 / 心跳src/fast_agent/mcp/mcp_connection_manager.pyreconnect_server_run_ping_loop
一次性连接src/fast_agent/mcp/gen_client.pygen_client
定制 client session(挂反向回调)src/fast_agent/mcp/mcp_agent_client_session.pyMCPAgentClientSession
sampling(借模型)src/fast_agent/mcp/sampling.pysamplecreate_sampling_llm_select_sampling_model
elicitation handler 解析src/fast_agent/mcp/elicitation_factory.pyresolve_elicitation_handler
elicitation handler 实现src/fast_agent/mcp/elicitation_handlers.pyforms_elicitation_handlerauto_cancel_elicitation_handler_handle_url_elicitation
OAuth provider 装配src/fast_agent/mcp/oauth_client.pybuild_oauth_provider
token 存储(keyring/内存)src/fast_agent/mcp/oauth_client.pyKeyringTokenStorageInMemoryTokenStoragecompute_server_identity
Streamable HTTP 诊断埋点src/fast_agent/mcp/streamable_http_tracking.pytransport_tracking.pyChannelTrackingStreamableHTTPTransportTransportSnapshot
暴露 agent 为 server(调度)src/fast_agent/core/server_runtime.pyrun_server_moderun_mcp_server
暴露 agent 为 server(FastMCP)src/fast_agent/mcp/server/harness_app_server.pyrun_harness_mcp_app_server
MCP-UI / Skybridge 识别src/fast_agent/mcp/skybridge.pyextract_app_tool_metadataSkybridgeServerConfig
MCP-UI 呈现混入src/fast_agent/mcp/ui_mixin.pyMcpUIMixin