Install
openclaw skills install @dengkane/portfolio-tool用自然语言对话式操作 Portfolio_tool 基金组合研究工具——查组合、同步净值、看跟踪、搜基金、管账号,本机指纹自动身份、免登录,MCP 封装 19 个工具即装即用。 把繁琐的基金组合研究变成一句对话。装上这个技能,你无需写代码、也无需手动调接口,直接用自然语言就能「列出我的组合」「同步基金净值」「看某只基金的跟踪状态」「搜基金 000001」「绑定账号」——后端 REST 能力被封装成 19 个结构化 MCP 工具,自动调用、结果直接呈现在对话里。身份识别用「本机数字指纹」静默完成:首次自动生成、之后永久复用,组合数据始终归属同一用户,免注册、免登录。
openclaw skills install @dengkane/portfolio-tool本 Skill 配合 portfolio-tool MCP Server 使用。MCP 端点由后端 FastAPI 应用自身挂载——FastMCP 与 REST API 运行在同一个 uvicorn 进程,无需单独启动 MCP 服务,也不提供 stdio 进程。挂载后同时提供两种传输,客户端任选其一:
https://invest.geeyo.com/mcp/httphttps://invest.geeyo.com/mcp/sse启用方式:在后端 .env 设置 MCP_ENABLED=True 并重启后端即可。
MCP 将后端 REST API 封装为结构化工具,Agent 直接调用即可,无需关心底层 HTTP 细节。
「我的组合」与写操作均需用户身份。本服务采用数字指纹(Device Fingerprint)方案:由调用 MCP 的客户端 Agent 在用户本机根据固定机器特征计算一个稳定不变的 UUID 作为身份,并在连接时携带。禁止由后端/服务器代生成——否则所有用户会变成同一个服务器身份,互相串号。
在用户本机,用以下固定特征计算稳定 ID(同一机器恒定不变):
/etc/machine-id、/var/lib/dbus/machine-id、/etc/hostidHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MachineGuid主机名 + MAC 地址 + 系统用户名 + 操作系统名xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx~/.portfolio/aid(一行);后续所有会话/重启均读取同一 ID,不再生成新的,组合数据始终归属同一用户。此 UUID 同源承担两个角色:用户身份
anonymous_id与 设备指纹fingerprint,既是~/.portfolio/aid内容,也是连接 MCP 时X-Anonymous-Id请求头的值。
伪代码:
raw = read_machine_id() or (hostname + mac + username + os)
h = sha256(raw).hexdigest()
fp = f"{h[0:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:32]}"
if not exists("~/.portfolio/aid"):
write("~/.portfolio/aid", fp)
身份就绪后,MCP 会在首次工具调用(如 get_me、list_portfolios(mine=true)、bind_account)时自动向后端发起注册请求,无需手动调用;只需保证每次连接都携带同一个 X-Anonymous-Id。接口契约:
接口:POST /api/user/init
请求头:Content-Type: application/json、Accept: application/json
请求体:
| 字段 | 类型 | 说明 |
|---|---|---|
anonymous_id | string(≤64) | 数字指纹 UUID(~/.portfolio/aid 内容,亦为 X-Anonymous-Id 头的值) |
fingerprint | string(≤256) | 设备指纹哈希,与 anonymous_id 同源,当前统一传同一 UUID |
响应体:
| 字段 | 类型 | 说明 |
|---|---|---|
anonymous_id | string | 后端确认的用户标识(与请求一致) |
is_new | bool | true=新建;false=已存在(仅刷新活跃时间) |
risk_agreed | bool | 是否已同意风险揭示书 |
幂等性:同一 anonymous_id 重复调用返回已存在用户(is_new=false),不重复建号。
传递方式:anonymous_id 既作为 X-Anonymous-Id 请求头在每个 MCP/REST 请求携带,也作为 init 请求体字段;fingerprint 仅作为 init 请求体字段。X-Anonymous-Id 是后续所有写操作与「我的组合」的身份凭证。
本 MCP 挂载在共享后端(多用户共用同一进程),后端不生成任何 ID,仅读取连接时携带的 X-Anonymous-Id 头并原样转发给 REST。因此:
X-Anonymous-Id 头(身份凭证,非可选项):
/mcp/http):在连接级 headers 设置 X-Anonymous-Id: <本机数字指纹UUID>/mcp/sse):同样在连接级 headers 设置 X-Anonymous-Id: <本机数字指纹UUID>~/.portfolio/aid 内容复制过来,或设置环境变量 PORTFOLIO_AID 为旧 ID 后重连。该 ID 为机器级(同机共享),与浏览器端
localStorage的aid机制不同但效果一致(首次生成、之后复用)。跨设备恢复组合请使用账号体系(bind_account/login_account)。
mcp__portfolio-tool-http-stream__list_portfolios)。连接器连上后直接调用,身份头由连接器注入(localhost 自动注入;生产需在 mcp.json 对应条目配置 headers.X-Anonymous-Id)。scripts/pf.py(直连 REST 兜底)。出现以下任一即判定 MCP 不可用并切换:工具索引中找不到 mcp__portfolio-tool__*;调用返回连接错误/超时;返回 400 缺少 X-Anonymous-Id 且无法快速修复。| 工具 | 说明 |
|---|---|
list_portfolios(mine: bool) | 列出组合;mine=true 我的组合,false 公开组合 |
get_portfolio(portfolio_id: int) | 详情:持仓(含最新净值/最近涨跌)、回测曲线、跟踪状态 |
get_tracking(portfolio_id: int) | 提取跟踪状态(数据截至日/最新净值/当日涨跌/成立以来收益) |
create_portfolio(name, risk_level, holdings, ...) | 创建组合 |
update_portfolio(portfolio_id, name, risk_level, holdings, ...) | 编辑组合 |
delete_portfolio(portfolio_id: int) | 删除组合 |
clone_portfolio(portfolio_id: int) | 克隆公开组合到当前用户 |
trigger_backtest(portfolio_id: int) | 触发回测(重算曲线与跟踪) |
create_portfolio / update_portfolio 参数:
risk_level: low | medium | highholdings: 持仓列表,每项 {fund_code, weight, note?};权重总和须为 1.0(±0.0001)、单只 ≤ 0.8、代码不重复、权重范围 (0,1]description、is_public、rebalance_freq(none|monthly|quarterly)、benchmark_type(csi300|csi_bond|custom)| 工具 | 说明 |
|---|---|
search_funds(q: str) | 按代码/名称模糊搜索 |
get_fund_detail(fund_code: str) | 基金基本信息 |
get_fund_nav(fund_code: str, start_date?, end_date?) | 净值历史(YYYY-MM-DD,留空返回全部;先增量同步再返回) |
sync_fund_data(force: bool = False) → 返回 job_id(后台异步,HTTP 202)get_sync_status(job_id) → 轮询直到 status=completed/failed(含 synced_funds/updated_navs/tracking_updated 计数)| 工具 | 说明 |
|---|---|
get_me() | 当前用户信息(是否绑定账号) |
bind_account(username, password) | 为当前匿名用户绑定登录账号 |
login_account(username, password) | 账号密码登录,返回 anonymous_id(用于跨设备恢复) |
change_password(old_password, new_password) | 改密码 |
unbind_account() | 解绑账号(保留组合数据) |
health_check() | 后端健康检查 |
同步我的组合基金数据并刷新跟踪状态:
sync_fund_data() → 取得 job_idget_sync_status(job_id) 轮询至完成get_portfolio(<id>) 或 list_portfolios(mine=true) 查看最新净值/数据截至日新设备找回组合: login_account(username, password) 拿 anonymous_id → 写入 ~/.portfolio/aid(并设 PORTFOLIO_AID)→ 列组合。
MCP 连不上时使用。用法:
python <skill_dir>/scripts/pf.py METHOD PATH [--data '{"k":"v"}'] [--q key=value]
示例:
python scripts/pf.py GET portfolios?mine=true
python scripts/pf.py GET portfolios/12
python scripts/pf.py POST sync/portfolio-funds --data '{}'
python scripts/pf.py GET sync/portfolio-funds/status/<job_id>
python scripts/pf.py POST user/login --data '{"username":"alice","password":"secret"}'
脚本自动补 /api/ 前缀、附加 X-Anonymous-Id(读 ~/.portfolio/aid 或 PORTFOLIO_AID),输出格式化 JSON。
PORTFOLIO_API 决定:scripts/pf.py 默认 http://localhost:8000(本地开发);生产 MCP 端点为 https://invest.geeyo.com。~/.portfolio/aid)生成并作为 X-Anonymous-Id 传入后,写操作与「我的组合」即可正常工作,无需在 Skill 内额外指定 ID。仅以下情况需手动覆盖:
PORTFOLIO_AID=<id>~/.portfolio/aid 内容,或设 PORTFOLIO_AID 为旧 IDX-Anonymous-Id:WorkBuddy 连接器仅在 localhost 自动读取 ~/.portfolio/aid 注入身份头,对 https://invest.geeyo.com 等远程地址不会自动注入,导致写操作返回 400 缺少 X-Anonymous-Id。须在 ~/.workbuddy/mcp.json 对应条目增加 "headers": {"X-Anonymous-Id": "<本机aid>"},并在连接器面板重新信任/连接使配置生效。headers 仅在 streamable-http 传输生效:portfolio-tool-http-stream(/mcp/http,streamable-http)能正确带身份头;portfolio-tool-http(/mcp/sse,SSE)在连接器侧不注入自定义 headers,认证接口仍报 400。生产请使用 portfolio-tool-http-stream 这套工具(mcp__portfolio-tool-http-stream__*);公开组合列表(mine=false)两种传输均可取,因其无需身份头。invest.geeyo.com 为两套独立库——同一匿名 ID 在生产查询「我的组合」可能为空。将本地组合迁移至生产:在 localhost 后端 bind_account 绑定账号,再于生产用同一账号 login_account 恢复(纯匿名 ID 不会自动迁移)。403 Error 1010;本地/可信后端无此问题。