rhino-gh-spatial-mcp
by emmaruyiyang
README.md
# rhino-gh-spatial-mcp
空间推理 + 性能评估层,构建在 [`jingcheng-chen/rhinomcp`](https://github.com/jingcheng-chen/rhinomcp) 之上(见 [rhino-gh-spatial-mcp-brief.md](./rhino-gh-spatial-mcp-brief.md))。
`rhinomcp` 负责"怎么操作"(建几何、连 GH 组件、设 slider、求解、截图)。这个仓库只加一层:把场景读成结构化的空间关系 (`spatial_graph`),算几个纯几何性能指标 (`performance_report`),并跑一个 evaluate → adjust → recompute → re-evaluate 的闭环。**不重写任何几何创建 / GH 连线逻辑** —— 所有会改变 Rhino 文档的操作,都是直接调用 rhinomcp 已有的 GH 命令(`gh_set_parameter_value` / `gh_run_solution` / `gh_add_component` / `gh_connect_components` 等)。
## 架构
```
Agent (Claude Code / Cursor / ...)
│ MCP (stdio) │ MCP (stdio)
▼ ▼
rhinomcp 的 Python MCP Server rhino-gh-spatial-mcp (本仓库)
│ TCP 127.0.0.1:1999 │ TCP 127.0.0.1:1999 (同一个端口)
└──────────────┬───────────────────┘
▼
Rhino 8 内的 rhinomcp 插件 (mcpstart 后监听)
│
▼
Rhino + Grasshopper
```
两个 MCP server 是两个独立进程,都作为 TCP client 连到 Rhino 插件里**同一个**监听 socket(rhinomcp 插件本身支持多个客户端连接,一台机器同时装两个 agent 工具本来就是这个模式)。本仓库不依赖 `rhinomcp` 的 Python 包本身(它没有对外暴露 Python API,工具只存在于它自己的 MCP 进程里),而是复刻了它极简的 4 字节长度前缀 + JSON 的帧协议,直接对话同一个插件(见 [`bridge.py`](src/rhino_gh_spatial_mcp/bridge.py))。
## 前置条件
1. Rhino 8(Windows/macOS),安装 `rhinomcp` 插件:Rhino 内 `Tools → Package Manager` 搜索 `rhinomcp` 安装,重启 Rhino。
2. 打开 Rhino 后,在命令行输入 **`mcpstart`**(每次 Rhino 会话都要手动跑一次,插件不会自动监听)。
3. 安装 `uv`(`brew install uv`)。
## 安装
```bash
cd rhino-gh-mcp
uv sync # 或者: pip install -e ".[dev]"
```
需要 Python ≥3.10(和 rhinomcp 一致)。
## 注册两个 MCP server
```bash
# 底座:负责建几何 / 连 GH 组件 / 求解 / 截图
claude mcp add rhino -- uvx rhinomcp
# 本仓库:负责空间推理 / 性能评估
claude mcp add rhino-gh-spatial -- uv run --directory /path/to/rhino-gh-mcp rhino-gh-spatial-mcp
```
或手动写 MCP 配置:
```json
{
"mcpServers": {
"rhino": { "command": "uvx", "args": ["rhinomcp"], "env": { "RHINO_MCP_HOST": "127.0.0.1" } },
"rhino-gh-spatial": {
"command": "uv",
"args": ["run", "--directory", "/path/to/rhino-gh-mcp", "rhino-gh-spatial-mcp"],
"env": { "RHINO_MCP_HOST": "127.0.0.1", "RHINO_GH_SPATIAL_CONFIG": "/path/to/rhino-gh-mcp/massing.json" }
}
}
}
```
## 环境变量
复用了和 `rhinomcp` 完全相同的 `RHINO_MCP_*` 变量名,一份 `.env` 能同时配好两个 server:
| 变量 | 默认值 | 说明 |
|---|---|---|
| `RHINO_MCP_HOST` | `127.0.0.1` | 只允许 loopback,除非 `RHINO_MCP_ALLOW_REMOTE=1` |
| `RHINO_MCP_PORT` | `1999` | rhinomcp 插件监听端口 |
| `RHINO_MCP_TIMEOUT` | `15.0` | socket 超时(秒) |
| `RHINO_GH_SPATIAL_CONFIG` | `massing.json`(相对当前工作目录) | 见下面的 massing 配置 |
## massing 配置 (`massing.json`)
MVP 的核心简化:每个参数化体量的 bbox,直接从 GH 里的 number slider 当前值算出来(`gh_get_component_info` 按 nickname 读 `value/min/max`),而不是去解析 GH 输出的 Brep 几何 —— 这样空间推理层只是"读 GH 参数状态"的轻量客户端,不用重新实现 GH 几何序列化。
```json
{
"north_axis": "+Y",
"site_area_sqm": 900,
"site_layer_filter": null,
"context_layer_filter": "Context",
"adjacency_threshold_m": 0.5,
"massings": [
{
"name": "MainBlock",
"size_sliders": { "width": "Width", "depth": "Depth", "height": "Height" },
"origin_sliders": { "x": null, "y": null, "z": null },
"floors": 1
}
]
}
```
`north_axis` 只支持 `+Y/-Y/+X/-X` 四种(假设体量轴对齐、无旋转 —— MVP 范围内的已知限制)。`context_layer_filter` / `site_layer_filter` 指向 Rhino 文档里按图层过滤的静态物体(通过 `get_objects` 读取),会和参数化体量一起进入 `spatial_graph`。
## Resources / Tools
- Resource `spatial://graph`:最近一次算好的空间图(每个物体的 bbox/尺寸 + 两两之间的 adjacency/containment/方位/距离)。
- Resource `performance://report`:最近一次算好的性能指标报告。
- Tool `analyze_spatial_relationships()`:重新读场景、刷新 `spatial_graph`。只读。
- Tool `compute_metrics(names=None)`:算 `south_facing_area` / `far` / `shape_factor` / `nearest_neighbor_distance` / `sightline_count`,刷新 `performance_report`。只读。
- Tool `evaluate_design(goal)`:按目标(自然语言关键词或 `{"metric":..., "direction":...}`)算分并给出建议调哪个 slider(闭式敏感度分析,不触发重算)。只读。
- Tool `propose_and_apply_adjustment(goal, step_fraction=0.1, revert_if_worse=True)`:真正调 slider → `gh_run_solution` 重算 → 再评估,返回前后得分对比。这是本仓库唯一会修改 Rhino 文档的 tool。
- Tool `setup_demo_scene(...)`:一次性搭好 MVP 演示场景(见下)。搭建过程完全通过 rhinomcp 自己的 `gh_add_component`/`gh_search_components`/`gh_connect_components`/`create_object` 完成。
## 跑一遍 MVP 闭环(验收)
1. Rhino 里跑 `mcpstart`。
2. 让 agent 调用 `setup_demo_scene()`:搭 3 个 slider(Width/Depth/Height)接到一个 Box 组件上,再放一个固定的 "Context" 邻居体量用来演示间距关系,并写好 `massing.json`。
3. `analyze_spatial_relationships()` → 应该能读到 `spatial_graph`:两个物体、bbox、方位、间距。
4. `evaluate_design("让这个体量朝南受光最大化")` → 返回当前南向受光面积得分 + 建议调 Width 还是 Height。
5. `propose_and_apply_adjustment("让这个体量朝南受光最大化")` → 实际调整 slider、重算,返回调整前后得分(应有可复现的提升)。
## 不做(MVP 范围之外)
- 不重写几何创建 / GH 连线 / viewport 截图(rhinomcp 已有,直接调用)。
- 不做真实日照(Ladybug 等)/ CFD / 结构模拟 —— 所有指标都是纯几何代理值。
- 不支持体量旋转(轴对齐 bbox only)。
- 不做 GUI。
## 测试
```bash
uv run pytest
```
测试用一个进程内的假 TCP server([`tests/mock_rhino_plugin.py`](tests/mock_rhino_plugin.py),实现和真实插件相同的帧协议)跑通 bridge / spatial / metrics / evaluator / demo 全部逻辑,不需要真的打开 Rhino。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues