内容
<div align="center">
<img src="docs/banner.svg" alt="mcp-terra" width="100%">
# mcp-terra
**从单句命令运行、自动修复和解释您的Terra笔记本和WDL工作流,无需任何删除或覆盖原始文件。**
[](https://github.com/talha1369/mcp-terra/actions/workflows/ci.yml)
[](LICENSE)



</div>
---
MCP(模型上下文协议)服务器,允许AI助手 - 任何MCP-aware代理 - 代表您管理Terra(terra.bio)工作区、运行时VM、工作区存储桶和WDL/Cromwell工作流。
> **它填补的空白:** 在您的笔记本电脑上,AI编码助手运行您的代码并修复错误;在Terra上,它只能将笔记本复制到您的存储桶中。`mcp-terra`允许助手驱动整个循环 - 配置合适大小的VM,运行笔记本,自动修复失败并重新运行,然后**发送验证报告 + 音频解释**结果 - 具有受控访问保护,支出上限和24小时会话感知。
### 您可以使用
| | |
|---|---|
| 🚀 **运行整个循环** | 配置合适大小的VM → 运行笔记本 → 自动修复错误 → 重新运行,仅需一句命令。 |
| 🛠️ **确定性自动修复** | 0级分诊(`missing_module`,`name_error`,...)修复大多数故障,无需LLM成本;仅在需要时升级。 |
| 📧 **验证报告 + 音频** | 第二个代理根据运行日志交叉检查报告;可选的NotebookLM式音频解释。 |
| 🧬 **WDL / Cromwell** | 编写、注册和提交工作流;`scatter`将数百个Batch任务并行分发。 |
| 🔒 **安全构造** | **任何层级都没有删除或覆盖原始文件** - 加上工作区存储桶允许列表,HMAC签名作业,审计跟踪和终止开关。 |
| 💸 **支出控制** | 可选的每次运行上限 + 滚动时间窗口预算;在上限时,VM**自动暂停**(磁盘保留 - 永远不会删除)。 |
为Broad / Stanford Terra生态系统而构建。包装 **Rawls** (工作区,数据表,提交,方法配置),**Leonardo** (运行时VM + 无缝开机运行),**Sam** (身份),**Agora** (WDL方法),**Cromwell** 通过Rawls(工作流提交,元数据,成本)和 **gsutil** 进行存储桶I/O。
## 安装
两种安装方式。两者最终结果相同:MCP连接 + Terra技能加载。**两种方式的前提条件**(每台机器一次):
```bash
gcloud auth application-default login # 使用您注册的Terra邮箱进行身份验证
```
### 选项A - Claude Code插件(推荐用于团队)
将MCP服务器**和**Terra技能(`terra-bugfix-loop`,`terra-wdl-run`,`terra-setup-check`,`terra-share-pack`)捆绑在一个安装包中:
```text
# 在Claude Code中:
/plugin marketplace add talha1369/mcp-terra
/plugin install mcp-terra
```
然后运行一次性引导脚本(创建专用venv,运行者密钥和`~/.mcp-terra/config.env` - 它**不**会触及您的系统Python或shell rc),并重启Claude Code:
```bash
# 找到插件安装的引导脚本,然后用您的workspace运行它:
BOOTSTRAP="$(find ~/.claude/plugins -name terra-bootstrap.sh -path '*mcp-terra*' 2>/dev/null | head -1)"
bash "$BOOTSTRAP" your-namespace/your-workspace
```
插件的MCP服务器通过 `scripts/terra-mcp-launch.sh` 启动,该脚本加载配置并从venv启动服务器。重新运行引导脚本以更改工作区;它是幂等的。
### 选项B - 一次性安装程序(单用户/无市场)
```bash
git clone https://github.com/talha1369/mcp-terra ~/projects/mcp-terra
cd ~/projects/mcp-terra
./install.sh your-namespace/your-workspace # 依赖项,密钥,`claude mcp add`
claude
```
`install.sh` 是幂等的 - 随时重新运行以刷新MCP注册。它验证gcloud身份验证,运行者密钥强度,您的Terra工作区访问(通过Rawls),然后通过 `claude mcp add` 注册MCP。无需手动编辑 `settings.json`,无需在shell rc中设置环境变量,无需猜测PATH。(技能在您从存储库目录打开Claude Code时加载,或全局安装选项A。)
### 首次提示(任一选项)
```text
运行 terra_health。
使用自动修复循环运行 <my-notebook>.ipynb,成功后自动停止,并将报告通过电子邮件发送给我。
```
## 架构(信任边界)
<div align="center">
<img src="docs/architecture.svg" alt="mcp-terra architecture: agent → safety-gated MCP → Terra REST + GCS bucket → on-VM runner+watchdog → Jupyter VM" width="100%">
</div>
**信任边界:**
- `Agent ⇄ MCP`:标准输入输出,进程内。代理输入被视为不可信。
- `MCP ⇄ GCS 存储桶`:使用用户的OAuth凭证的gsutil。写入时拒绝覆盖。
- `MCP ⇄ Runner`:HMAC-SHA256签名规范绑定到 `_spec_gcs` + `_submit_ts`,因此仅具有存储桶访问权限的共同成员无法伪造或重放作业。(注意:在**共享**工作区中,具有存储桶写入权限的共同成员可以交换初始VM启动脚本 - 由Leonardo使用运行者密钥运行 - 并捕获密钥;参见SECURITY.md §7了解此共同成员信任边界和单用户工作区/受限存储桶写入者缓解措施。)
- `MCP ⇄ Terra REST`:Sam身份验证;锁定后拒绝跨工作区操作。
- `Runner ⇄ Notebook`:执行前进行SHA-256完整性检查。存储桶侧篡改在提交和领取之间被检测到。
## 为什么
如果您曾经希望Claude能够:
- “在我的Terra工作区中启动一个T4 GPU虚拟机并启动训练笔记本”
- “跟踪已经运行了4小时的训练作业的日志”
- “将这些脚本上传到工作区存储桶”
- “确保在您离开办公室之前,所有Terra虚拟机都已暂停”
…这就是桥梁。代理调用MCP工具;MCP工具在用户的gcloud OAuth凭证下调用Terra REST API。
## 安全模型
MCP旨在抵御连接代理的提示注入滥用。多个层级,所有层级都在代码中强制执行(不仅是文档字符串):
**硬保护**(无法被代理绕过):
1. **没有销毁原始文件**。没有任何`terra_delete_*`,没有rm包装器,没有覆盖路径。要删除任何内容(虚拟机,存储桶对象,本地文件),请直接使用Terra UI / `gsutil rm`。
2. **没有覆盖**。如果目标对象已经存在,则存储桶上传会被拒绝。本地下载如果目标已经存在,则会被拒绝。
3. **本地路径阻止列表**。工具拒绝本地路径下的路径:`~/.ssh`,`~/.aws`,`~/.gnupg`,`~/.kube`,`~/.azure`,`~/.netrc`,`~/.config/gcloud/application_default_credentials.json`,`~/.git-credentials`,以及系统路径`/etc/`,`/private/etc/`,`/System/`,`/usr/bin/`,`/Library/Keychains/`等。在检查前会解析符号链接,因此符号链接到被阻止路径的链接也会失败。
4. **工作区存储桶允许列表**。存储桶读取/写入被限制为`gs://`URI,其存储桶用户具有Terra访问权限(每5分钟从Rawls刷新)。对任意公共/私有存储桶的读取/写入被拒绝。
5. **审计跟踪**。每个工具调用都会在stderr上写入一行,包含工具名称,操作类(READ / WRITE-SAFE / SPEND)和参数摘要。易于grep /审查。
**支出率控制**(基于文档字符串,代理确认用户):
| 工具 | 成本? | 备注 |
|---|---|---|
| `terra_whoami` | 否 | 身份检查 |
| `terra_list_workspaces` | 否 | |
| `terra_get_workspace` | 否 | |
| `terra_list_runtimes` | 否 | |
| `terra_get_runtime` | 否 | |
| `terra_stop_runtime` | 否 | 暂停虚拟机,节省资金 |
| `terra_start_runtime` | **是** | 恢复虚拟机,代理确认 |
| `terra_create_runtime` | **是** | 创建虚拟机,代理确认 |
| `terra_list_bucket` | 否 | 限制在工作区存储桶内 |
| `terra_upload_to_bucket` | 否 | 限制;不覆盖;不允许凭证路径 |
| `terra_download_from_bucket` | 否 | 限制;不覆盖;不允许凭证路径 |
**不存在删除工具**。这是故意的。
## 设置(一次性,每用户)
### 1. 先决条件
```bash
# Google Cloud SDK(gcloud + gsutil)
# https://cloud.google.com/sdk/docs/install
# Python 3.10+
python --version
# 以注册的Terra用户身份进行身份验证
gcloud auth application-default login
```
确保您登录的邮箱是注册的Terra用户(在 https://terra.bio 上查看)。
### 2. 安装MCP
```bash
git clone <此存储库网址> # 或复制 ~/projects/mcp-terra/
cd mcp-terra
pip install -e .
```
### 3. 连接到Claude Code
使用 `claude mcp add` 命令 - **不是** 在 `settings.json` 中使用 `mcpServers` 块。目前Claude Code会静默忽略 `settings.json` 中的 `mcpServers`(无错误),因此手动编辑它会使MCP无法正常工作。使用 `./install.sh` 可以为您完成此操作;要手动注册:
```bash
claude mcp add terra \
--env MCP_TERRA_ALLOW_WRITES=1 \
--env MCP_TERRA_WORKSPACE=namespace/your-workspace \
-- /绝对路径/到/python -m mcp_terra.server
```
使用具有已安装 `mcp-terra` 的Python解释器的绝对路径(例如 `./install.sh` 创建的虚拟环境)。然后在Claude Code中运行 `/mcp` 并确认 `terra` 已连接。随时重新运行 `claude mcp add`(或 `./install.sh`)以更改环境变量 - 它们在MCP启动时快照。
### 4. (Claude Desktop 替代方案)
Claude **桌面版** 使用JSON配置 - 在 `~/Library/Application Support/Claude/claude_desktop_config.json` 中添加一个 `mcpServers` 块:
```json
{
"mcpServers": {
"terra": {
"command": "/绝对路径/到/python",
"args": ["-m", "mcp_terra.server"],
"env": { "MCP_TERRA_ALLOW_WRITES": "1",
"MCP_TERRA_WORKSPACE": "namespace/your-workspace" }
}
}
}
```
(此 `mcpServers` 格式仅适用于Claude Desktop;Claude Code使用 `claude mcp add` 如§3。)
### 5. 验证
在Claude Code中,询问:*“运行 terra_whoami”*。代理应该以您的Sam注册电子邮件 + 用户主题ID回应。
### 6. 配置矩阵
所有MCP行为均由环境变量控制。它们在启动时快照 - 更改它们在会话中间不会产生影响(防御环境变量劫持)。要更改值,请重启MCP进程。
| 变量 | 默认值 | 所需 | 含义 |
|---|---|---|---|
| `MCP_TERRA_ALLOW_WRITES` | `0` | 任何写入/支出工具 | `1`启用WRITE-SAFE + SPEND工具。`0` = 只读模式。 |
| `MCP_TERRA_WORKSPACE` | *(未设置)* | 工作区锁定 | `namespace/name` - 将MCP限制在一个工作区。强烈建议。 |
| `MCP_TERRA_CONTROLLED_ACCESS` | `0` | NIH受控数据 | `1` = 拒绝原始数据流出到LLM(`terra_read_bucket_object`,`terra_get_entities`)适用于非公共存储桶 - GDS/DUC。默认关闭(实验室/公共分析不受阻碍)。参见 [docs/compliance.md](docs/compliance.md) |
| `MCP_TERRA_DATA_EGRESS_ALLOW` | *(未设置)* | 受控访问例外 | 用逗号/空格分隔的存储桶名称,您认证为非受控(实验室开放)可以读取,即使守卫开启。公共引用存储桶自动允许。 |
| `MCP_TERRA_RUNNER_SECRET` | *(未设置)* | 笔记本执行 | HMAC签名作业规范。必须是生成的、高熵令牌(≥ 32字符,≥ 12个唯一字符);如果包含空格、长串小写字母或解码为已知/弱口令,则拒绝。 通过 `python -c 'import secrets; print(secrets.token_urlsafe(32))'` 生成。 |
| `MCP_TERRA_MAX_CALLS_PER_MIN` | `60` | 速率限制 | 绑定每分钟调用速率,防止失控循环。 |
| `MCP_TERRA_KILL_REFUSAL_THRESHOLD` | `10` | 自动终止开关 | 在窗口中拒绝N次后自动触发终止开关。 |
| `MCP_TERRA_KILL_REFUSAL_WINDOW_SEC` | `60` | 自动终止开关 | 拒绝次数的窗口,用于自动触发。 |
| `MCP_TERRA_SPEC_MAX_AGE_SEC` | `300` | 运行者重放防御 | 拒绝旧规格(VM上的时钟偏移容差)。 |
| `MCP_TERRA_FAIL_STREAK_LIMIT` | `5` | 运行者失控成本 | 在N个连续作业失败后停止虚拟机。1..50。 |
| `MCP_TERRA_SMTP_HOST` | *(未设置)* | 发送电子邮件 | SMTP主机。未设置 ⇒ 回退到 `~/.mcp-terra/reports/` 下的 `.eml` 文件。 |
| `MCP_TERRA_SMTP_PORT` | `587` | 发送电子邮件 | `587`(STARTTLS)或 `465`(隐式TLS)。 |
| `MCP_TERRA_SMTP_USER` | *(未设置)* | 发送电子邮件 | 认证用户名。 |
| `MCP_TERRA_SMTP_PASS` | *(未设置)* | 发送电子邮件 | 应用程序密码(不是主账户密码)。 |
| `MCP_TERRA_SMTP_STARTTLS` | `1` | 发送电子邮件 | `0` 禁用STARTTLS(仅适用于端口465隐式TLS)。 |
| `MCP_TERRA_SMTP_FROM` | = `SMTP_USER` | 发送电子邮件 | `From:` 头部值。 |
| `MCP_TERRA_SMTP_RELAY` | *(未设置)* | 发送电子邮件 | `1` = 无认证中继(模式C):发送时无需USER/PASS(中继服务器通过IP授权)。明确选择加入,仅限。 |
| `MCP_TERRA_EMAIL_RECIPIENT_OVERRIDE` | *(未设置)* | 测试专用 | 必须与身份验证的Terra邮箱一致;仅当`gcloud account`与预期邮箱不同时有用。否则拒绝。 |
| `MCP_TERRA_TTS_VOICE` | `en-US-Studio-O` | 音频摘要 | 云TTS语音ID。覆盖例如`en-US-Studio-Q`(男),`en-US-Neural2-J`。音频摘要工具还需要`gcloud services enable texttospeech.googleapis.com`。 |
| `MCP_TERRA_TTS_QUOTA_PROJECT` | = 锁定项目 | 音频摘要 | 云TTS的配额/计费项目(作为`X-Goog-User-Project`发送)。需要在该项目上具有`serviceusage.services.use`权限。 |
| `MCP_TERRA_SLACK_WEBHOOK` | *(未设置)* | Slack ping(文本) | `https://hooks.slack.com/services/...`传入的webhook URL。锁定启动;`terra_notify_slack`工具无URL参数(防外泄)。未设置 ⇒ Slack ping被跳过。 |
| `MCP_TERRA_SLACK_BOT_TOKEN` | *(未设置)* | Slack文件上传 | `xoxb-…`机器人令牌(Slack应用,具有`files:write`权限)。将音频文件附加到Slack需要此令牌(网络钩子无法上传文件)。 |
| `MCP_TERRA_SLACK_CHANNEL` | *(未设置)* | Slack文件上传 | 一个或多个目标(逗号/空格分隔),机器人上传到 - **直接消息** 和/或 **频道**。一个 **频道ID** (`C…`/`G…`; 机器人必须是成员 - `/invite @bot`),和/或一个 **用户ID** (`U…`) 以直接消息通知用户(不需要邀请; 需要`im:write`机器人范围)。例如`U0123ABCD, C0456WXYZ`。每个目标的结果独立报告。 |
## 通知:电子邮件 + Slack(设置)
运行结束报告(和NotebookLM样式的音频解释)可以通过 **电子邮件** 和/或 **Slack** 交付。两者都是 **可选的** 和 **默认关闭** - 没有设置,报告会作为 `~/.mcp-terra/reports/<id>.eml` 文件写入本地。
**在哪里放置这些变量。** 它们在 **MCP启动时读取一次** (快照 - 在会话中间更改它们不会产生影响;重启MCP后编辑):
- **插件安装(选项A):** 将它们添加到 `~/.mcp-terra/config.env` (一行一个`KEY=value`),然后重启Claude Code。
- **一次性安装程序(选项B):** 在您的`claude mcp add`(或 `./install.sh`)注册中添加 `--env KEY=value`,或在您启动 `claude` 的shell中 `export` 它们。
> **内置安全保护:** 电子邮件收件人被 **硬锁定到您自己的Terra账户邮箱** (没有`to:`参数 - 防数据通过电子邮件外泄),并且Slack webhook/令牌在启动时被 **锁定** (`terra_notify_slack`工具没有URL/令牌参数)。机密仅存储在您的环境/ `config.env`(模式`0600`)中,而不存储在聊天记录或审计日志中。
### 电子邮件 - 选择一种
**A · 零设置(本地 `.eml` 文件)。** 留下所有 `MCP_TERRA_SMTP_*` 未设置。MCP会将报告写入 `~/.mcp-terra/reports/<id>.eml` (模式 `0600`)并返回路径;您自己打开或转发。
**B · 身份验证发送(例如Gmail应用密码)。** 使用范围限定的 **应用密码**,而非您的真实账户密码:
1. 在Google账户上启用两步验证,然后在 <https://myaccount.google.com/apppasswords> 创建 **应用密码** (选择“邮件”)。
2. 设置:
```bash
MCP_TERRA_SMTP_HOST=smtp.gmail.com
MCP_TERRA_SMTP_PORT=587 # 587 = STARTTLS(默认);465 = 隐式TLS
MCP_TERRA_SMTP_USER=you@broadinstitute.org
MCP_TERRA_SMTP_PASS='<16位应用密码>' # 不是登录密码
```
3. 重启MCP,然后询问Claude发送报告 - 它将发送到您的Terra邮箱。
**C · 中继,无密码(通过IP授权的组织SMTP中继)。** 对于Broad/Workspace中继:
```bash
MCP_TERRA_SMTP_HOST=smtp-relay.gmail.com
MCP_TERRA_SMTP_RELAY=1 # 显式选择加入;发送时不使用USER/PASS
```
被遗忘的密码绝不会默默地中继 - 中继需要显式`=1`;否则MCP会回退到`.eml`文件(模式A)。
### Slack — 两个层级
> Slack 没有 "应用密码" — 相当的凭证是一个 **机器人 OAuth令牌**
> (`xoxb-…`) 您在 Slack 应用中创建(以下步骤)。仅用于文本 ping 您需要一个 webhook URL(无需令牌)。
**1 · 文本 ping(传入 webhook)。** 最简单;发布运行摘要为文本。
1. 在 Slack 中:**创建应用 → 从头开始** (<https://api.slack.com/apps>),选择您的
工作区 → **传入 Webhooks → 激活** → **将新 Webhook 添加到工作区**,
选择频道,并复制 `https://hooks.slack.com/services/…` URL。
2. 设置 `MCP_TERRA_SLACK_WEBHOOK='https://hooks.slack.com/services/…'` 并重启。
**2 · 文件上传(附加音频解释)。** Webhooks 无法上传文件,因此音频附件需要一个 **机器人令牌**:
1. 在您的 Slack 应用中 → **OAuth 和权限** → 添加 Bot 令牌范围
`files:write` (和 `im:write` 如果您希望它给您发送直接消息)→ **安装到工作区**
→ 复制 **机器人用户 OAuth 令牌** (`xoxb-…`)。
2. 查找您的目标 ID(s):一个 **频道 ID** (`C…`/`G…` — 打开频道 → *查看频道详情* → 底部的 ID;然后在该频道中执行 `/invite @your-bot`),
和/或您的 **用户 ID** (`U…` — 您的个人资料 → *复制成员 ID*) 以接收直接消息。
3. 设置:
```bash
MCP_TERRA_SLACK_BOT_TOKEN='xoxb-…'
MCP_TERRA_SLACK_CHANNEL='U0123ABCD, C0456WXYZ' # 直接消息和/或频道;逗号/空格分隔
```
重启 MCP。每个目标的成功/失败都会独立报告。
### 尝试
```text
通过 bug-fix 循环运行我的笔记本并向我发送报告。 # 邮箱
运行它并将结果 + 音频发布到 Slack。 # Slack
```
## 工具参考
所有工具都接受简单的参数(字符串,整数,布尔值)并返回 JSON
(或纯文本)。有关完整参数的说明,请参见 `src/mcp_terra/server.py` 中的文档字符串。
这列出了最常用的工具;`terra_health` 报告准确的
`tools_count` (目前 **45**),而 `src/mcp_terra/server.py` 是
每个工具及其参数的权威参考。
### 只读 / 检测
```
terra_whoami() → {userEmail, userSubjectId, gcloud_account}
terra_list_workspaces() → [{namespace, name, bucketName, googleProject, accessLevel}, …]
terra_get_workspace(namespace, name) → {workspace: {bucketName, googleProject, …}}
terra_list_runtimes(google_project=None) → [{runtimeName, status, machineType, …}]
terra_get_runtime(google_project, runtime_name) → {status, runtimeConfig, …}
terra_list_bucket(bucket_uri, recursive=False) → ["gs://…", …]
terra_recommend_runtime_for_notebook(notebook_gcs) → {machine_type, gpu, rationale}
terra_health() → {writes_allowed, workspace_lock, runner_secret, audit_chain, tools_count, …}
terra_killswitch_status() → {tripped, reason, …}
```
### 工作区数据和工作流检测(只读)
这些工具为 MCP 提供了全面的读取覆盖 —
所有 READ 类,无花费,无销毁。
```
terra_list_data_tables(namespace, name) → {entityType: {count, attributeNames, …}}
terra_get_entities(namespace, name, entity_type, page=1, page_size=50) → {results, resultMetadata}
terra_list_submissions(namespace, name) → [{submissionId, status, …}]
terra_get_submission(namespace, name, submission_id) → {status, workflows, …}
terra_get_workflow_metadata(namespace, name, submission_id, workflow_id, include_calls=False)
terra_get_workflow_outputs(namespace, name, submission_id, workflow_id)
terra_get_workflow_logs(namespace, name, submission_id, workflow_id, max_bytes=65536, failed_only=True) # 每个任务的 Cromwell 标准错误 — 真实的故障信号
terra_get_workflow_cost(namespace, name, submission_id, workflow_id)
terra_list_method_configs(namespace, name) → [{namespace, name, methodRepoMethod, …}]
terra_get_method_config(namespace, name, config_namespace, config_name)
terra_read_bucket_object(bucket_uri, max_bytes=102400) → {text, bytes_returned, truncated}
terra_get_bucket_object_metadata(bucket_uri) → {stat}
terra_get_batch_job_status(google_project, region, job_name) → {status, logging_command}
```
### 工作流(WDL / Cromwell)— 编写 + 提交
```
terra_register_method(method_namespace, method_name, wdl, synopsis="") # Agora,追加,秘密扫描
terra_create_method_config(...) # 不覆盖
terra_submit_workflow(namespace, name, config_namespace, config_name, …) # 花费;设计上无中止/删除
```
### 生命周期(产生费用 → 代理人确认用户)
```
terra_start_runtime(google_project, runtime_name)
terra_stop_runtime(google_project, runtime_name)
terra_create_runtime(google_project, runtime_name,
machine_type="n1-standard-4",
disk_size_gb=100,
gpu_type="", # 或 "nvidia-tesla-t4" / "nvidia-tesla-v100"
num_gpus=0,
auto_pause_threshold_minutes=60,
tool_docker_image="",
bucket_uri="", # 默认值为锁定工作区的存储桶
auto_start_runner=True) # 无缝开机运行(见下文)
```
> **原子 + 无缝** (默认 `auto_start_runner=True`):VM 恢复
> 带有 **活动运行器**,否则此调用会大声失败。它连接一个 Leonardo
> `startUserScriptUri`,在每次启动时自动启动 VM 上的运行器
> (创建 *和* 恢复)— 无 SSH,无 IAM,无手动步骤 — 并阻塞直到
> 运行器发布新鲜的、匹配身份的心跳(否则返回 VM 运行器日志尾部)。
> 传递 `auto_start_runner=False` 以获取传统
> 先发后管模式。
> **没有 `terra_delete_runtime`。** MCP 不能
> 销毁 VM 或磁盘 — 使用 Terra UI 删除运行时(选择 **"保留
> 持久磁盘"**)。这是无销毁原则:删除和
> 覆盖在物理上是不可能的,通过此 MCP。更改
> 尺寸错误,`terra-bugfix-loop` 技能检测不匹配并
> *引导* 您通过 UI 更改,然后在正确规格下重新创建。
### 存储桶 I/O
```
terra_upload_to_bucket(local_path, bucket_uri, recursive=False)
terra_download_from_bucket(bucket_uri, local_path, recursive=False)
```
### 完成记录 + 投递渠道
在运行完成时,循环写入一个具有溯源的记录(参见
[docs/metadata.md](docs/metadata.md)),邮件,Slack 和音频渠道
都从中渲染 — 因此每个渠道都同意,验证门涵盖所有
三个渠道。
```
terra_write_run_record(run_id, record_json, bucket_uri="") # WRITE; 不覆盖;MCP 标记溯源
terra_send_run_report_email(subject, body, job_id, verification_acknowledgment) # 锁定收件人
terra_notify_slack(text, run_id="") # WRITE; 环境锁定(无 URL 参数)
terra_render_audio_summary(...) # "结果含义" 通过 Cloud TTS
```
## Bug-fix 循环(笔记本执行 + `.BAK` 版本控制)
端到端流程 — *提供正确的 VM → 运行 → 自动修复 → 邮件
报告(+ 可选的 NotebookLM 式音频解释)* — 被打包为
**`terra-bugfix-loop` Claude 技能** (规范的"运行我的 Terra 笔记本"
入口)。它弥补了 Claude Code 只能 *复制*
笔记本到存储桶;MCP 让它实际配置,运行和报告。
在底层,技能组成这些有凭证的基元:
| 工具 | 目的 | 费用 |
|---|---|---|
| `terra_submit_notebook_job(notebook_gcs, bucket_uri, …, auto_stop_after_completion)` | 提交笔记本进行异步执行(HMAC 签名规范)。返回 `job_id`。 | 无(VM 是成本所在) |
| `terra_get_notebook_job_result(bucket_uri, job_id, wait_for_complete, timeout_s)` | 轮询作业。返回状态 + 确定性分类 + 失败单元格的源/跟踪信息(如果失败)。终端状态从签名的 `result.json` 读取。 | 无 |
| `terra_get_run_log` · `terra_render_audio_summary` · `terra_send_run_report_email` | 基础日志 · 验证者门控音频解释 · 收件人锁定报告邮件。 | 无 / TTS / SMTP |
| `terra_install_notebook_runner(bucket_uri)` | *仅限旧版* — 运行器现在在 VM 启动时自动安装(见上文)。 | 无 |
> **MCP 与技能(基本原理):** MCP 是有凭证的,
> 安全门控 *原始* 层 — 它持有 Terra/GCS/HMAC 凭证并
> 强制执行硬不变式(无删除,无覆盖,签名作业,
> 写入门控,审计,终止开关,锁定邮件收件人),**无判断
> 和无销毁**。Claude *技能* 持有判断 + 协调并且
> 不能超过 MCP 的能力范围 — 因此即使是 buggy 或
> 提示注入的技能仍然不能销毁您的数据。
### 运行器启动 — 无缝(无手动步骤)
MCP 不能直接驱动 Jupyter 单元格;它通过
GCS 使用作业规范合约,由 VM 上的运行器执行。
**您不再手动启动运行器** —
`terra_create_runtime` (`auto_start_runner=True`,默认)连接一个 Leonardo
`startUserScriptUri`,在 **每次** VM 启动时启动它
(创建 *和* 从自动暂停恢复)。运行器秘密 + 存储桶通过 Leonardo
`customEnvironmentVariables` 交付(静止加密;从不存储在 GCS,argv 或审计中)。
对于 **旧版运行时**,在创建之前
此功能,通过 `terra_start_runner_on_vm(...)` (gcloud-ssh;需要 SSH/IAM 角色)或
手动 Jupyter 终端 fallback 在 SOP §3b 中启动运行器。
运行器每 15 秒轮询存储桶并使用 `papermill` 执行笔记本。
它 **从不删除任何内容** — 使用的规范被重命名为 `*.consumed` 通过
`gsutil mv`。
> **工作区存储桶锁定:** 保持对 `gs://<bucket>/mcp_terra_jobs/`
> (启动脚本 + 心跳)**仅所有者** 的写入访问权限。具有写入访问权限的共同成员
> 可以篡改 VM 执行的启动脚本 — 一个存储桶信任
> 假设 HMAC 签名作业合约本身并未涵盖。
### 循环,端到端
1. Claude 通过 `terra_upload_to_bucket` 将笔记本 + 脚本上传到 GCS。
2. Claude 调用 `terra_submit_notebook_job(notebook_gcs, bucket_uri)`。
3. Claude 轮询 `terra_get_notebook_job_result(bucket_uri, job_id)`。
4. **在 `FAILED` 时**:结果包括 `failed_cell_index`,`failed_cell_source`,
`failed_cell_traceback`。Claude 读取跟踪信息,在本地编辑源
(使用其 `Read`/`Edit` 工具),通过
`terra_upload_to_bucket(..., version_existing=True, version_method='bak')`
重新上传。以前的错误版本变为 `<name>.BAK.<ISO-ts>.<ext>`;新
版本采用原始名称。循环回到(2)。
5. **在 `succeeded` 时**:下载 `executed_notebook_gcs` 以获取输出。
### 预授权循环(以便代理不会因权限而暂停)
默认情况下,Claude Code 在每次工具调用前都会提示您。对于无人值守
的 bug-fix 循环,在
`~/.claude/settings.json` (或在
Claude Code 中使用 `/permissions` 斜杠命令)中将以下工具添加到您的
**允许的工具** 列表中,以便循环无需提示运行:
```jsonc
{
"permissions": {
"allow": [
"mcp-terra:terra_submit_notebook_job",
"mcp-terra:terra_get_notebook_job_result",
"mcp-terra:terra_upload_to_bucket",
"mcp-terra:terra_get_run_log"
]
}
}
```
在预授权循环期间保持硬性防护:
- 仍必须在环境中设置 `MCP_TERRA_ALLOW_WRITES=1`。
- 工作区锁定(`MCP_TERRA_WORKSPACE`)仍然被强制执行。
- HMAC 规范签名仍然适用 — `MCP_TERRA_RUNNER_SECRET` 必须
是一个高熵值(≥ 32 个字符,≥ 12 个唯一字符)。
- 终止开关文件 `~/.mcp-terra/KILL` 仍然会立即中止所有操作
如果您创建了它。
- 速率限制(默认 60 次调用/分钟)仍然限制失控循环。
### 端到运行报告邮件(具有代理验证的非幻觉)
在 `succeeded` 后,代理组成一份最终运行报告,描述它遇到的每个
错误以及它应用的特定修复。要将其作为电子邮件发送:
三种投递模式 — 选择您的组织允许的模式:
- **A — 无凭证(`.eml` 文件)。** 留下 SMTP 未设置。MCP 将报告
编写为 `~/.mcp-terra/reports/` 下的 `.eml` (模式 `0o600`)并返回
路径;您打开/转发它。零凭证。
- **B — 身份验证发送(应用密码)。** 设置 HOST + USER + PASS。PASS
是一个 **应用密码** (一个范围限定、可撤销的 SMTP 令牌 — *不是*
您的帐户密码);它存在于您的环境/配置中,从不在聊天中。
```bash
export MCP_TERRA_SMTP_HOST=smtp.gmail.com
export MCP_TERRA_SMTP_PORT=587
export MCP_TERRA_SMTP_USER=you@broadinstitute.org
export MCP_TERRA_SMTP_PASS='<app-password>' # 不是您的主密码
```
- **C — 中继,无密码。** 对于一个通过 IP 授权的 Workspace/Broad SMTP 中继。
设置 HOST 和 **显式** 的选择性 `MCP_TERRA_SMTP_RELAY=1` (不需要
USER/PASS);MCP 在不进行身份验证的情况下发送。
```bash
export MCP_TERRA_SMTP_HOST=smtp-relay.gmail.com
export MCP_TERRA_SMTP_RELAY=1
```
仅仅 *忘记* 的密码从不悄悄中继 — 中继需要显式
`=1` 选择加入;否则它会回退到 `.eml` 文件(模式 A)。
2. 收件人 **硬锁定** 到您的 Terra 帐户电子邮件 — 没有
电子邮件工具上的 `to` 参数。MCP 拒绝发送给其他人,
击败通过电子邮件的数据外流。
3. 在发送之前,主要代理人会询问第二个 Claude 代理人(通过
`Task` 工具)验证报告与 `terra_get_run_log()` 输出的匹配情况。
验证者返回一个自由格式的确认,描述它交叉检查的确切证据。
邮件工具除非确认 ≥ 50 个字符并描述具体证据(例如
"验证运行。stderr 第 47 行跟踪信息")。MCP **不强制**
确认的语义正确性 — 那是验证代理人的工作。MCP 的角色是
拒绝空白批准并锁定收件人。
### `.BAK` 与时间戳版本控制
`terra_upload_to_bucket(..., version_existing=True, version_method='bak')`:
- `gs://b/script.py` 存在,用户上传修复
- 旧版 → `gs://b/script.BAK.20260625T013000Z.py` (保留,不删除)
- 新版 → `gs://b/script.py` (修复后的版本采用原始名称)
`version_method='timestamp'` (默认)将时间戳放在 `.BAK.` 之后:
- 旧版 → `gs://b/script.20260625T013000Z.py`
使用 `bak` 进行代码替代(清除"这是旧版/错误版本"的信号);
使用 `timestamp` 进行数据版本控制。
## 运行多个/并行作业
MCP 支持一次运行多个作业 — 在提交层没有序列化。
三种并行层级:
1. **WDL / Cromwell 散点(并行计算路径)。** `terra_submit_workflow`
启动 Cromwell 提交;`scatter` 块将每个分片作为单独的 Google Batch 任务分发,
因此 **一个** 工作流运行数百个任务并行(受限于您的 Batch 配额)。
调用缓存意味着重新提交只会重新运行 **失败** 的分片。
这是适用于大型并行工作负载的正确工具 — 请参见 `terra-wdl-run` 技能。
2. **多个并发提交。** 每个 `terra_submit_workflow` /
`terra_submit_notebook_job` 是独立且非阻塞的 — 提交多个
它们全部同时运行(每个花费单独确认)。
3. **多个运行器的笔记本。** 提交多个笔记本作业并运行
几个运行器(VM);VM 上的运行器采用 **原子每规格声明**
(无锁标记,键控到运行器),因此每个 VM 选取 **不同** 的作业 —
笔记本在多个 VM 上并行运行,同一个作业绝不会被执行两次。
(单个 VM 依次运行自己的作业,按设计,
以避免 OOM — 对于一次运行的多个笔记本,请使用更多运行器或 WDL 路径。)
## 多个计算环境
MCP 不局限于一个运行环境。`terra_create_runtime` 采用
`runtime_name`,因此调用它时使用不同的名称会创建 **多个计算环境**
在锁定工作区的项目中(锁定到项目/工作区,而不仅仅是一个运行环境);
`terra_list_runtimes`,`terra_get_runtime`,`terra_start_runtime` 和 `terra_stop_runtime`
都按名称操作。一次运行多个:每个运行环境的 VM 上的运行器
采用 **原子每规格声明**,因此 VM 选择 **不同的** 笔记本作业
并且绝不运行相同的作业。混合尺寸 — 例如用于训练的 GPU 环境和用于预处理的
高内存环境 — 并将作业提交到共享存储桶;
正在运行的运行环境将它们分开。(对于一个 VM 来运行多个作业,请参见
单 VM 并发;对于大扇出,请首选 WDL/Cromwell 散点路径。)
## 用例
按人们实际要求分组:
| 类别 | 示例请求 |
|---|---|
| **运行和调试** | "运行此笔记本并修复所有错误", "仅重新运行失败的 WDL 分片", "为什么我的提交失败了?" |
| **检查** | "哪些 VM 正在运行?", "查看训练日志", "列出我的最后一个工作流的输出", "该运行花费了多少?" |
| **作者和提交** | 注册 WDL 方法,创建方法配置,提交带有 `scatter` 的 Cromwell 工作流 |
| **成本卫生** | "在我离开之前暂停所有正在运行的 VM", "运行此操作但将花费上限设为 20 美元" |
| **报告** | 通过电子邮件或 Slack 提供的已验证的端到端运行报告 + NotebookLM 式音频解释 |
## 示例会话
现实的,端到端。代理人调用 MCP 工具(显示在 `[方括号]` 中);
硬安全防护适用于每一个。
### 1 · 运行笔记本,自动修复错误,发送验证报告
*在真实的Terra环境中进行端到端验证(部署 → 失败 → 自动修复 → 成功 → 音频邮件)。*
```text
你: 在Terra上运行我的analysis.ipynb,修复任何错误,并将结果通过电子邮件发送给我。
克劳德:[terra_recommend_runtime_for_notebook] → "需要一个T4 GPU虚拟机(约0.50美元/小时)。确定吗?"
你: 是的
克劳德:[terra_create_runtime] → 约5分钟 → 状态="就绪"
(虚拟机正在运行,虚拟机上的运行程序处于活动状态 — 无需SSH,无需IAM,无需手动步骤)
[terra_submit_notebook_job auto_stop=True] → job_id
[terra_get_notebook_job_result wait_for_complete=True]
→ 失败 · 故障诊断 = 缺少模块 'pyarrow'
→ 添加 '!pip install pyarrow',重新上传(旧文件保留为.BAK),重新提交
→ 成功(返回代码=0)
[terra_get_run_log] → 验证子代理交叉检查报告与日志
[terra_render_audio_summary] + [terra_send_run_report_email]
→ "已将验证报告和NotebookLM风格的音频解释器通过电子邮件发送给您。"
```
### 2 · 排查失败的Cromwell提交
```text
你: 我的提交1a2b…失败了 — 发生了什么?
克劳德:[terra_get_submission] → 40个工作流中有3个失败
[terra_get_workflow_logs failed_only=True] → 读取每个任务的Cromwell标准错误输出
[terra_classify_workflow_failure] → 确定的分类:
```
| shard | task | category | cause |
|---|---|---|---|
| 7 | HaplotypeCaller | `oom` | 在约13 GB时被终止(请求 ≥ 16 GB) |
| 22 | HaplotypeCaller | `oom` | 相同 |
| 31 | MergeVcfs | `missing_file` | 上游shard 7未产生输出 |
```text
克劳德: "两个OOM和一个下游丢失。增加HaplotypeCaller内存到16 GB并重新提交 — 调用缓存仅重新运行3个失败的shard,而不是全部40个。"
```
### 3 · 预先限制支出
```text
你: 在GPU盒上训练,但不要让它花费超过20美元。
克劳德:[terra_create_runtime gpu_type=nvidia-tesla-t4 max_cost_usd=20 vm_hourly_usd=0.55]
→ 一个独立的虚拟机监视器在估计支出达到20美元之前自动暂停虚拟机(保留磁盘 — 永远不会删除),在80%时发出警告。
[terra_submit_notebook_job …] → 在限制下运行。
```
### 4 · 每天结束时的成本检查
```text
你: 暂停我工作空间中正在运行的所有虚拟机。
克劳德:[terra_list_runtimes] → 3个正在运行
[terra_stop_runtime] ×3
→ "已暂停所有3个(保留持久性磁盘)。MCP没有删除操作。"
```
## 认证说明
- Token来自 **gcloud Application Default Credentials**。没有单独的Terra OAuth流程 — Terra信任相同的Google身份。
- MCP永远不会缓存token。每个工具调用都会调用
`gcloud auth application-default print-access-token`以获取新的token
(gcloud内部处理刷新)。
- 协作者每个人都需要自己的gcloud身份验证和Terra注册。
## 限制 / 暂不支持
- **无交互式Jupyter内核控制** — MCP通过虚拟机运行程序(`papermill`)以无头模式执行笔记本 —
这是错误修复循环运行它们的方式 — 但它不驱动活动交互式内核中的单元格。
- **无运行时或磁盘删除** — 设计如此(无销毁)。在Terra UI中删除运行时并选择"保留持久性磁盘"。
- **NotebookLM风格的音频** 需要在您拥有 `serviceusage`权限的项目上启用Cloud TTS(设置 `MCP_TERRA_TTS_QUOTA_PROJECT`);
没有它,报告仍然会通过电子邮件发送 — 只是没有音频。
- 无多区域感知 — 假设Terra的默认值(us-central1 / us-east4)。
## 贡献
欢迎PR。通过以下方式添加工具:
1. 在 `terra_client.py`(或 `bucket.py`用于gsutil操作)中实现API调用
2. 在 `server.py`中添加带有清晰文档字符串的 `@server.tool()`包装器
3. 在 `tests/`中添加烟雾测试
保持安全模型完整 — 任何支出率或破坏性工具都必须在其文档字符串中告知代理首先确认用户。
## License
MIT.
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器