首页
/ last30days-pp-mcp:把 last30days 研究引擎打包成 Claude Desktop 可拖拽安装的 .mcpb 插件

last30days-pp-mcp:把 last30days 研究引擎打包成 Claude Desktop 可拖拽安装的 .mcpb 插件

2026-09-04 12:27:20作者:殷蕙予

last30days-skill 的 Python 研究引擎原本通过 Claude Code 的 /last30days <topic> 斜杠命令使用;mcp/ 目录下的 Go 项目 last30days-pp-mcp 则为 Claude Desktop 提供了另一条等价的接入路径。本篇基于 mcp/README.md 展开,结合 mcp/ 下的 Go 源码、mcp/manifest.json 与构建脚本,讲清楚这个 MCP 服务器如何把内嵌的 Python 引擎解压到用户级缓存、通过 python3 子进程执行研究任务,以及完整的本地构建与 .mcpb 打包流程。读完后你将掌握:.mcpb 清单的字段语义与 API Key 注入机制、引擎嵌入/解压/执行三层的实现细节、超时与缓存目录等可调环境变量的含义,以及可复制的构建命令。

一、它是什么:一个包装器,而非重写

mcp/README.md 对项目的定位一句话讲清:"Go MCP server that wraps the last30days Python engine for Claude Desktop. Packaged as a .mcpb bundle (drag-drop install into Claude Desktop)"(用 Go 编写的 MCP 服务器,把 last30days Python 引擎包装给 Claude Desktop 使用,以 .mcpb 捆绑包形式交付,拖入即可安装)。

关键设计决策是不重写研究逻辑:MCP 服务器对外暴露研究工具(README 原文称其镜像 Claude Code 中的 /last30days <topic> 斜杠命令),运行时的实际行为是——二进制把内嵌(vendored)的 Python 引擎解压到按版本隔离的用户缓存目录,然后 shell out 到宿主机的 python3 解释器,由 Python 引擎产出综合报告所需的原始材料,再交还给 Claude 渲染。也就是说,真正的多源检索、评分、综合流水线仍然运行在 skills/last30days/scripts/ 这套 Python 代码里,Go 一侧只承担三件事:

  • 以 stdio 方式提供 MCP 协议端点;
  • 把引擎源码经 //go:embed 内置进二进制并在首次使用时解压;
  • 管理子进程的生命周期(超时、环境变量、错误归一化)。

这个"薄壳 + 宿主解释器"的取舍直接决定了终端用户的运行环境要求:宿主必须装有 Python 3.12+ 且在 PATH 中(README "Runtime requirements" 一节明确:bundle 只携带引擎源码,不携带解释器)。

二、目录结构与职责

README 的 "Architecture" 小节列出了五个构件,与仓库实际文件一一对应:

