开源项目 Vibe-Trading 不装界面也能用:MCP 接入方式与边界
本文基于 Vibe-Trading 仓库 commit 3a752d5(2026-08-04)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/HKUDS/Vibe-Trading 最新代码与文档为准。
HKUDS 放出的这个开源仓库里,最值得复用的部分不是它的界面,而是 agent/mcp_server.py 这一个入口文件——它把整套金融研究工具按 MCP 协议暴露出来,你现有的 Agent 加一段 mcpServers 配置就能全量拿到,前端目录一行代码都不用碰。 更关键的是,这个入口做过一次明确的裁剪:服务端文件顶部的说明写得很直白,经 MCP 暴露的每个工具都是只读或仅研究用途,下单与撤单类工具永远不会出现在这个面上。也就是说,你接进来的是一套研究工具,不是一条交易通道。
站内已经有三篇讲 MCP 本身的文章,分工不同:协议本身怎么回事看这篇,手上挂了一堆 server 之后怎么管看这篇,接之前先在本地把它测通看这篇;本篇不重复协议与通用运维,只谈 Vibe-Trading 这一个具体服务端接进来会发生什么、代价是什么。
本文只讨论工程实现,不构成任何投资建议;文中不涉及任何策略效果的讨论,历史表现也不代表未来。
一、你接进来的到底是什么
先把范围划清楚,否则很容易接完之后发现拿到的东西跟预期不一样。
这个 MCP 服务端注册的工具大致分成几类:技能读取(list_skills / load_skill)、研究目标与证据链(start_research_goal / get_research_goal / add_goal_evidence / update_research_goal_status)、计算类(backtest / factor_analysis / analyze_options / analyze_options_payoff / pattern_recognition)、行情与基本面读取(get_market_data / get_financial_statements / get_sec_filings / get_options_chain / search_symbol / screen_market 等一大批)、多智能体编制(list_swarm_presets / run_swarm / get_swarm_status / get_run_result / list_runs / reap_stale_runs / retry_run)、交易日志与影子账户分析(analyze_trade_journal / extract_shadow_strategy / run_shadow_backtest / render_shadow_report / scan_shadow_signals),以及一组只读的券商连接器工具(trading_check / trading_account / trading_positions / trading_orders / trading_quote / trading_history)。仓库 agent/SKILL.md 里项目自己写的总数是 55 个工具,同一份文档里还自述有 9 个回测引擎、462 个预置 alpha、24 个行情数据源。
这些自述数字请当作项目的说法看待。你自己能当场数出来的是另一批:agent/src/skills/ 下 88 个技能目录,agent/src/swarm/presets/ 下 30 份编制 yaml,agent/src/trading/connectors/ 下 12 家券商连接器子目录,agent/src/ 下 23 个模块目录。这些用一条 ls 就能复核,不依赖任何人的描述。
有一个细节值得提前记住:trading_* 这一组工具里,trading_orders 的说明明确写了它只读,不下单、不撤单、不改单、不替换订单。整个 MCP 面上没有任何一个工具能把订单送出去。
二、三种传输方式,以及为什么默认那个最省事
服务端入口的 main() 用 argparse 收三类参数:--transport(可选 stdio / sse / http,默认 stdio)、--host(默认 127.0.0.1)、--port(默认 8900),外加一个 --enable-shell-tools 开关。
文件顶部的用法说明是这三行:
python mcp_server.py # stdio transport (default)
python mcp_server.py --transport sse # legacy SSE transport (GET /sse + POST /messages/)
python mcp_server.py --transport http # Streamable HTTP transport (single POST/GET /mcp endpoint)
三者的取舍在代码注释里说得很清楚。stdio 是父子进程之间的私有管道,不占端口,也不需要主机头校验,客户端只要能拉起子进程就行。http 走的是 Streamable HTTP,单端点服务在 /mcp,注释特意点名不要指向 /sse——那是遗留 SSE 传输的产物。sse 本身在注释里被标为 deprecated,除非你的客户端只认它,否则没有理由选。
同一个包里还给了控制台命令。仓库根目录 pyproject.toml 的 [project.scripts] 只有两行,其中一行把 vibe-trading-mcp 映射到 mcp_server:main(另一行是交互式 CLI 的 vibe-trading),所以装完之后不必写文件路径。agent/SKILL.md 给的最小配置就是这个形态:
{
"mcpServers": {
"vibe-trading": {
"command": "vibe-trading-mcp"
}
}
}
服务端文件顶部另给了一份直接指向脚本路径的 Claude Desktop 配置写法,两条路都通。用哪种取决于你的客户端是按可执行名解析还是按绝对路径拉起进程。
三、组成部分对照表
下表里的每个路径都在仓库里真实存在,接之前照着翻一遍,比读任何介绍都快。
| 组成部分 | 它负责什么 | 对应仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| MCP 服务端入口 | 用 FastMCP 注册工具、解析传输参数、决定要不要挂 shell 工具 | agent/mcp_server.py | 配置 command 指向它的时候 |
| 控制台命令映射 | 把 vibe-trading-mcp 绑到 mcp_server:main | 仓库根目录 pyproject.toml 的 [project.scripts] | 装完包却找不到命令的时候 |
| 本地工具注册表 | build_registry 自动发现本地工具,shell 工具由参数控制 | agent/src/tools/__init__.py | 某个工具没出现在列表里的时候 |
| 网络传输护栏 | Host 与 Origin 白名单中间件,默认只放行回环地址 | agent/mcp_server.py 内的 _HostGuardMiddleware / _OriginGuardMiddleware | 用 --transport http 并想跨机访问的时候 |
| MCP 客户端适配层 | 反方向:让它自己的 Agent 去调你的 MCP server | agent/src/tools/mcp.py | 想把自研工具喂给它的时候 |
| 技能库 | 88 个技能目录,经 list_skills / load_skill 以文本形式返回 | agent/src/skills/ | 想知道 load_skill 到底吐回什么的时候 |
| 编制预设 | 30 份多智能体团队 yaml,run_swarm 按名字取用 | agent/src/swarm/presets/ | list_swarm_presets 拿到名字之后 |
| 因子库与许可声明 | 各 zoo 的公式实现,以及各自的上游来源与许可 | agent/src/factors/zoo/ 与仓库根目录 NOTICE | 判断能不能商用的时候 |
| 券商连接器 | 12 家连接器实现,MCP 面上只暴露只读部分 | agent/src/trading/connectors/ | 调 trading_select_connection 的时候 |
四、接进来之后,有四个行为跟你的直觉不一样
第一个是会话 id。_resolve_session_id 的注释解释得很清楚:本地工具注册表会由宿主注入会话 id,所以 session_id 根本不在必填 schema 里;MCP 没有这个注入点,早期版本把它标成必填,等于要求模型凭空编一个它无从知晓的内部标识符。现在的做法是每个服务端进程生成一个稳定 id(形如 mcp- 加一段十六进制),客户端如果自己管理会话再显式传。落到你身上就是一句话:别去猜这个参数,让它空着。
第二个是行情返回的截断。get_market_data 有一个 max_rows 参数,默认取 DEFAULT_MAX_ROWS。超过上限的标的不是简单截头留尾,而是等距抽样、并把最后一根 bar 钉住,同时附上截断元数据;没超限的标的原样返回,小查询逐字节不变。想要全量就把 max_rows 设成 0。这个设计的用意是让返回序列仍然覆盖完整区间,而不是中间断一截。
第三个是长任务的保活。run_swarm 在等待期间通过 Context.report_progress 持续发进度帧,注释写明这些帧同时充当传输层保活,所以是每轮都发而不是只在状态变化时发。它还会在开头发一条固定格式的消息 swarm_started run_id=<id>,注释建议解析方按这个字面量匹配——这样即使传输中断,你也能用 run_id 走 get_run_result 把结果捞回来。wait_seconds 用完只是提前返回当前状态,任务本身不受影响;不想阻塞就传 start_only。
第四个是键控工具的报错。get_macro_series 和 iwencai_search 需要各自的密钥,没配的时候它们会被 check_available() 挡在自动发现的注册表之外,走通用路径只会得到一句 “Tool not found”。项目为此专门写了 _execute_key_gated:注册表里没有就直接调具体工具类的 execute(),好让报错点名具体环境变量。这里有个真实的文档差异值得留意——agent/SKILL.md 的工具表格里写的是 IWENCAI_KEY,而服务端代码里写的是 VIBE_TRADING_IWENCAI_KEY;配环境变量的时候以代码为准。
五、反方向也通:它也能当 MCP 客户端
agent/src/tools/mcp.py 是另一条路。agent/SKILL.md 专门加了注释区分:MCP 插件那节是把 Vibe-Trading 的工具给你的 Agent 用,这一节是让 Vibe-Trading 自己的 Agent 去调你的 server。配置写在 ~/.vibe-trading/agent.json:
{
"mcpServers": {
"my-server": {
"command": "uvx",
"args": ["my-mcp-server"],
"toolTimeout": 30,
"enabledTools": ["*"]
}
}
}
这层适配的几个决定值得抄。远程工具进来后按 mcp_<server>_<tool> 命名,make_mcp_tool_name 会把两段都做 ASCII 化清洗;不同原始名清洗后撞车时,_dedupe_server_name_segment 会在这段服务器名后面追加一小截 sha1(取的是原始 server 名的哈希,所以同一个配置项每次得到的后缀都一样),让两组工具名重新分得开。_tool_is_enabled 的语义是白名单:enabled_tools 为空表示一个都不放行,只有 * 或精确命中才通过。normalize_mcp_tool_schema 把远端 schema 归一成顶层对象形态,顺手折叠可空联合。_filter_arguments 会在转发前剔掉本地专有参数(当前是 run_dir)。
安全取向也写在注释里。MCPRemoteToolSpec.annotations 那段说得很明白:服务端自报的 readOnlyHint / destructiveHint 只是来自可能不可信一方的提示,绝不能作为放宽安全的唯一依据。重试策略同理——list_tools 允许一次瞬时重试,工具调用则不重试,理由是远端工具可能已经在超时前提交了副作用,重试等于把副作用做两遍。这条判断在任何工具层设计里都成立,可以对照工具返回设计那篇一起看。
六、边界与代价:它明确不管什么
这一节请认真读,接之前想清楚比接之后返工便宜。
放弃了执行能力,这是主动选择。 MCP 面不暴露任何下单撤单工具,_risk_tier_from_text 还会直接拒绝执行类的风险档位,抛出 “live trading or execution goals are not supported”。你想要一条从研究直通下单的链路,这个入口给不了,也不打算给。
shell 工具默认关闭。 bash / background_run / cancel_background 这组进程控制工具在模块级默认为关,只有显式传 --enable-shell-tools 或设 VIBE_TRADING_ENABLE_SHELL_TOOLS 才注册。注释解释了原因:一旦 MCP 服务端可达,这组工具就是远程进程控制面,传输类型不构成隐式授权;早先 stdio 传输强制开启且无法关闭,注释里直接引用了两个 GHSA 编号作为背景。如果你确实要开,先把最小权限设计那套约束先立起来。
网络传输的默认可达范围只有本机。 _DEFAULT_MCP_ALLOWED_HOSTS 是 127.0.0.1 / ::1 / localhost,Host 与 Origin 两道中间件在进入 MCP 会话之前就拦。注释说明 FastMCP 本身不带主机与来源校验,所以这层是项目自己包的。想放开必须显式配 VIBE_TRADING_MCP_ALLOWED_HOSTS,而这一步意味着你要自己承担暴露面。
文件读写是有根目录约束的,不是自由读写。 agent/src/tools/path_utils.py 里读和写是两套互相独立的白名单:写入侧 allowed_write_roots 有一组默认根(uploads 与 runs 系列目录,分别挂在 agent 根、当前工作目录、家目录下的 .vibe-trading 以及运行时根下),额外根由 VIBE_TRADING_ALLOWED_WRITE_ROOTS 配,write_file 与 edit_file 都取这一套;读取侧是另一个函数 allowed_file_roots,read_file 取的是它。所以「能读」不等于「能写」,改环境变量的时候别配错那一个。
客户端侧的 v1 限制写在文档里。 agent/SKILL.md 的「v1 limits」一节列了五条,除去第一条只是说明三种传输都支持,剩下四条都是实打实的限制:执行只支持串行,MCP 工具不进并行只读路径;只暴露 tools,resources 与 prompts 不暴露;swarm worker 的注册表在 v1 里排除 MCP 工具;不支持热重载,改配置要重启进程。同一节下面还接了一张失败处理表,把配置文件缺失、配置文件非法、某个 server 起不来分别对应到什么行为写清楚了——最后一条是「跳过这个 server,本地工具和其它 server 照常加载」,意味着你不会因为一个外部 server 挂掉就整个 Agent 起不来,但也意味着工具悄悄少了一批而进程看起来一切正常,这种情况只能靠日志发现。
凭据与券商连接是最需要慎重的一块。 trading_* 虽然只读,但它读的是你的真实账户。这意味着几件事必须自己认清楚:连接器凭据(本地 TWS/Gateway 会话、OAuth token)一旦落到 Agent 可达的进程里,暴露面就等于那个进程的暴露面;项目把 OAuth token 存进 FileTreeStore 并把目录 chmod 成 0700,这只挡得住同机其它用户,挡不住被接管的 Agent 本身;反向接入侧的 _format_zero_enabled_tools_warning 还专门为实盘券商 server 留了一条定制文案——当某个被识别为 Robinhood 的实盘 MCP 配了通配符 enabledTools 却发现零个可用工具时,它不是笼统报「检查白名单」,而是直接把该走的只读工具名列出来并指向那份安全种子配置。这个细节的含义是:实盘 server 在这套适配层里被当作需要显式收窄的一类,通配符在这儿不是省事写法而是可疑写法。更进一步说,一旦有任何环节能触达下单能力,错单是不可撤销的,程序化交易的合规义务也因司法辖区而异——能不能这么用,以你所在司法辖区的监管要求与券商协议为准。
因子库不是这个项目原创的,别当自研写进你的文档。 仓库根目录 NOTICE 声明得很细:qlib158 打包了 Microsoft Qlib 的特征定义,走 Apache 2.0 并钉了上游 commit SHA;alpha101 来自 Kakushadze (2015) 的 arXiv:1601.00991;gtja191 来自国泰君安 2014 年那份研报;academic 一组来自 Fama-French 等公开模型。NOTICE 明确说明只重实现了数学公式这一类事实性内容,原文的散文、表格与图未被复制,各 zoo 子目录下另有 LICENSE.md。这些是公开公式的工程化重实现,仅此而已,与效果无关。本文不提供法律意见,能不能商用以许可证原文为准;仓库整体是 MIT(Copyright 2026 Vibe-Trading Contributors)。
七、上手与避坑清单
装完找不到 vibe-trading-mcp。 为什么会踩:包名是 vibe-trading-ai,命令名是 vibe-trading-mcp,两者不同,装完按包名去敲必然找不到。怎么避:先确认仓库根目录 pyproject.toml 的 [project.scripts] 里那两行映射到底给了哪些命令名,命令找不到就在配置里退回直接指向 agent/mcp_server.py 的脚本路径写法。
HTTP 传输指错端点。 为什么会踩:很多人凭 SSE 时代的印象去连 /sse。怎么避:Streamable HTTP 是单端点,指向 http://<host>:<port>/mcp,服务端注释里把这点专门标了出来。
跨机连不上,误以为服务没起来。 为什么会踩:默认只放行回环 Host 与 Origin,非本机请求会被中间件在会话前挡掉,返回的是 400 或 403 而不是连接失败,看起来像应用层问题。怎么避:确认这是主机白名单在生效,要放开就配 VIBE_TRADING_MCP_ALLOWED_HOSTS,同时想清楚放开之后谁能连上来。
模型在 goal 系工具上卡住,因为它想编 session_id。 为什么会踩:这个参数看名字像必填。怎么避:留空让服务端用进程级 id,除非你的客户端确实自己管理会话。
行情数据看着比预期少。 为什么会踩:默认有每标的行数上限,超限走等距抽样。怎么避:读返回里的截断元数据确认,真要全量就把 max_rows 设成 0,同时接受载荷变大。
swarm 跑一半客户端超时。 为什么会踩:客户端可能有硬性的工具调用超时,不认进度通知。怎么避:从第一条 swarm_started run_id=<id> 进度消息里把 run_id 记下来,之后用 get_swarm_status 或 get_run_result 单独取;或者干脆传 start_only 立即返回。
反向接自研 server 时零工具。 为什么会踩:enabledTools 为空等于全部禁用,不是默认全开。怎么避:显式写 ["*"] 或精确工具名;真的出现零工具时,日志里会有一条点名该 server 的警告,按它排查。
两个 server 名字清洗后撞车,工具名变得认不出来。 为什么会踩:名字会被 ASCII 化清洗,foo-bar 与 foo_bar 会塌成同一段。怎么避:见到带 sha1 后缀的前缀就说明触发了消歧,直接在 agent 配置里把 server 改名回来。
收尾
判断这个入口值不值得接,问自己三个问题就够:你要的是研究工具还是执行通道(是后者就别接,它结构性不给);你的客户端支持 stdio 还是只能走 HTTP(决定传输选型和要不要面对主机白名单);你是否准备好为券商凭据的暴露面负责(不确定就先只用免密钥那批读取类工具)。
接下来该读哪个文件,顺序建议是:先通读 agent/mcp_server.py 顶部的模块 docstring,工具面和三种传输的取舍全在那三十来行里;再翻 agent/SKILL.md 的工具表格与 v1 限制两节,对齐预期;打算反向接自研工具的,最后看 agent/src/tools/mcp.py 的命名、白名单与重试三段注释。至于 agent/src/skills/ 和 agent/src/swarm/presets/,等你真的调通 list_skills 与 list_swarm_presets 之后再对着目录看,会比现在读高效得多。
本篇属于一个把开源个人交易 Agent 项目 Vibe-Trading逐层拆开讲的系列,整体地图见 Vibe-Trading 是什么:HKUDS 这个开源交易 Agent 项目的工程全景与边界;沿着这条线往下,还可以看 开源项目 Vibe-Trading 渠道层:接进聊天软件的代价与新问题 和 Vibe-Trading 开源仓库的 Web 层:一条时间线让长任务可见。