数据截至 (上游 commit 610a2f575c46)
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 |
MCPClientCallbackRuntime | 组装 sampling/elicitation/roots 回调与能力,直接传给 MCP SDK 的 mcp.client.Client | mcp/client_callback_runtime.py:61 |
sample / create_sampling_llm | 接住 server 的 sampling 请求,借 client 的模型生成 | mcp/sampling.py:218、:52 |
| elicitation handlers | 接住 server 的 elicitation 请求(表单/自动取消/禁用) | mcp/elicitation_handlers.py、mcp/elicitation_factory.py |
build_oauth_provider | 按 server 配置装配 OAuth provider(PKCE + keyring) | mcp/oauth_client.py:1232 |
| transport tracking | 记录每条传输通道的收发,做 Streamable HTTP 诊断 | mcp/transport_tracking.py、mcp/client_gateway.py(_transport_metrics_hook) |
run_server_mode / FastMCP | 把 agent 反向暴露成 MCP server | core/server_runtime.py:70、mcp/server/harness_app_server.py:99 |
| Apps / MCP-UI 识别 | 识别 server 声明的交互式 UI 资源(MCP Apps / OpenAI Apps SDK) | mcp/app_integrations/__init__.py、mcp/app_integrations/models.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-422。list_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 | 建的传输 | 适用 |
|---|---|---|
stdio | tracking_stdio_client(启子进程,标准输入输出) | 本地 server |
sse | tracking_sse_client | 远程(旧式 SSE) |
http | tracking_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:1469),心跳循环在 _run_ping_loop(:339,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>;identity由compute_server_identity(:407)按 server URL 规范化算出,保证同一台 server 复用同一份 token。- 没有钥匙串后端(如无头容器)时退回
InMemoryTokenStorage(:139),token 只活在本次进程。
对应的用户侧配置(README 已印证 PKCE + keyring 默认):
| YAML 字段 | 默认 | 含义 | 代码锚点 |
|---|---|---|---|
auth.oauth | true | 该 server 是否走 OAuth | config.py:117 |
auth.persist | keyring | token 存钥匙串还是仅内存 | config.py:131 |
auth.redirect_port | 3030 | 本地回调 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 把这两条都接到了自己的资源上。
这两条通道是在哪注册的? 就在 MCPClientCallbackRuntime.__post_init__(mcp/client_callback_runtime.py:93)里:它组合出全部回调属性,再由 ServerConnection 直接传给 MCP SDK 的 mcp.client.Client:
# mcp/client_callback_runtime.py:93-99
def __post_init__(self) -> None:
if self.aggregator is None:
self.subscription_ready.set()
self.client_info = self._client_implementation()
self.list_roots_callback = self._make_list_roots_callback() # roots:告诉 server 我暴露哪些根目录
self.sampling_callback = self._make_sampling_callback() # sampling:server 借我的模型
self.sampling_capabilities = self._make_sampling_capabilities()
self.elicitation_callback = self._resolve_elicitation_handler() # elicitation:server 借我的真人
sampling —— 让 server 反过来用 client 的模型。 当 server 发来 CreateMessageRequest,sample()(mcp/sampling.py:218)接住它:
_select_sampling_model(:174)按优先级挑模型——server 配置指定的 > agent 自己的模型 > 系统默认;create_sampling_llm(:52)用ModelFactory现场造一个临时 LLM;- 把 server 给的消息喂进去
llm.generate(...); - 结果按 MCP 类型回传。
精华在于它支持"带工具的 sampling":如果 server 在请求里带了 tools 且模型决定用工具,sample 会返回 CreateMessageResultWithTools 且 stopReason="toolUse",把工具执行的球踢回 server(mcp/sampling.py:201)。这让 server 能借用 client 的模型做多轮工具推理。
_make_sampling_callback(mcp/client_callback_runtime.py:139)决定要不要挂这个回调:server 配了 sampling、或全局 auto_sampling 开着(config.py:250,默认 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 传输用量"的工具。机制是给每条传输通道挂一层记账回调:_transport_metrics_hook(mcp/client_gateway.py:364)返回一个 ChannelEvent 记录器,每收发一条消息就记进 TransportChannelMetrics(mcp/transport_tracking.py:182),最后汇成 TransportSnapshot(:165)——它就挂在 ServerStatus.transport_channels(mcp/mcp_aggregator.py:334,_apply_transport_status 在 :2209 赋值)上,可以在诊断界面看每条通道的消息计数、活跃状态。
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 / stdio | MCP server(run_mcp_server) | 把 agent 暴露为标准 MCP server |
acp | ACP server | Agent Client Protocol |
a2a | A2A server | Agent-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/app_integrations/__init__.py:extract_app_tool_metadata(:64)从工具的 _meta 里认出它是否声明了 UI 资源(靠 MIME 类型判别 supported_mime_types,:106,区分 OpenAI Apps SDK 与 MCP Apps 两套约定),结果汇成 AppServerConfig(mcp/app_integrations/models.py:77),挂在 ServerStatus.app_integration_config(mcp/mcp_aggregator.py:335)上。聚合器在工具探测时逐个跑 extract_app_tool_metadata(mcp/mcp_aggregator.py:1474)把这些探测串起来。
收集侧 在聚合器里:_collect_app_resources(mcp/mcp_aggregator.py:1500)把 server 资源里 URI 以 ui:// 开头的(_app_resource_candidate,:1588,判别在 :1597)挑成 AppResourceConfig(mcp/app_integrations/models.py:37)挂进 server 配置的 resources,并按需 _read_app_resource(:1614)拉取内容;若 server 声明了 UI 资源却没有对应输出,_warn_if_app_resources_are_unexposed(:1747)会告警。这样 UI 资源不会污染喂给 LLM 的文本上下文。
6. 巧妙之处(可借鉴的技术)
- 命名空间全名截断到 64 字符(
mcp/common.py:8):一个不起眼但救命的细节——很多模型的工具名有长度上限,聚合多 server 时前缀 + 工具名很容易超,直接在造名处截断。 - 能力探测锁外做 I/O、锁内写缓存(
mcp/mcp_aggregator.py:1323):让不同 server 的探测并发,而不是被一把大锁串行化。 - 权限与执行拆成两个协议钩子(
ToolPermissionHandler/ToolExecutionHandler,mcp/tool_permission_handler.py:40、mcp/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 自身模型 > 系统默认,让"借脑子"既可控又有兜底。 - 在 gateway 层挂事件回调记账而非改协议栈(
mcp/client_gateway.py:364_transport_metrics_hook):诊断能力靠给每条传输通道挂ChannelEvent记录器实现,不动协议逻辑,这也是它敢说"唯一能审视 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.py | MCPAggregator |
| 工具命名空间包装 | src/fast_agent/mcp/mcp_aggregator.py | NamespacedTool |
| server 运行时快照 | src/fast_agent/mcp/mcp_aggregator.py | ServerStatus、ServerStats |
| 工具调用入口 | src/fast_agent/mcp/mcp_aggregator.py | call_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 |
| 动态挂载/卸载 server | src/fast_agent/mcp/mcp_aggregator.py | attach_server、detach_server |
| 命名空间常量与工具函数 | src/fast_agent/mcp/common.py | SEP、create_namespaced_name |
| 权限/执行协议钩子 | src/fast_agent/mcp/tool_permission_handler.py、tool_execution_handler.py | ToolPermissionHandler、ToolExecutionHandler |
| 连接管理 器 | src/fast_agent/mcp/mcp_connection_manager.py | MCPConnectionManager、ServerConnection |
| 传输分派(三种) | src/fast_agent/mcp/mcp_connection_manager.py | create_transport_context |
| 断连重连 / 心跳 | src/fast_agent/mcp/mcp_connection_manager.py | reconnect_server、_run_ping_loop |
| 一次性连接 | src/fast_agent/mcp/gen_client.py | gen_client |
| 回调运行时(挂反向回调) | src/fast_agent/mcp/client_callback_runtime.py | MCPClientCallbackRuntime |
| sampling(借模型) | src/fast_agent/mcp/sampling.py | sample、create_sampling_llm、_select_sampling_model |
| elicitation handler 解析 | src/fast_agent/mcp/elicitation_factory.py | resolve_elicitation_handler |
| elicitation handler 实现 | src/fast_agent/mcp/elicitation_handlers.py | forms_elicitation_handler、auto_cancel_elicitation_handler、_handle_url_elicitation |
| OAuth provider 装配 | src/fast_agent/mcp/oauth_client.py | build_oauth_provider |
| token 存储(keyring/内存) | src/fast_agent/mcp/oauth_client.py | KeyringTokenStorage、InMemoryTokenStorage、compute_server_identity |
| Streamable HTTP 诊断埋点 | src/fast_agent/mcp/streamable_http_tracking.py、transport_tracking.py | ChannelTrackingStreamableHTTPTransport、TransportSnapshot |
| 暴露 agent 为 server(调度) | src/fast_agent/core/server_runtime.py | run_server_mode、run_mcp_server |
| 暴露 agent 为 server(FastMCP) | src/fast_agent/mcp/server/harness_app_server.py | run_harness_mcp_app_server |
| MCP-UI / Skybridge 识别 | src/fast_agent/mcp/skybridge.py | extract_app_tool_metadata、SkybridgeServerConfig |
| MCP-UI 呈现混入 | src/fast_agent/mcp/ui_mixin.py | McpUIMixin |