路径 职责
mcp/cmd/last30days-pp-mcp/ 服务器入口,注册工具并经 stdio 提供服务
mcp/internal/engine/ Python 引擎的 embed.FS、缓存解压器、子进程封装
mcp/internal/tools/ MCP 工具处理器(research,另有 preflight
mcp/internal/engine/vendored/ skills/last30days/scripts/ 的镜像,由 mcp/scripts/sync-engine.sh 生成(gitignored)
mcp/manifest.json Claude Desktop 与 printing-press bundle 消费的 MCPB v0.3 清单

其中 vendored/ 为什么放在 internal/engine/ 内部,README 给出了一条精确原因:"//go:embed cannot reach files outside its own package directory"(Go 的 //go:embed 无法引用其所在包目录之外的文件)。这与 sync-engine.sh 中的注释互为印证:"Embed path must live inside the consuming package ... so vendored/ sits under engine/"。当前仓库中 mcp/internal/engine/vendored/ 是空的(gitignored,仅保留 .gitkeep 锚定 embed 路径),任何一次构建前都需要先跑同步脚本把引擎拷进来。

注意一点版本演进:README 描述 MCP 服务器"exposes a single research tool",但从 mcp/internal/tools/research.goRegister 函数看,当前代码实际注册了 researchpreflight 两个工具——文档描述的是主体工具面,preflight 是后续加入的安全预检工具(下文详述)。

三、.mcpb 清单:MCPB v0.3 的关键字段

mcp/manifest.json 是整个捆绑包对 Claude Desktop 的"合同",当前版本 3.6.0。核心字段如下:

  • manifest_version: "0.3" / server.type: "binary":声明这是一个二进制型 MCP 服务器,入口为 bin/last30days-pp-mcp,以 ${__dirname}/bin/last30days-pp-mcp 启动、无额外命令行参数;
  • compatibility:要求 claude_desktop >= 1.0.0,目标平台为 darwinlinux(从源码结构看,run.go 对 Windows 场景也有显式错误处理,但清单声明的发布平台目前只有这两个);
  • license: "MIT"display_name: "Last30Days" 等元数据。

3.1 user_config:13 个可选 API Key 的注入

清单的 user_config 段定义了 13 个敏感("sensitive": true)但均可选("required": false)的字符串配置,每一项都通过 server.mcp_config.env 里的 ${user_config.xxx} 占位符映射到子进程环境变量。Claude Desktop 在安装向导中收集后,会在启动 MCP 服务器进程时完成注入,引擎的 Python 代码再从环境中读取。完整映射关系(user_config 键 → 环境变量 → 用途,依据 manifest 中各条目的 description):

user_config 键 注入的环境变量 用途
openai_api_key OPENAI_API_KEY 经 OpenAI web_search 工具驱动 Reddit 研究
xai_api_key XAI_API_KEY 经 xAI x_search 工具驱动 X / Twitter 研究
brave_api_key BRAVE_API_KEY 有事实依据的网络搜索结果
exa_api_key EXA_API_KEY 备选网页搜索后端(语义排序)
serper_api_key SERPER_API_KEY 经 API 的 Google 搜索
google_api_key GOOGLE_API_KEY YouTube 转录抓取等 Google 服务
gemini_api_key GEMINI_API_KEY 其他 LLM 提供商不可用时综合步骤的兜底
google_genai_api_key GOOGLE_GENAI_API_KEY GEMINI_API_KEY 同源的备选命名
apify_api_token APIFY_API_TOKEN 经 Apify actors 的 TikTok / Instagram Reels 搜索
bsky_app_password BSKY_APP_PASSWORD Bluesky 应用专用密码(AT Protocol 帖子搜索)
parallel_api_key PARALLEL_API_KEY 跨源并行研究运行
scrapecreators_api_key SCRAPECREATORS_API_KEY 覆盖 TikTok / Instagram / YouTube 的创作者向搜索
openrouter_api_key OPENROUTER_API_KEY 综合步骤的备选 LLM 网关

这里有一个容易被忽视的机制:这些 env 注入发生在 Go 子进程这一层mcp/internal/engine/run.gocmd.Env = buildEnv(...) 把父进程(即被 Claude Desktop 以注入环境变量方式启动的 MCP 服务器)的整个环境传给 Python 子进程,README 所说的"shells out to python3"因此天然携带全部 Key——Python 引擎侧无需任何 MCP 特有代码。

四、服务器入口:stdio 服务与版本戳

入口实现非常薄,见 mcp/cmd/last30days-pp-mcp/main.go

  1. mark3labs/mcp-goserver.NewMCPServer("last30days", "1", server.WithToolCapabilities(false)) 创建 MCP 服务器;
  2. tools.Register(s, tools.Config{Version: Version}) 注册工具;
  3. server.ServeStdio(s) 把协议端点绑在 stdio 上——这正是 Claude Desktop 以本地进程方式拉起 MCP 服务器所要求的传输形态。

版本戳是入口里的另一个要点(main.go#L16-L19):

// Version is stamped at build time via -ldflags "-X main.Version=<tag>".
// It namespaces the per-user cache directory in internal/engine so multiple
// installed versions can coexist without clobbering each other.
var Version = "dev"

Version 默认值为 "dev",构建时经 -ldflags "-X main.Version=..." 注入(README 的本地构建命令即 -ldflags "-X main.Version=dev";发布 CI 则从 git tag 盖戳)。它的真正用途不是展示,而是给每个已安装版本的引擎缓存目录命名空间,使不同版本的 bundle 可以共存而互不覆盖。

五、引擎嵌入与缓存解压:embed.FS 的完整生命周期

这一层是 README "At runtime the binary extracts the vendored Python engine into a per-user cache" 一行的完整实现,分布在 mcp/internal/engine/embed.gomcp/internal/engine/extract.go

5.1 构建期:同步与内嵌

scripts/sync-engine.sh 是构建前的必要步骤("Run before go build locally and in CI before printing-press bundle"),其逻辑为:

  1. skills/last30days/scripts/ 为唯一事实来源("Source of truth ... Never edit mcp/vendored/ directly"),把 last30days.py 与整个 lib/ 目录拷入 mcp/internal/engine/vendored/
  2. 清空旧内容但保留 .gitkeep 锚点;
  3. 删除 __pycache__*.pyc,保证 embed.FS 内容确定(deterministic)。

embed.go 随后用 //go:embed all:vendored 把目录编译进二进制。all: 前缀是有意为之:它保留以 ._ 开头的文件,让 .gitkeep 锚点能在同步脚本运行前存活——否则空目录会导致 embed 直接编译报错。EngineFS()fs.Sub 把根重定位到 vendored/ 内部,因此调用方看到的是 last30days.py 而非 vendored/last30days.py

5.2 运行期:按版本命名空间 + 原子解压

Ensure/EnsureUserCacheextract.go#L36-L69)定义了缓存布局 <OS 用户缓存目录>/last30days-pp-mcp/<version>/,并带有三个工程上值得注意的细节:

  • 哨兵文件短路:解压成功后写入 .version 哨兵(内容即版本号),后续调用若版本匹配则直接复用目录,不重复写盘(sentinelMatches);
  • 原子性:解压先写入兄弟目录 cacheDir + ".tmp",成功后 os.Rename 提升为正式目录(extract.go#L91-L98),保证"半截解压"永远不会被误认为完整引擎;
  • 并发与可重试:同一进程内对同一缓存目录的并发调用由每目录一个 sync.Once 串行化,使 rename 恰好发生一次;而失败时 resetOnce 会清掉 Once,让下一次调用可以重试——注释说明这是因为失败往往是瞬态的(磁盘满、父目录恢复);
  • 只读环境逃生口LAST30DAYS_CACHE_DIR 环境变量可把缓存重定向到别处,注释中点名的场景是锁定公司镜像与临时 CI 容器(extract.go#L22-L25)。

六、执行层:shell out 到 python3 的规则

run.go 封装了 engine.Run,即"在缓存目录里用 python3 跑 last30days.py"的全部约定:

const DefaultPythonBinary = "python3"   // 找不到时给出明确报错,而非静默选错二进制
const MinPythonVersion = "3.12"          // 与引擎 last30days.py 的 MIN_PYTHON 保持一致
const DefaultTimeout = 5 * time.Minute   // 单次研究子进程上限;deep 模式可能跑几分钟
const TimeoutEnvOverride = "LAST30DAYS_MCP_TIMEOUT"
  • 解释器解析:优先用调用方传入的 PythonPath(测试用来注入 stub 解释器);否则在 PATH 上查找 python3,找不到时报错信息会带出所需最低版本与安装指引("need Python 3.12+"),对应 README 的运行时要求;
  • 超时:显式传入 RunOptions.Timeout 优先;其次读取 LAST30DAYS_MCP_TIMEOUT(注释写明"seconds, integer",实现上同时接受 300 这类裸秒数与 Go duration 字符串);都缺省则 5 分钟。超时与"非零退出"、"解释器缺失"被归一为三种互不相同的错误,工具处理器据此映射出面向用户的文案,无需再去解析 stderr;
  • PYTHONPATH 手术run.go#L149-L168):buildEnv丢弃父环境中任何既有的 PYTHONPATH= 条目,再追加指向缓存目录的新值。注释解释了为什么必须丢弃:POSIX getenv 遇到两个同名条目返回第一个,若保留用户值,引擎的 from lib import ... 会因找不到自己的 lib 而抛 ModuleNotFoundError。引擎自包含,不需要宿主的模块搜索路径;
  • 输出约定Stdout 是呈现给 agent 的正文;Stderr 被保留在 RunResult 中并在错误信息里附出(research.go#L148-L158formatRunError 会把 engine stderr: 拼进去),让用户不离开 Claude Desktop 也能诊断引擎故障。

七、MCP 工具面:research 与 preflight

7.1 research 工具

research 工具的 schema 定义在 research.go#L27-L43

参数 类型 必填 说明
topic string 研究对象(人物、公司、产品、事件或一般主题),空串直接报错
emit string compact(默认,供内联综合)或 html(另存可分享简报)
save boolean 把综合结果持久化为 Markdown 报告,默认目录 ~/Documents/Last30Days/,可用 LAST30DAYS_MEMORY_DIR 覆盖

工具描述本身也是给模型看的提示词:"Research what people are actually saying about any topic in the last 30 days. Aggregates Reddit, X, YouTube, Hacker News, Polymarket, GitHub, and the web, scored by upvotes, likes, transcripts, and real-money prediction-market odds. Returns the engine's compact output for the model to synthesize."——即工具返回的是引擎的紧凑输出,最终综合仍由宿主模型完成。

参数到命令行实参的映射在 research.go#L89-L103

func researchRunArgs(topic, emit string, save bool) []string {
    runArgs := []string{topic, "--emit=" + emit, "--no-browser-cookies"}
    if save {
        runArgs = append(runArgs, "--save-dir", mcpSaveDir())
    }
    return runArgs
}

两点值得注意:

  1. --no-browser-cookies 无条件追加。桌面端 MCP 进程无法像 Claude Code 环境那样引导用户授权读取浏览器 Cookie,因此固定禁用该路径,这是 CLI 与 MCP 两种形态的行为差异之一;
  2. save=true 时追加 --save-dir,目录取 LAST30DAYS_MEMORY_DIR,缺省 ~/Documents/Last30Days——与工具描述中向模型承诺的落盘位置严格一致。

7.2 preflight 工具

tools/preflight.go 注册了第二个工具 preflight,描述为"Safely summarize what last30days would read, write, execute, and contact without running research, saving files, or reading browser cookies"(安全地总结 last30days 将会读什么、写什么、执行什么、联系什么,而不真正跑研究、不存文件、不读浏览器 Cookie)。它只有一个可选参数 formattext 默认 / json),底层执行的实参是:

runArgs := []string{"--preflight", "--preflight-report-on-save-dir", mcpSaveDir()}
// format == "json" 时追加 "--emit=json"

并标注了 WithReadOnlyHintAnnotation(true)(对照 researchfalseWithOpenWorldHintAnnotation(true)),让客户端明确区分只读预检与开放式研究。这一工具对应 Python 侧的 preflight 能力(见 skills/last30days/scripts/lib/preflight.py),为 Claude Desktop 这类"先问后跑"的场景提供了低成本的信任入口。

八、本地构建与打包

mcp/README.md 的 "Local build" 一节给出了三步命令(在 mcp/ 目录下执行,Go 模块要求 Go 1.25.5,见 mcp/go.mod):

# 1. 把 Python 引擎镜像进 vendored/
bash scripts/sync-engine.sh

# 2. 为当前主机构建二进制
go build -ldflags "-X main.Version=dev" -o build/last30days-pp-mcp ./cmd/last30days-pp-mcp

# 3. 打包为 .mcpb(需 printing-press 二进制在 PATH 中)
printing-press bundle . --skip-build --binary build/last30days-pp-mcp

产出物落在 build/last30days-pp-mcp-<os>-<arch>.mcpb,README 的安装方式即"Drag it into Claude Desktop's Extensions panel to install"——把 .mcpb 拖进 Claude Desktop 的 Extensions 面板完成安装,这正是 MCPB(printing-press bundle)相比手写 claude_desktop_config.json 的低门槛所在:清单中的 user_config 会自动变成安装向导中的表单字段。

发布流程方面,README "Versioning" 一节的规则是:MCPB 的 manifest.json 版本在与"值得发布的引擎变更"同批的 PR 里手工递增;发布 CI 则从 tag 盖戳 Go 二进制的 main.Version。由于缓存目录按该版本号命名空间,这条纪律直接保证了引擎升级后用户机器上的引擎代码会重新解压、而不是静默沿用旧版。

九、运行前提与边界

把分散在 README、manifest 与源码中的约束汇总成清单,便于部署前核对:

  • 宿主必须有 Python 3.12+ 且在 PATHpython3 可解析);缺失时错误信息会直接给出最低版本,不会静默降级;
  • 平台:manifest 声明 darwinlinux,且要求 Claude Desktop ≥ 1.0.0;
  • 可调项LAST30DAYS_MCP_TIMEOUT(子进程超时,秒)、LAST30DAYS_CACHE_DIR(缓存目录重定向)、LAST30DAYS_MEMORY_DIRsave=true 时的报告落盘目录);
  • API Key 全部可选:不配置任何 user_config 时服务器仍可启动,但各数据源的可用性会按引擎自身的降级逻辑收缩——这是引擎侧行为,清单本身不做强制校验;
  • vendored 是构建期产物:仓库中 mcp/internal/engine/vendored/ 内容为空属正常状态,任何直接 go build 之前必须先执行 sync-engine.sh,否则内嵌引擎不完整。

十、小结:为什么值得这样设计

mcp/ 这个子项目示范了一个务实的跨宿主移植模式:用单一二进制承载"协议端点 + 引擎分发 + 进程管理"三件小事,把全部研究逻辑留在原 Python 引擎中不复制、不分叉//go:embed + 按版本命名空间的缓存 + 原子 rename,让引擎升级随 .mcpb 版本自然滚动;PYTHONPATH 清洗、超时归一化、stderr 随错误上报,则把"子进程"这种最容易失控的边界收拾得可诊断。对维护者而言,同步脚本保证了 skills/last30days/scripts/ 仍是唯一事实来源;对终端用户而言,整个安装就是"拖一个文件 + 填几个可选 Key"。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341