Skip to main content
Glama
ChengZiiii

opera-houdini-mcp

by ChengZiiii
README.md
# opera-houdini-mcp

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12-blue)](https://www.python.org)
[![MCP](https://img.shields.io/badge/MCP-1.12.2-green)](https://github.com/modelcontextprotocol/python-sdk)
[![Upstream](https://img.shields.io/badge/upstream-capoomgit%2Fhoudini--mcp-lightgrey)](https://github.com/capoomgit/houdini-mcp)

`opera-houdini-mcp` 是 [`capoomgit/houdini-mcp`](https://github.com/capoomgit/houdini-mcp) 的独立增强 fork,定位为**可作为 git submodule 嵌入到任意 Houdini 插件库**的 MCP server。MIT 协议,与上游完全兼容,额外提供 Tier 1 工具集、`execute_code` 安全护栏、零新增 pip 依赖。

> **当前上游基线**:`capoomgit/houdini-mcp` @ `de4fd93`(2026-07-17 同步)
> **同步策略**:cherry-pick only,禁止 merge
> **集成方式**:作为 git submodule 嵌入到消费方 Houdini 项目(见 [§3](#embedding-as-a-git-submodule))

---

## 目录

1. [Features](#features)
2. [Architecture](#architecture)
3. [Embedding as a git submodule](#embedding-as-a-git-submodule)
4. [Tier 1 工具清单](#tier-1-工具清单)
5. [自进化知识库](#自进化知识库)
6. [RAG 文档检索与版本化索引](#rag-文档检索与版本化索引)
7. [Console 日志与命令审计](#console-日志与命令审计feat-mcp-console-log-audit)
8. [AI 调用引导](#ai-调用引导feat-mcp-tool-guidance)
9. [`execute_code` 安全模型](#execute_code-安全模型)
10. [AI 调用 hou API 的硬约束](#ai-调用-hou-api-的硬约束)
11. [Configuration](#configuration)
12. [Upstream Sync Policy](#upstream-sync-policy)
13. [Testing](#testing)
14. [Troubleshooting](#troubleshooting)
15. [Edge Cases & 集成陷阱](#edge-cases--集成陷阱)
16. [Contributing](#contributing)
17. [Security](#security)
18. [License & Acknowledgement](#license--acknowledgement)

---

## Features

- **176 个已注册 MCP 工具**(feat-mcp-console-log-audit 后口径:`houdini_mcp_server.py` 中**活动**的 `@mcp.tool` 装饰器数,即 AI 工具 `tools/list` 可见数(174 + `get_console_log` / `clear_console_log`);另有 9 个工具的装饰器按 slim-mcp-toolset 计划注释停用——6 个 OPUS 资产 + 3 个 base64 渲染——不计入)
- **13 个 Tier 1 工具** — 场景 CRUD / 节点发现 / 图编辑 / 错误扫描(含 warnings)/ 几何摘要 / 材质 / 截图 / 文档查询 / 缓存管理 / 诊断,独立模块化
- **`execute_code` 三档安全 policy** — `read-only` / `normal` / `privileged` × dangerous / heavy / mutation 三类黑名单(正则 + AST 别名双检)
- **双开关 bypass** — 任何 dangerous / heavy / privileged 操作都需「请求端参数 + 服务端 `HOUDINI_MCP_ALLOW_BYPASS=1`」同时开启
- **零新增 pip 依赖** — `get_houdini_help` 用 stdlib `html.parser` 替代 `beautifulsoup4`,维持 `mcp[cli]==1.12.2 + requests + python-dotenv` 三件套
- **结构化 audit** — 每次 `execute_code` 响应附 `_audit` 块(policy / dangerous_hits / heavy_hits / mutation_hits / bypass_used / elapsed_ms / undo_group)
- **local-help-first** — `get_houdini_help` / `verify_hou_api` 优先打 Houdini 本地 help server(`127.0.0.1:48626`),失败自动回退在线 SideFX
- **自进化知识库** — 6 个 bridge-local 知识工具(`search_lessons` / `save_lesson` / `read_lesson` / `knowledge_stats` / `capture_workflow_snapshot` / `save_recipe`)+ 自动错误捕获 hook(零上下文成本);多 root(个人库自动发现 + 团队库注册表声明,默认只读);**无嵌入模型**(BM25 + 指纹 + 统计,全 stdlib)

---

## Architecture

```mermaid
flowchart LR
    A[AI Agent<br/>Claude Desktop / Cursor / ...] -->|stdio / MCP JSON| B[houdini_mcp_server.py<br/>bridge]
    B -->|TCP 127.0.0.1:9876| C[server.py<br/>HoudiniMCPServer]
    C -->|hou API| D[Houdini main thread<br/>H21+ / Python 3.11+]
    D -.->|hou.helpServerUrl| E[Local help server<br/>127.0.0.1:48626]
    E -.->|F1 失败 fallback| F[SideFX online docs]
    C -->|RAG index build| G[scripts/build_rag_index.py]
```

**关键约束**:

- `server.py` **必须**运行在 Houdini 主进程内(`hou` 是 C 扩展,跨进程 import 会 hang)
- `bridge`(`houdini_mcp_server.py`)与 `server` 通过 **TCP `127.0.0.1:9876`** 通信,每次 tool call 短连接
- AI 工具看到的 MCP tool 列表来自 **bridge** 端(`mcp[cli]` SDK),实际 `hou` 调用发生在 Houdini 进程内

---

## Embedding as a git submodule

### 1. 添加 submodule

```bash
# 在你的 Houdini 插件库根目录
git submodule add https://github.com/ChengZiiii/opera-houdini-mcp.git external/houdinimcp
git submodule update --init --recursive
```

### 2. 隔离 Python 环境

opera-houdini-mcp 与你项目里的其他工具运行环境解耦。建议目录布局:

```
<your-project>/
├── external/
│   ├── houdinimcp/                  # 本仓库(submodule)
│   ├── houdinimcp-env/              # 本仓库专用的 venv(python/ + pylibs/)
│   ├── <other-tool>/                # 你的其他第三方工具
│   └── <other-tool>-env/            # 各工具独立环境,互不冲突
```

`<dirname>-env/` 不要提交进 git。**env 目录名 = package 目录名 + `-env`**,自动派生,详见 [Configuration](#configuration)。如果你把 submodule 改名为 `external/opera-houdini-mcp/`,env 自动变成 `external/opera-houdini-mcp-env/`,跟着 rename 一下就行。

环境初始化细节(`python/` 解释器 + `pylibs/` 依赖)由消费方项目侧决定,可参考 `pyproject.toml` 的 `dependencies` 三件套自行装配,或用 `uv pip install -p <venv-python> mcp[cli]==1.12.2 requests python-dotenv` 一行命令起步。

### 3. 启动 server

从你的项目代码里直接 import `houdinimcp` 包:

```python
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "external"))

from houdinimcp import start_server, stop_server, is_server_running

# 启动(默认 127.0.0.1:9876)
start_server()

if is_server_running():
    print("opera-houdini-mcp is up")
```

`houdinimcp` 包对外暴露的完整 API:

| 函数 | 用途 |
|------|------|
| `start_server(host='127.0.0.1', port=9876)` | 启动 TCP server(幂等,重复调用早退) |
| `stop_server()` | 停止 server |
| `restart_server(host, port)` | stop + start |
| `is_server_running() -> bool` | 查询状态 |
| `initialize_plugin()` | 一次性初始化 `hou.session` 标志位 |

### 4. 配置 AI 工具

任一兼容 MCP 的客户端(Claude Desktop / Cursor / ZCode / OpenCode / Codex):

```json
{
  "mcpServers": {
    "houdini": {
      "command": "uv",
      "args": [
        "run",
        "python",
        "<your-project>/external/houdinimcp/houdini_mcp_server.py"
      ]
    }
  }
}
```

> MCP JSON key 固定为 `houdini`(`mcpServers.houdini` / `mcp.houdini` / `mcp_servers.houdini` 三种形式都接受),与上游完全兼容,老用户配置零改动。

### 5. 升级 submodule 消费者

```bash
git submodule update --remote external/houdinimcp
git submodule sync
```

无需重装 env —— 运行环境独立放在 `external/<工具名>-env/` 下,与源码完全解耦。

---

## Tier 1 工具清单

> 以独立 PR 形式合入。完整计划与进度见 `CHANGELOG.md`。

| 类别 | 工具 | 说明 |
|------|------|------|
| 场景 | `get_scene_info` | 增强版场景元信息(houdini_version / node_count) |
| 场景 | `save_scene` / `load_scene` / `new_scene` | 场景 CRUD,自动失效缓存 |
| 节点发现 | `list_node_types` | 按 category 过滤 + name 模糊匹配 + 分页 |
| 节点发现 | `list_children` | 递归子树 + compact 模式 + 分页 |
| 节点发现 | `find_nodes` | glob + 类型过滤,Houdini 端单次扫描 |
| 图编辑 | `reorder_inputs` / `layout_children` / `set_node_position` / `set_node_color` / `create_network_box` | 节点位置/颜色/网络盒 |
| 节点信息 | `get_node_info` | 增强:errors / cook_state / compact / input details |
| 错误扫描 | `find_error_nodes` | 默认含 warnings,单次 `allSubChildren` 扫描 |
| 几何 | `get_geo_summary` | counts / bbox / attributes / groups + 大几何降级 |
| 材质 | `create_material` / `assign_material` / `get_material_info` | 50+ 参数白名单 + texture 引用识别 |
| HScript | `execute_hscript` | 包装 `hou.hscript` |
| 安全代码 | `execute_code` | 三档 policy + bypass 双开关 + 结构化 audit |
| 安全代码 | `get_last_scene_diff` | 仅 mutation 模式提供前后场景快照 |
| 截图 | `capture_pane_screenshot` / `render_node_network` / `list_visible_panes` / `capture_multiple_panes` | pane 截图,响应走 `apply_response_cap` |
| 渲染 | `render_single_view` / `render_quad_views` / `render_specific_camera` | 路径版渲染(落盘 `image_path`);`render_viewport_base64` / `render_quad_views_base64` / `render_specific_camera_base64` 已注销注册(slim-mcp-toolset;恢复 = 取消对应 `@mcp.tool()` 注释);缺 OGL 3.3 环境经 render policy redirect 到视口截图 |
| 渲染 policy | `start_render` / `monitor_render` | ROP 同步渲染(四层防御 + consent token)+ husk/mantra OS 进程 best-effort 监控(bridge-only) |
| 文档 | `get_houdini_help` | **本地 help server 优先** + 在线 SideFX 回退(stdlib `urllib` + `html.parser`);返 `_source` / `_fallback_reason` |
| 文档 | `verify_hou_api` | python_hou 默认 + `_ai_hint` 合成,AI-friendly wrapper over `get_houdini_help` |
| 诊断 | `check_connection` / `ping_houdini` | 不持久化连接的 ping |
| 缓存 | `manage_cache` | stats / invalidate / warmup |
| 知识库 | `get_best_practices` | fork 人工审查 advisory recipes(bridge-local,不建立 Houdini 连接) |
| 知识库 | `search_docs` / `get_doc` / `parse_hip_offline` | BM25 离线文档检索 / 全文 / 离线 .hip 解析 |
| 知识库 | `search_lessons` / `save_lesson` / `read_lesson` / `knowledge_stats` / `capture_workflow_snapshot` / `save_recipe` | 自进化知识沉淀:跨 root BM25 融合检索 / 沉淀 / 全文 / 统计 + 工作流快照 / recipe 写入 |

---

## 自进化知识库

agent 操作 Houdini 的试错经验跨 session 持久化为可检索 lesson(模块 `_lessons.py` +
`_lessons_search.py`,纯 stdlib、无嵌入模型)。**触发时机**:遇到报错、重试第 2 次
仍未解决、或遇到不认识的 API/参数时,先 `search_lessons` 检索既往经验;命中后用
`read_lesson` 拉全文;解决问题后用 `save_lesson` 沉淀。lesson 是 advisory,不替代
`verify_hou_api` / `get_houdini_help` / `get_best_practices`。

**自动捕获(零上下文成本)**:bridge 在响应出口检测 `status=error` 响应,把错误
事件以 append-only 方式写入个人库 `inbox/events.jsonl`(同指纹去重、≥3 次自动生成
draft 骨架并在检索时提示「已踩 N 次,请补充 fix」)。

**存储位置**(全部在个人目录 `~/.opera-houdini-mcp/`,不入仓库 git):

```
~/.opera-houdini-mcp/
├── config.json              # 注册表(仅声明额外团队 root,个人库自动发现)
├── knowledge/
│   ├── lessons/*.md         # draft + published lesson(9 字段 + id/status/strength/root/时间戳)
│   ├── recipes/BEST_PRACTICES.md   # 个人人工 recipes(可空)
│   └── inbox/events.jsonl   # 自动捕获原始事件
└── cache/index/<root-name>/ # 各 root BM25 索引缓存
```

**团队库注册**(`config.json`,可选):`path` 接受三种形式——`${VAR}` 环境占位符、
相对路径(相对 `~/.opera-houdini-mcp/`)、或**绝对路径**(Windows 盘符 `C:\` / `C:/`、
POSIX 前导 `/`、UNC `\\server\share`、前导 `\`)。绝对路径支持团队协作中各成员 NAS
映射盘符不同的场景——`config.json` 是本机配置(位于 `~/.opera-houdini-mcp/`,每台
机器各一份),各成员各自写自己盘符的绝对路径即可,无需统一环境变量,无跨机器误导。
`writable` 默认 `false`(AI 只读,晋升人工把关);占位符未定义 → `unconfigured`
静默跳过,路径不可达(绝对/相对路径指向的目录不存在、或占位符已定义但目录不可读)
→ `unavailable` 跳过并附 `_warning`,均不影响个人库。含 `${` 但非纯占位符的混合
形式(如 `${VAR}/sub`)仍被拒绝。

```json
[
  { "name": "team_knowledge", "path": "${TEAM_SHARE}/houdini/knowledge", "priority": 0.8, "writable": false }
]
```

团队协作 NAS 示例(各成员盘符不同,各自在**本机** `config.json` 写绝对路径;同事若
把同一 NAS 映射到其他盘符,则改成自己的盘符即可):

```json
[
  { "name": "team_knowledge", "path": "Z:\\team\\houdini\\knowledge", "priority": 0.8, "writable": false }
]
```

---

## RAG 文档检索与版本化索引

`search_docs` / `get_doc`(BM25 跨文档检索)面向「跨文档主题检索」,与
`get_houdini_help` / `verify_hou_api`(单条 API 结构化查询)互补。索引产物按
**Houdini 版本隔离**(`versioned-rag-index`):多版本共存互不覆盖,Houdini 升级后
自动为新版本构建全量索引。

**双进程架构**(检索与路由全部在 bridge 进程,env 不跨进程边界):

```
AI 客户端 ──stdio── bridge(houdini_mcp_server.py + _rag.py + _rag_lifecycle.py)
                      │  无 hou、无 HFS;LRU/BM25/预热都在此进程
                      │  版本路由:TCP 查 get_scene_info(hou_version + hfs_path)
                      │            → 进程内设 HOUDINI_MCP_RAG_INDEX_DIR
                      └──TCP 9876── server(server.py,Houdini GUI 进程,import hou)
```

**目录布局**(个人目录,不入 git):

```
~/.opera-houdini-mcp/rag/
├── 21.0.596/                # 每个 Houdini 版本一个目录,互不干扰
│   ├── index.v1.json        # 全量索引(H21:46 zip / 10,093 docs / 27.7 MB)
│   ├── build.status.json    # 构建状态(state/started_at/finished_at/pid/...)
│   └── eval/                # 评测报告落点(eval_rag.py 默认 out-dir)
├── 22.0.100/                # H22 升级后首次使用时自动构建
└── index.v1.json.legacy-5zip-20260909   # 旧 flat 索引封存(内容保留,不迁移)
```

**生命周期**(首次 RAG 工具调用时 lazy 触发,`_rag_lifecycle.py`):

1. **版本路由**:bridge 经 TCP 向 server 查 `get_scene_info` 取
   `houdini_version` + `hfs_path` → 设 env 指向 `rag/<ver>/`(进程内一次,
   粘滞)。server 不在线 → 回退 flat 解析序,30s 后重试;手工预设 env 则整体跳过。
2. **封存**:rag 根存在旧 flat 索引且版本目录无索引 → 改名
   `.legacy-5zip-20260909`(内容保留;它是 5-zip 局部快照,迁移等于把 11%
   覆盖合法化)。幂等,失败仅警告。
3. **自动构建**:版本目录无索引 → 独占 `build.lock` + 二次探测 → detached
   无窗口子进程(bridge 内嵌 Python 3.12,`CREATE_NO_WINDOW`,零端口)跑
   `scripts/build_rag_index.py --source $HFS/houdini/help --output <verdir>`;
   状态文件 pid 探活 + 300s 龄期双判据防并发/自愈;H21 全量构建实测 ~7.5s。
4. **预热**:daemon 线程 `load_index()`(27.7MB 加载 ~2.9s,绝不上事件循环——
   FastMCP 同步工具直接跑在事件循环上);构建完成后 watcher 自动 re-preheat。

**envelope 语义**(只加字段不改签名,现有消费者零影响):构建中 →
`rag_index_missing` + `building: true` + `eta_hint`(本轮回退
`get_houdini_help`);预热未完 → `rag_index_warming_up` + `warming_up: true`
(快速返回,不阻塞 bridge)。

**eval 门禁**(`scripts/eval_rag.py` + `scripts/rag_gold_set.json`,随 submodule
入库的 `scripts/rag_baseline.json` 为 H21 基线:MRR 0.4040 / hit@10 0.70):

```bash
# 对任意索引评测(报告默认落 <index 目录>/eval/,Markdown+JSON)
python scripts/eval_rag.py --index ~/.opera-houdini-mcp/rag/21.0.596/index.v1.json

# 与基线对比(可比性指纹判定:gold hash 相同且 zip 差异仅新增才可比)
python scripts/eval_rag.py --index <idx> --baseline scripts/rag_baseline.json

# 新版本索引定新基线
python scripts/eval_rag.py --index <idx> --write-baseline scripts/rag_baseline.json
```

门禁双层判据(整体 MRR 降幅 > 0.05 红 + RR 降幅 > 0.3 条目占比 ≥ 25% 红)+
gold_missing 守卫(> 2 条或 > 10% 红)+ 双向告警(MRR 异常升高黄灯,污染/
分母逃逸形态);告警不阻断构建。**注意**:5-zip 旧索引的 MRR 与全量不可比
(分母逃逸,实测 14/30 条 gold_missing 时 MRR 反而虚高)。

手工构建(不依赖 bridge 自动链):

```bash
python scripts/build_rag_index.py --source "$HFS/houdini/help" --version-dir 21.0.596
# 默认 zip 集 = help 目录全部 *.zip 减排除清单(images);--zips 可覆盖
```

---

## Console 日志与命令审计(feat-mcp-console-log-audit)

两个旁路能力:AI 排障取证(console 日志)+「AI 让 Houdini 干了什么」的事后追溯(命令审计)。

### Console 日志(server 侧)

- `get_console_log`(READ_ONLY):读 Houdini 进程内 Python 层 console 环形缓冲。
  参数 `offset` / `limit`(分页)、`tail`(末尾 N 条)、`last_seconds`(时间窗,**优先于 tail**)。
  envelope `{status, lines, offset, limit, total, filter_mode, truncated}` 过 `apply_response_cap`;
  每行 `{ts, ts_iso, stream, text}`(stream 区分 stdout / stderr)。
- `clear_console_log`(NO_UNDO):清空缓冲,返回清空前条数。
- **覆盖边界**:tee 只包装 Python 层 `sys.stdout` / `sys.stderr`(`print`、Python traceback、
  `execute_code` 捕获输出自动同步入缓冲);**不覆盖** Houdini 原生 C++ 层输出与独立 hython 进程输出。
- 典型用法:AI 遇到报错时截图(视觉证据)+ `get_console_log(last_seconds=120)`(文本证据)联合定位。
- 关闭开关:`HOUDINI_MCP_CONSOLE_LOG=0` 时启动完全跳过 tee 安装(缓冲恒空,两工具仍可调但无数据)。

### 命令审计(bridge 侧 JSONL)

- bridge 进程内**每次 MCP 工具调用**(全部工具,含 bridge-local 与只读查询)在结束时
  append 一行 JSON 到 `$TEMP/houdini_mcp/audit/audit-<yyyymmdd>-<seq>.jsonl`;
  字段:`ts`(ISO8601 毫秒)/ `session_id`(bridge 进程 UUID4)/ `tool` / `ok`(响应
  `status == "success"` 判定;str 响应按成功记)/ `duration_ms` / `args`(≤256 截断),
  失败附 `error_code` / `error_message`。
- 分段与保留:空闲超 15 分钟开新段(seq 续接当日最大,重启不覆盖);保留上限 30 段,超限删最旧。
- 旁路语义:落盘失败(磁盘满 / 权限)仅进程内 warning,绝不影响工具调用;审计内容不注入任何响应。
- **隐私边界**:`args` 摘要可能含用户本机路径,本机单用户信任边界内不脱敏;文件仅供人工与离线分析。
- **v1 只记录不重放**:不提供任何重放/回放工具(读回、重执行历史命令);重放为未来扩展,引入时另立 change。

### env

| 环境变量 | 默认 | 作用 |
|----------|------|------|
| `HOUDINI_MCP_CONSOLE_LOG_LINES` | `4000` | console 环形缓冲行数 |
| `HOUDINI_MCP_AUDIT_DIR` | `$TEMP/houdini_mcp/audit` | 审计目录覆盖 |
| `HOUDINI_MCP_AUDIT_KEEP` | `30` | 审计保留段数 |
| `HOUDINI_MCP_AUDIT_SEGMENT_MIN` | `15` | 审计分段间隔(分钟) |

---

## AI 调用引导(feat-mcp-tool-guidance)

把「引导 AI 正确使用这套 MCP」内建到协议元数据与返回文本本身(用户工作区外
AGENTS.md 不可控,MCP 必须自带行为契约)。四个机制:

### Tool annotations(三分类映射)

- `_tool_annotations.py` 在 bridge 启动时后处理全部工具的 `annotations`:
  以 server 命令三分类为锚——READ_ONLY 工具 `readOnlyHint=true`(含
  `idempotentHint=true`);破坏性语义(节点删除 / 场景载入新建 / execute_code
  变更档 / 磁盘清写类,12 个)`destructiveHint=true`;其余变更类显式
  `readOnlyHint=false, destructiveHint=false`。
- 对账测试双向守卫:annotations 注册表 ↔ server 三分类集合(含 7 对 bridge
  工具名 ↔ server 命令名改名映射),任一侧增删都会红。
- 规范语义:annotations 是对客户端的**提示而非安全边界**;权威执行层仍是
  server 三分类 + render policy。

### instructions 行为契约

`initialize` 响应的 `instructions`(≤1200 字符)承载四要点:专用工具优先 /
`execute_code` 最后手段、写 hou API 前先 `verify_hou_api`、遇错或第 2 次重试
失败先查 `search_lessons` / `get_best_practices`、渲染走 `start_render` +
截图取证用 `capture_pane_screenshot`。

### execute_code 失败引导(`_ai_hint` / `_hint`)

- 返回文本含 `hou.*` 异常模式(Traceback / Stderr 段)时,末尾追加
  `_ai_hint:` 行——列出提取到的 API 名(去重 ≤3)+ `verify_hou_api` /
  `get_houdini_help` 指引;无 `hou.*` 模式不追加(零噪音)。
- 会话内 `execute_code` 调用达到阈值(默认 5)后每次返回追加 `_hint:` 行,
  提示 `set_parameters` / `create_wrangle` / `connect_nodes` / `batch` 替代。
  `HOUDINI_MCP_EXEC_HINT_THRESHOLD=0` 关闭。
- hint 为**文本行追加**(返回仍为 str,不改 envelope 形态)。

### 描述瘦身与 lint

全部工具 description 两层拆分:调用方信息(何时用 / 参数 / 边界 / 失败先查
什么)留 docstring;实现备忘(PR 编号 / 设计史 / 路径怪癖)搬家到函数体注释。
`tests/test_tool_description_lint.py` 守卫:pattern 禁令(`PR \d` / issue 号 /
change 代号 / `§`)+ 长度上限(默认 ≤1200 字符)+ help 类首行触发时机 +
备忘搬家抽查(20 工具跨五批)。

---

## `execute_code` 安全模型

| Policy | mutation | dangerous | heavy_geometry | import hou | 默认 bypass |
|--------|----------|-----------|----------------|------------|-------------|
| `read-only` | **拒绝**(命中 mutation 正则/AST) | 拒绝 | 拒绝 | 拒绝 | — |
| `normal`(默认) | 允许 | 拒绝(除非 `allow_dangerous=True`) | 拒绝(除非 `allow_heavy_geometry=True`) | 提示 | 仅在客户端显式开启 |
| `privileged` | 允许 | 允许(必须同时开启 `allow_dangerous=True` **和** `HOUDINI_MCP_ALLOW_BYPASS=1`) | 允许(必须同时开启 `allow_heavy_geometry=True` **和** `HOUDINI_MCP_ALLOW_BYPASS=1`) | 允许 | 必须服务端环境变量 |

**双开关原则**:任何 dangerous / heavy / privileged 操作都需要「请求端参数 + 服务端环境变量」同时开启。服务端不开环境变量,再多客户端请求也无效。

**Audit**:每次 `execute_code` 调用都会在响应里附 `_audit` 块(policy / dangerous_hits / heavy_hits / mutation_hits / bypass_used / elapsed_ms / undo_group / exception)。

**超时**:执行超时**不会**自动 `hou.undos.performUndo()`,避免误回滚正常操作。客户端需根据 `_audit.elapsed_ms` 自行决定。

---

## AI 调用 hou API 的硬约束

> 任何 AI agent 通过 `execute_code` 调用 hou API 之前 **MUST** 先 verify。`hou` 是 C 扩展,跨 major version 间会重命名 / 废弃 / 新增方法。假定跨版本 hou API 等价 = bug 风险(hang / type-check 失败 / 行为不一致)。

**正确工作流**:

1. **调 `verify_hou_api('Class.method')` 先看 `_ai_hint`**,绝不直接把假设的 hou API 写进 `execute_code` 的 `code` 参数
2. 若返 `status="success", methods=[]`(API 不存在),改用其他等价 API(例如在 SOP 子节点设 display/render flag,而不在 OBJ 容器调不存在的 setDisplayNode)
3. 若返 `status="success"` 且 `_ai_hint` 提到 thread 安全 caveat(如 `ObjNode.setInput` 需 input_index + item + output_index 三参),谨慎评估是否值得在 worker thread 冒险

### 三级 fallback(F0 → F1 → F2 → F3)

按优先级从高到低:

- **F0 — 判断 hou 版本**:verification 第一步必须先 `hou.version()` 确认 major version,因为 hou API 在跨 major 时会重命名 / 废弃 / 新增
- **F1 本地 hou help**(优先,无网络依赖,最快):调 `verify_hou_api(item_name=...)`;若需进一步信息,`hou.node(path).help()`(已存在节点)或 `execute_code` 跑 `help(hou.<Class>.<method>)`
- **F2 联网 SideFX 文档**(F1 拿不到时):`verify_hou_api(item_name="<Class>.<method>", help_type="python_hou")` 走 stdlib `urllib.request` 抓 `https://www.sidefx.com/docs/houdini/hom/hou/<name>.html`;不引入新 pip 依赖
  - **local-help-first(自动)**:`get_houdini_help` / `verify_hou_api` 优先打 Houdini 本地 help server(默认 `http://127.0.0.1:48626/`),本地不可达 / 超时 / 白屏(HTTP 200 但内容无效)时**自动回退在线**。返回 `_source` 字段(`"local"` / `"online"` / `""`)告知实际命中方,`_fallback_reason` 说明回退原因。健康缓存:本地失败后 60s cooldown 内跳过本地直查在线(fix-mcp-help-cap-protocol:本地 HTTP 404 **不**进 cooldown——页面不存在是合法答案,直接回退在线;cooldown 仅由 timeout / 5xx / 网络错 / 白屏触发)。`Class.method` 点号名(python_hou)自动拆分为类页面 + 方法精确匹配,不再直接拼 URL 导致双 404
- **F3 让用户开梯子**(F2 返 `status="error"` 且 `reason` 含网络关键字时):AI agent 必须在输出里**显式**写出"⚠ SideFX 文档站不可达,请检查网络/梯子,或在 Houdini 内用 `hou.helpServerUrl()` 查本地帮助"

跨工具说明:底层 = `get_houdini_help`;AI-friendly wrapper = `verify_hou_api`。建议优先用 `verify_hou_api` 调 hou API,`get_houdini_help` 用于 SOP/OBJ 节点本身或 vex_function 查询。

> 详细复盘 / postmortem(含 2026-07-21 `ObjNode.setInput` hang 案例)见 `CHANGELOG.md`。

---

## Configuration

| 环境变量 | 默认 | 作用 | 适用工具 |
|----------|------|------|----------|
| `HOUDINI_MCP_ALLOW_BYPASS` | 未设 | `privileged` policy 启用开关(**不设则任何 bypass 请求都失败**) | `execute_code` |
| `HOUDINI_MCP_ALLOW_NEW_SCENE` | 未设 | `new_scene` 放行开关(服务端/Houdini 进程内读取;未设或非 truthy 时**默认禁用**,防 AI 自主清空用户场景;`1/true/yes/on` 显式放行) | `new_scene` |
| `HOUDINI_MCP_ENV_DIR` | 见下方约定 | embedded env 目录**绝对路径**覆盖;未设时从 package 目录名自动派生(`<dirname>-env/`,与 package 平级) | `_env_dir()`(3 处 prod + 2 处 test) |
| `HOUDINI_MCP_LOCAL_HELP_URL` | `http://127.0.0.1:48626/` | 本地 help server base URL | `get_houdini_help` / `verify_hou_api` |
| `HOUDINI_MCP_LOCAL_HELP_TIMEOUT` | `8.0` | 本地探测短超时(秒,clamp `[0.5, 60.0]`;fix-mcp-help-cap-protocol:2.5→8.0,H21 本地 ~1MB 页面实测需 6-8s) | `get_houdini_help` / `verify_hou_api` |
| `HOUDINI_MCP_LOCAL_HELP_COOLDOWN` | `60` | 本地失败后 cooldown 窗口(秒,clamp `[0.0, 600.0]`) | `get_houdini_help` / `verify_hou_api` |
| `HOUDINI_MCP_LOCAL_HELP_DISABLE` | 未设 | `1` / `true` / `yes` / `on` 时完全禁用 local-first,退化到"仅在线" | `get_houdini_help` / `verify_hou_api` |
| `HOUDINI_MCP_RAG_INDEX_DIR` | bridge 路由时自动设为 `~/.opera-houdini-mcp/rag/<ver>/` | RAG 索引目录覆盖;bridge 首次 RAG 调用时查 Houdini 版本自动指向版本目录(versioned-rag-index);手工预设则整体跳过路由。未设且路由失败时解析序 home 目录(存在即用)→ 旧 fork 模块目录(兼容)。索引由 `scripts/build_rag_index.py` 生成(默认自动构建,手动命令见「RAG 文档检索与版本化索引」章节) | `search_docs` / `get_doc` |
| `HOUDINI_MCP_CONSOLE_LOG` | 未设(开) | `0` 时 Houdini server 启动完全跳过 console tee 安装(`get_console_log` 恒空) | `get_console_log` / `clear_console_log` |
| `HOUDINI_MCP_CONSOLE_LOG_LINES` | `4000` | console 环形缓冲行数 | `get_console_log` |
| `HOUDINI_MCP_AUDIT_DIR` | `$TEMP/houdini_mcp/audit` | 审计 JSONL 目录覆盖 | 全部工具(bridge 审计) |
| `HOUDINI_MCP_AUDIT_KEEP` | `30` | 审计保留段数 | 全部工具(bridge 审计) |
| `HOUDINI_MCP_AUDIT_SEGMENT_MIN` | `15` | 审计分段间隔(分钟) | 全部工具(bridge 审计) |
| `HOUDINI_MCP_EXEC_HINT_THRESHOLD` | `5` | `execute_houdini_code` 会话计数达阈值后每次返回附 `_hint:` 专用工具指引行;`0` 关闭 | `execute_houdini_code` |
| `RAPIDAPI_KEY` | 未设 | OPUS 资产库 API key | `_opus.py` |
| `RAPIDAPI_HOST` | `opus5.p.rapidapi.com` | OPUS API host | `_opus.py` |
| `RAPIDAPI_HOST_URL` | `https://opus5.p.rapidapi.com/` | OPUS API base URL | `_opus.py` |

**`HOUDINI_MCP_ENV_DIR` 派生约定**:

```
package at  <parent>/<dirname>/                 env 派生为  <parent>/<dirname>-env/

examples:
  opera-houdini-mcp/                            opera-houdini-mcp-env/
  external/houdinimcp/                          external/houdinimcp-env/
  external/mcp/                                 external/mcp-env/
  D:/我的项目/external/houdinimcp/              D:/我的项目/external/houdinimcp-env/
```

- **相对路径的 override 会被静默忽略**(fallback 到默认派生),因为 bridge 进程的 cwd 取决于 AI 工具怎么 spawn 它(Claude Desktop / Cursor / ZCode 各不相同),相对路径不可靠
- 99% 的场景**不需要设这个变量**,保持默认派生即可

### OPUS 集成(可选)

OPUS 提供大量家具 / 环境程序化资产。订阅步骤:

1. 注册 [RapidAPI](https://rapidapi.com/) 账号
2. 订阅 [OPUS API](https://rapidapi.com/genel-gi78OM1rB/api/opus5/pricing)
3. 复制本地配置文件:

```bash
cp urls.env.example urls.env  # urls.env 已在 .gitignore
```

4. 编辑 `urls.env` 填入 key:

```env
RAPIDAPI_HOST_URL=https://opus5.p.rapidapi.com/
RAPIDAPI_HOST=opus5.p.rapidapi.com
RAPIDAPI_KEY=<your-key>
```

> **不设 key 时 server 仍可启动,仅 OPUS 工具被禁用**。OPUS 集成是可选的。

---

## Upstream Sync Policy

| 字段 | 值 |
|------|-----|
| 上游仓库 | [`capoomgit/houdini-mcp`](https://github.com/capoomgit/houdini-mcp) |
| 同步基线 | `de4fd93`(2026-07-17) |
| 同步方式 | **cherry-pick only**(禁止 merge) |
| 提交规范 | 标题前缀 `[opera]`,正文引用上游 PR 号(如 `[upstream PR #42]`) |
| 同步窗口 | 手动触发,每次有上游合入后 7 天内 |

**为什么禁止 merge**:保持 opera 自身的提交图干净可审计,区分"上游原样"与"opera 独有"的改动点。

**贡献到上游**:opera 独有的改进建议优先以 PR 形式回提给 `capoomgit/houdini-mcp`,合入后再 cherry-pick 回来。这样全社区都能受益。

---

## Testing

```bash
# 全量回归(推荐入口;2026-09-26 口径:74 个测试文件 / 2298 passed / 0 failed)
cd external/houdinimcp
pytest tests/

# 单文件示例
pytest tests/test_rag_lifecycle.py tests/test_rag_versioned.py
```

**防泄漏护栏(fixture 自动生效,无需手工设 env)**:`tests/conftest.py` 的 autouse
fixture 默认把桥端口钉到死端口,测试**不会穿透真机 9876**;确需真机的手动 e2e
必须显式 opt-in(`HOUDINI_MCP_TEST_ALLOW_LIVE=1 pytest tests/h21_live_*.py`)且
先征得用户同意。注意 `search_docs` / `get_doc` 有遗留必填参数 `ctx`(体内未用,
调用时传空串即可)。

测试基线:Houdini 21.0 + Python 3.11。详细 fixture / 共享 helper 见 `tests/conftest.py` 与 `tests/_e2e_helpers.py`;历史专项回归 `tests/phase5_full_regression.py` 保留可用。

---

## Troubleshooting

| 现象 | 排查 | 修复 |
|------|------|------|
| AI 连不上 9876 | `netstat -an \| findstr 9876` | 关防火墙,或在 shelf 重新 Start MCP |
| License 相关 | Houdini license server 状态 | `hkey -n` 看 license,Houdini 21 试用版过期需要重新申请 |
| 升级后工具找不到 | Houdini 还加载着旧 plugin | 在 shelf 点 Stop MCP → 重启 Houdini → 点 Start MCP |
| `get_houdini_help` 失败 | 本地 help server(`127.0.0.1:48626`)是否可达 + 网络是否能访问 `www.sidefx.com` | 看 `_source` / `_fallback_reason`:`online` + `local_*` 说明本地挂了已自动回退在线;两边都挂设 `HOUDINI_MCP_LOCAL_HELP_DISABLE=1` 走纯在线,详见 `_help.py` |
| `execute_code` 永远 `bypass_used=false` | 服务端 `HOUDINI_MCP_ALLOW_BYPASS` 未设 | 服务端 `export HOUDINI_MCP_ALLOW_BYPASS=1` 并重启 server |
| `import houdinimcp` 找不到 | `external/` 不在 `sys.path` | 按 [Embedding §3](#3-启动-server) 把 `external/` 加进 `sys.path` |

---

## Edge Cases & 集成陷阱

### 改 package 目录名

env 路径自动从 package 目录名派生(`<dirname>-env/`)。**改了 package 目录名要同步改 env 目录名**,或用 `HOUDINI_MCP_ENV_DIR` 指过去。env 内的 Python + 依赖无需重装,**纯文件系统 rename 即可**:

```bash
mv external/houdinimcp external/opera-houdini-mcp
mv external/houdinimcp-env external/opera-houdini-mcp-env   # 跟着改
```

### 多个项目共享一个 env

每个项目派生的 env 路径按各自 package 目录名走,默认不会共享。多项目共享:

```bash
export HOUDINI_MCP_ENV_DIR=/shared/envs/opera-houdini-mcp-env
```

**建议绝对路径**。相对路径的 override 会被静默忽略(bridge 进程的 cwd 取决于 AI 工具怎么 spawn,跨进程不稳定)。

### Windows 目录大小写

Windows 不区分大小写但 git checkout 保留原始大小写。如果你在 Windows 上 clone 后看到 `Houdinimcp/` 而代码 baseline 用 `houdinimcp/`,basename 派生会按实际拼写走,可能导致 env 路径大小写不一致:

```powershell
# 强制 git 严格大小写敏感
git config core.ignorecase false
# 如已 checkout 成错误大小写,重命名
Rename-Item external/Houdinimcp external/houdinimcp
```

### env 跨 OS 不通用

env 内嵌的 Python 是平台相关 wheel(`cp311-cp311-win_amd64` 这种)。**Windows env 拷到 Linux 跑不了**,反之亦然。跨 OS 迁移必须重装。

### 改 env 路径不需要重装依赖

env 改名、移位置、改 owner,**依赖本身可以原地保留**:

```bash
mv external/houdinimcp-env /new-location/my-env
export HOUDINI_MCP_ENV_DIR=/new-location/my-env
```

无需 `pip install`,无需重下 Python 包。

### git worktree / 多分支并行

每个 worktree 派生独立 env,不会互窜。共享 env 见上方「多个项目共享一个 env」。

### 权限与只读 env

env 目录存在但**不可写**(网络盘权限、只读 checkout)时,`_consent_dir()` 的 `os.makedirs` 兜底会抛 `PermissionError`。安装时确保 env 所在目录对运行用户可写。

### `pip install -e .` 会污染全局

不要把 opera-houdini-mcp 装到全局 Python。env 是隔离的,依赖会随 env 走;全局装会污染系统 Python,且后续覆盖会破坏 env 完整性。

---

## Contributing

1. Fork → 建分支 → commit(标题前缀 `[opera]`,正文引用对应上游 PR 号如有)
2. `pytest tests/` 全绿
3. PR 描述里说明:
   - 改动的 Tier 1 工具编号(如果适用)
   - 是否新增 pip 依赖(**不允许**,除非有充分理由并单独标注)
   - 是否动到不变量(见下)
4. 等 CI / 维护者 review

### 不变量清单(动到任一需先在 issue 里讨论)

| 不变量 | 值 |
|--------|-----|
| 监听端口 | `127.0.0.1:9876` |
| pip 依赖基线 | `mcp[cli]==1.12.2 + requests + python-dotenv` |
| MCP JSON key | `mcpServers.houdini` / `mcp.houdini` / `mcp_servers.houdini` |
| 公共 API | `start_server` / `stop_server` / `restart_server` / `is_server_running` / `initialize_plugin` |

---

## Security

- `execute_code` 是 LLM 驱动 Python 执行入口,三档 policy + 双开关 + 结构化 audit 是当前安全基线
- 漏洞报告:通过 GitHub Issue 的 **Private vulnerability reporting** 渠道(**不要**在公开 issue 里贴 PoC),或直接联系维护者

---

## License & Acknowledgement

本仓库全部代码沿用上游 [MIT License](./LICENSE)。`opera-houdini-mcp` 本身的改动部分同样以 MIT 协议发布。

Houdini-MCP 的最初设计参考了 [blender-mcp](https://github.com/ahujasid/blender-mcp),感谢他们的贡献。`opera-houdini-mcp` 是 [capoomgit/houdini-mcp](https://github.com/capoomgit/houdini-mcp) 的独立 fork,遵循 MIT 协议,原版权归 Capoom 2025 所有。提交通过 cherry-pick 而非 merge 同步上游。

TDQS

C2.7/5.0

Scored across 174 tools

Disambiguation2/5

Many tools have overlapping purposes, such as get_geo_summary vs get_geometry_info, verify_hou_api vs get_houdini_help, and get_scene_info vs get_scene_summary. The sheer number of similarly named get_* and create_* tools makes it difficult for an agent to select the correct one without reading extensive descriptions.

Naming Consistency2/5

Tool names follow no single consistent pattern: some use verb_noun (set_parameters), some use prefix-based conventions (hda_list, pdg_cook, lop_prim_get), and others mix forms (uninstall_hda vs hda_install). This inconsistency increases cognitive load and makes predictions about tool names unreliable.

Tool Count1/5

With 174 tools, this server vastly exceeds the typical well-scoped range of 3-15 tools. The extreme count overwhelms agents and likely degrades selection accuracy, making it an extreme mismatch for a coherent MCP surface.

Completeness5/5

The server provides extensive CRUD/lifecycle coverage across Houdini's major domains: nodes, parameters, geometry, rendering, DOPs, COPs, CHOPs, LOPs, PDG, materials, HDAs, takes, caches, and even a knowledge base. No obvious dead ends or significant missing operations were found.

Maintenance

ActivityMaintained
ResponsivenessNo issues