rhino-mcp is a Model Context Protocol server that lets Claude (or any MCP client) drive Rhino 8 — creating geometry, manipulating layers and materials, baking Grasshopper output, exporting STEP/IGES/STL/OBJ — through plain natural-language tool calls.
It runs in two modes:
- Standalone (default): backed by
rhino3dmfor headless.3dmfile authoring; works without Rhino installed and exposes ~242 tools (geometry, file-I/O, transform, layer, material, analysis, RhinoScript docs, composition, document hygiene, geometry validation, GH template catalogue, freeform skin / panelisation / curvature / fields, drawing-set sheet + title block, schedule / quantity, block definition, sun-position + shadow projection, BIM metadata tagging, 19 physical material presets, BRE daylight factor + Bird DNI clear-sky model, and multi-Rhino announcement-file discovery). - Bridge: when the C# bridge plugin is loaded in a live Rhino 8 session, the server transparently forwards every call (booleans, lofts, sweeps, viewport image capture + zoom-to-object / zoom-to-layer, render, scripting (Python and gated Roslyn C# with timeouts and extra references), deformation, NURBS editing, SubD, paneling, freeform analysis with true Gaussian curvature, drawing-set view placement + section cuts + PDF export, accurate Brep-area schedules, block explode/redefine, ray-cast solar exposure + Brep ray probe, IFC / gbXML import-export, HDRI environment, camera / light / render / turntable automation, layer-material binding, and every Grasshopper operation including batched
connect_manywiring and slider placement) to RhinoCommon and Grasshopper.Instances, exposing ~245 tools.
- Full geometry catalogue — points, lines, polylines, arcs, circles, ellipses, polygons, helixes, NURBS curves, rebuilds.
- Solids and meshes — boxes, spheres, cylinders, cones, tori, mesh boxes; booleans / sweeps / lofts / fillets via the bridge.
- Transforms — move, rotate, scale, mirror, plane-to-plane orient, linear/polar/rectangular arrays.
- Script execution — run arbitrary RhinoScript Python (IronPython) or RhinoCommon C# (Roslyn) code in a live Rhino session, with built-in RhinoScript API documentation search (899 functions).
- Advanced modeling — deformation (bend/twist/taper/flow), NURBS editing (rebuild/unroll/evaluate), SubD, surface matching, extraction (dup edge/border/isocurve/Make2D), control points, and paneling tools.
- Drawing-set authoring — sheet / title block / north arrow / scale bar in standalone; multi-view placement, section cuts, and PDF export through the bridge.
- Quantity & schedule — per-layer / per-material / per-
user_textaggregation with CSV export; bridge mode usesAreaMassProperties/VolumeMassPropertiesfor accurate Brep area + volume. - Block / instance reuse — define / insert / list blocks in standalone; explode / redefine in bridge mode for synchronized component edits.
- Environmental analysis — sun position + monthly sun-path polylines + shadow projection (NOAA SPA approximation); ray-cast solar exposure in bridge mode. Daylight precision adds Bird DNI clear-sky model with Kasten-Young air mass and Linke turbidity, plus BRE simplified daylight factor.
- BIM interchange — IFC2x3 / IFC4 / IFC4x3 import & export, gbXML export, and IFC PropertySet metadata tagging that round-trips through
user_text. - Material presets + HDRI — 19 bundled physical presets (concrete / brick / steel / aluminium / glass / timber / stone / plaster / fabric / landscape) keyed by category; HDRI environment for rendering.
- Render automation — camera / light / render-engine setup, render-to-file, and parametric turntable sequences (bridge mode).
- Undo / redo and batch operations — every bridge operation is wrapped in undo records; batch modify changes many objects in a single call.
- Layers, materials, blocks, groups — full attribute control on the document.
- File I/O — open/save
.3dm, export OBJ/STL standalone; STEP/IGES/DXF and screenshots via the bridge. - Inline base64 screenshots —
rhino_screenshot(as_base64=True)returns the PNG payload inline for visual verification by the LLM. - Rich object selection —
rhino_object_selectfilters by glob name pattern, layer, RGB color, object type, and user-text key/value pairs. - Pagination on bulk queries —
rhino_list_objectsreturns apagination: {total, offset, limit, returned, has_more}block so large documents stay manageable. - Scene composition shortcuts (v0.2) —
rhino_place_grid,rhino_stack_floors,rhino_scatter,rhino_replicate_along_curvecollapse "loop + transform" into a single tool call. - Document hygiene tools (v0.2) — query/set units, tolerances, and base point (
rhino_document_units_*,rhino_tolerance_*,rhino_origin_set);rhino_document_summaryexposes them so the LLM can verify scale before issuing geometry calls. - Geometry validation (v0.2) —
rhino_validate_brep,rhino_report_mesh_health,rhino_curve_continuity, plusrhino_check_naked_edges(bridge) for explicit topology diagnostics before booleans / exports. - Grasshopper template loader (v0.2) —
gh_template_listreads a manifest of pre-wired definitions;gh_load_template,gh_bind_template_parameter,gh_run_template(bridge) load, parameterise, and bake them. - Free-form architecture toolkit —
rhino_skin_from_sections,rhino_uv_grid_panels,rhino_panel_planarity,rhino_panel_curvature_classify,rhino_surface_developable_score,rhino_attractor_displace_points,rhino_smooth_polyline. Bridge mode adds true Gaussian / mean / principal curvature, world-axis slicing, and waffle ribs. - Strategy prompts for the LLM — seven
@mcp.prompt()guides (general_strategy,rhinoscript_workflow,viewport_workflow,parametric_workflow,bim_authoring_workflow,design_dialogue_workflow,freeform_workflow) help Claude pick the right tool, avoid hallucinated APIs, and keep the user in the loop on design decisions. - Async-capable tools — read-only query tools run on
async defso concurrent bridge round-trips don't stall the MCP transport. - Robust connection layer — JSON-RPC over named pipe / Unix socket / TCP with
MSG_PEEKliveness probes and exponential backoff with jitter on reconnect. - Grasshopper automation — open
.ghfiles, drop components, wire them, set sliders/panels/toggles, run, bake, read DataTrees. - Three transports — stdio (Claude Desktop), Streamable HTTP, Docker/TCP.
- Capability-aware registration — bridge-only tools are simply not registered when no bridge is reachable; no stub functions, ever.
- Actionable errors — every failure carries a category, a remediation hint, and details the agent can act on.
┌──────────────┐ stdio | HTTP ┌────────────────┐
│ Claude / │ ◀──────────────────▶ │ rhino-mcp │
│ MCP client │ │ (Python) │
└──────────────┘ └─────┬──────────┘
│
JSON-RPC 2.0 │ (named pipe / unix socket / TCP)
│
┌─────────▼──────────┐
│ C# Bridge Plugin │
│ (inside Rhino 8) │
│ ──────────────── │
│ RhinoCommon │
│ Grasshopper │
│ Roslyn (C# exec) │
│ IronPython (Py) │
└────────────────────┘
In standalone mode the right-hand side is replaced by an in-process rhino3dm.File3dm document; bridge-only tools are unregistered.
- Python 3.11 or newer
rhino3dm >= 8.9,mcp[cli] >= 1.2,pydantic >= 2.6(installed automatically)- Optional: McNeel Rhino 8 (any platform) — needed only for bridge mode
- Optional: .NET 8 SDK — to build the C# bridge plugin (
dotnet build) - Optional:
pywin32on Windows for the named-pipe transport
uv tool install rhino-mcp
# or, from a checkout:
uv sync && uv run rhino-mcppip install rhino3dm-mcp
rhino-mcp --versionAfter installing the package, register the server with Claude Desktop in a single command instead of editing claude_desktop_config.json by hand:
rhino-mcp install # auto-detect launcher (uvx / rhino-mcp / python)
rhino-mcp install --mode bridge # force bridge mode in the env block
rhino-mcp install --force # overwrite an existing entry
rhino-mcp install --dry-run # preview the JSON without writingThe command writes a timestamped .bak.* copy of the existing config before saving, and is idempotent — re-running with the same flags is a no-op.
Restart Claude Desktop afterwards to pick up the change.
If the bridge plugin is loaded, type _McpInstall on the Rhino command line.
The command is non-interactive: it locates uvx / rhino-mcp / python on PATH (with ~/.local/bin, ~/.cargo/bin, /opt/homebrew/bin, and /usr/local/bin searched on macOS so the GUI-app PATH is enough), runs the same install pipeline as rhino-mcp install, and streams the outcome to the Rhino command line.
The MCP server is registered with --mode auto so Claude Desktop attaches to the bridge when Rhino is running and falls back to standalone (rhino3dm) when it is not.
docker compose -f docker/docker-compose.yml up --buildThe container exposes the server over Streamable HTTP on TCP :8765.
rhino-mcp is normally launched by an MCP client (Claude Desktop / Cursor / claude.ai connector) via the command + args you put into the client's config.
You can also run it manually for debugging.
| Use case | Command |
|---|---|
| Stdio (Claude Desktop default) | uvx rhino3dm-mcp or rhino-mcp |
| Streamable HTTP (local dev / Cursor) | rhino-mcp --transport http --host 127.0.0.1 --port 8765 |
| Streamable HTTP (claude.ai connector) | rhino-mcp --transport http --host 0.0.0.0 --port 8765 --allow-external --stateless |
Docker (HTTP on :8765) |
docker compose -f docker/docker-compose.yml up --build |
| Force standalone (no Rhino needed) | RHINO_MCP_FORCE_MODE=standalone rhino-mcp |
| Force bridge (fail fast if Rhino down) | RHINO_MCP_FORCE_MODE=bridge rhino-mcp |
| Force bridge with HTTP fallback | RHINO_MCP_FORCE_MODE=bridge RHINO_MCP_BRIDGE_OPTIONAL=1 rhino-mcp --transport http |
| Show all CLI flags | rhino-mcp --help |
The HTTP endpoint is http://<host>:<port>/mcp.
Use --allow-external only when exposing the server through ngrok / Cloudflare Tunnel / etc.
— it disables DNS-rebinding protection.
claude_desktop_config.json lives at:
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
Paste this entry under mcpServers:
{
"mcpServers": {
"rhino-mcp": {
"command": "uvx",
"args": ["rhino3dm-mcp"],
"env": {
"RHINO_MCP_TRANSPORT": "stdio",
"RHINO_HOST": "127.0.0.1",
"RHINO_PORT": "4242",
"RHINO_MCP_LOG_LEVEL": "INFO"
}
}
}
}Prefer a checkout?
Point command at uv and run from the repo:
{
"mcpServers": {
"rhino-mcp": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/rhino-mcp", "run", "rhino-mcp"],
"env": { "RHINO_MCP_LOG_LEVEL": "DEBUG" }
}
}
}Restart Claude Desktop; the rhino-mcp tools and the three strategy prompts (general_strategy, rhinoscript_workflow, viewport_workflow) appear in the palette.
Cursor reads ~/.cursor/mcp.json (global) or <project>/.cursor/mcp.json (per-project):
{
"mcpServers": {
"rhino-mcp": {
"command": "uvx",
"args": ["rhino3dm-mcp"],
"env": { "RHINO_MCP_FORCE_MODE": "bridge" }
}
}
}-
Run the server with HTTP + external access:
rhino-mcp --transport http --host 0.0.0.0 --port 8765 --allow-external --stateless
-
Tunnel it (e.g.
ngrok http 8765) and grab the public HTTPS URL. -
In claude.ai → Settings → Connectors → Add custom connector, set the URL to
https://<your-tunnel>/mcp.
Any MCP-compatible client (mcp-inspector, Continue, Claude Code, etc.) that can spawn a subprocess will work — give it uvx rhino3dm-mcp or the path to your rhino-mcp entry-point and inherit env vars from the table below.
Build and install the C# plugin for full 235 tool support.
The included helper script wraps dotnet build and verifies the post-build install location:
./scripts/build-plugin.sh # debug build + install
./scripts/build-plugin.sh --release # release build
./scripts/build-plugin.sh --clean # clean + rebuild
# Or invoke dotnet directly:
dotnet build rhino_plugin/csharp/RhinoMCPPlugin.csproj -c ReleasePost-build targets copy the .rhp:
| OS | Path |
|---|---|
| macOS | /Applications/Rhino 8.app/Contents/PlugIns/rhino-mcp.rhp |
| Windows | %APPDATA%/McNeel/Rhinoceros/8.0/Plug-ins/rhino-mcp/rhino-mcp.rhp |
Restart Rhino 8 — the bridge starts automatically on load (TCP :4242 by default).
Then restart rhino-mcp (or set RHINO_MCP_FORCE_MODE=bridge) and the bridge-only tools become available.
# After "rhino-mcp" is configured in Claude Desktop, ask Claude:
# "Create a 10-unit sphere at the origin and save the document to /tmp/demo.3dm"
# Behind the scenes Claude calls rhino_sphere → rhino_save.Three MCP prompts are registered automatically and surfaced in the client's prompt picker:
| Prompt | When to use |
|---|---|
general_strategy |
Decision tree for orienting in a document → choosing the right tool → best practices. |
rhinoscript_workflow |
Mandatory steps before invoking rhino_execute_python so the LLM doesn't hallucinate APIs. |
viewport_workflow |
How to combine rhino_zoom_extent + rhino_screenshot(as_base64=True) for visual checks. |
Telling Claude "use the general_strategy prompt before you start" gives much better tool selection on long sessions.
"Build a 5x5x5 box on layer 'Demo', then take a 1280x720 base64 screenshot so you can confirm it landed."
Claude calls rhino_box → rhino_zoom_extent → rhino_screenshot(as_base64=True) and reads the inline PNG from the response (image_base64 field) on the next turn.
"List the first 50 curves on the 'Walls' layer, then select every object whose name matches 'Pillar_*' on layer 'Structure'."
rhino_list_objects returns a pagination block; iterate with offset += limit until has_more is false. rhino_object_select accepts name_pattern, layer, color, object_type, user_text, and deselect_first.
Standalone smoke run from the shell:
RHINO_MCP_FORCE_MODE=standalone uv run python - <<'PY'
import asyncio
from rhino_mcp.server import build_server
from rhino_mcp.utils.registry import Mode
mcp, count = build_server(runtime_mode=Mode.STANDALONE, bridge_client=None)
print(f"{count} tools registered")
sphere = asyncio.run(
mcp._tool_manager._tools["rhino_sphere"].run(
{"args": {"center": {"x": 0, "y": 0, "z": 0}, "radius": 10.0}}
)
)
print(sphere)
PY| Category | Standalone | + Bridge | Notable members |
|---|---|---|---|
| Geometry | 14 | 14 | rhino_point, rhino_circle, rhino_helix, rhino_nurbs_curve, rhino_rebuild_curve |
| Curves | 3 | 3 | rhino_curve_length, rhino_curve_point_at, rhino_curve_split |
| Solids | 5 | 10 | rhino_box, rhino_sphere, rhino_boolean_union (bridge) |
| Surfaces | 0 | 11 | rhino_loft, rhino_sweep1, rhino_revolve |
| Mesh | 1 | 8 | rhino_mesh_box, rhino_weld_mesh (bridge) |
| Transform | 9 | 11 | rhino_array_polar, rhino_orient, rhino_flow (bridge) |
| Annotation | 2 | 8 | rhino_text_dot, rhino_dimension_linear (bridge) |
| Layer / Object | 6 | 8 | rhino_layer_create, rhino_block_insert (bridge) |
| Material | 2 | 3 | rhino_material_create, rhino_render_viewport (bridge) |
| File I/O | 5 | 9 | rhino_save, rhino_export_step (bridge) |
| Analysis | 4 | 9 | rhino_volume, rhino_zebra (bridge) |
| Query | 7 | 8 | rhino_list_objects, rhino_document_summary, rhino_get_selected_objects (bridge) |
| Display | 0 | 5 | rhino_zoom_extent, rhino_named_view_save |
| Scripting | 0 | 2 | rhino_execute_python, rhino_execute_csharp |
| RS Docs | 4 | 4 | rhino_search_rhinoscript_functions, rhino_get_rhinoscript_docs |
| History | 0 | 2 | rhino_undo, rhino_redo |
| Batch | 0 | 1 | rhino_batch_modify |
| Deformation | 0 | 4 | rhino_bend, rhino_twist, rhino_taper, rhino_flow_along_curve |
| NURBS | 0 | 5 | rhino_rebuild_surface, rhino_unroll, rhino_surface_from_points |
| SubD | 0 | 2 | rhino_create_subd, rhino_subd_to_nurbs |
| Srf Match | 0 | 3 | rhino_match_surface, rhino_blend_surface_edges, rhino_merge_surfaces |
| Extraction | 0 | 4 | rhino_dup_edge, rhino_dup_border, rhino_isocurve, rhino_make2d |
| Ctrl Points | 0 | 2 | rhino_get_control_points, rhino_set_control_points |
| Paneling | 0 | 3 | rhino_panelize_surface, rhino_create_uv_grid, rhino_panel_frames |
| Grasshopper | 0 | 22 | gh_set_slider, gh_bake_to_rhino, gh_data_tree_get |
| Drawing | 2 | 5 | rhino_drawing_sheet_create, rhino_drawing_title_block_add, rhino_drawing_view_place (bridge) |
| Schedule | 5 | 5 | rhino_schedule_by_layer, rhino_schedule_by_user_text, rhino_object_quantity |
| Blocks | 3 | 5 | rhino_block_define, rhino_block_insert, rhino_block_explode (bridge) |
| Environment | 3 | 4 | rhino_sun_position, rhino_sun_path, rhino_shadow_project |
| Freeform | 8 | 11 | rhino_skin_from_sections, rhino_panel_curvature_classify, rhino_attractor_displace_points |
| BIM I/O | 1 | 4 | rhino_bim_metadata_set, rhino_export_ifc (bridge), rhino_import_ifc (bridge), rhino_export_gbxml (bridge) |
| Materials+ | 2 | 3 | rhino_material_preset_list, rhino_material_preset_create, rhino_environment_set (bridge) |
| Render | 0 | 5 | rhino_camera_set, rhino_light_add, rhino_render_setup, rhino_render_to_file, rhino_turntable_render |
| Daylight | 2 | 2 | rhino_direct_irradiance, rhino_daylight_factor |
Detailed signatures live in docs/en/tools-reference.md.
1. Start Rhino 8 → open Grasshopper → load your `.gh` definition.
2. Ensure the C# bridge plugin (`rhino-mcp.rhp`) is loaded in Rhino.
3. Set `RHINO_MCP_FORCE_MODE=bridge` and restart rhino-mcp.
4. From Claude:
- "Open /work/wing.gh, set the 'span' slider to 12.5, run, bake to layer 'Wing'."
- "Read the output of the 'Voronoi' component as a DataTree."
See docs/en/grasshopper-guide.md for canvas/component/cluster recipes.
Every knob is controlled by an environment variable. CLI flags override env where applicable.
| Variable | Default | Purpose |
|---|---|---|
RHINO_MCP_TRANSPORT |
stdio |
stdio or http (Streamable HTTP) |
RHINO_MCP_FORCE_MODE |
(auto) | Force standalone or bridge |
RHINO_MCP_BRIDGE_OPTIONAL |
0 |
When 1 and RHINO_MCP_FORCE_MODE=bridge, fall back to standalone if the bridge is unreachable (recommended for HTTP connectors). |
RHINO_MCP_TRANSPORT_KIND |
(auto) | Bridge transport selector: pipe / unix / tcp |
RHINO_HOST, RHINO_PORT |
localhost:4242 |
TCP transport endpoint |
RHINO_MCP_PIPE |
rhino_mcp |
Windows named-pipe name |
RHINO_MCP_SOCKET |
(XDG runtime) | Unix socket path override |
RHINO_MCP_BRIDGE_TIMEOUT |
1 (auto), 5 (forced) |
Seconds to wait for a bridge ping |
RHINO_MCP_RECONNECT_RETRIES |
1 |
Max reconnect attempts after a transient transport failure |
RHINO_MCP_RECONNECT_BASE_DELAY |
0.5 |
Base delay (seconds) for exponential backoff |
RHINO_MCP_RECONNECT_JITTER |
0.25 |
Symmetric jitter ratio (0 disables, useful for tests) |
RHINO_MCP_LOG_LEVEL |
INFO |
Stderr log level. Set DEBUG to see every bridge JSON-RPC call. |
git clone https://github.com/easehee/rhino-mcp.git
cd rhino-mcp
uv venv && source .venv/bin/activate
uv pip install -e '.[dev]'
# Tests + coverage
uv run pytest --cov=src/rhino_mcp
# Lint + type check
uv run ruff check src/ tests/
uv run mypy src/rhino_mcp
# Build the C# bridge plugin (auto-installs into Rhino on macOS/Windows)
./scripts/build-plugin.sh --release
# Wheel + sdist
./scripts/build.sh# Terminal A — start the server with verbose logging
RHINO_MCP_LOG_LEVEL=DEBUG rhino-mcp --transport http --port 8765
# Terminal B — list tools via the MCP inspector or a curl probe
npx @modelcontextprotocol/inspector http://127.0.0.1:8765/mcp| Symptom | Likely cause / remediation |
|---|---|
Cannot reach Rhino bridge. |
Start Rhino 8 with the C# bridge plugin loaded; verify RHINO_HOST/RHINO_PORT. |
| Bridge-only tool absent in Claude | Server is in standalone mode — load the bridge plugin or set RHINO_MCP_FORCE_MODE=bridge. |
Invalid parameter: axis (cylinder) |
Standalone supports only {x:0,y:0,z:1}; switch to bridge for arbitrary axes. |
non-3DM import error |
Standalone import is .3dm-only; use bridge mode for STEP/IGES/DXF. |
transport failure on tcp://... |
The bridge crashed or Rhino was closed — rhino-mcp will reconnect with exponential backoff (RHINO_MCP_RECONNECT_RETRIES). |
| Stdout corrupted under stdio transport | Always log to stderr; rhino-mcp does this — verify no third-party libraries print to stdout. |
image_base64 missing from screenshot |
Caller passed as_base64=False (default) or the screenshot file failed to write — check the summary.path and base64_error fields. |
| Pagination loops forever | Always advance offset by pagination.returned, not by limit; the last page may be partial. |
rhino_object_select returns 0 matches |
Filters AND together — try removing one filter at a time; the color filter only works on objects with ColorSource=ColorFromObject. |
More in docs/en/troubleshooting.md.
MIT — see LICENSE.