rhinoclaw 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. rhinoclaw-0.7.0/PKG-INFO +108 -0
  2. rhinoclaw-0.7.0/README.md +87 -0
  3. rhinoclaw-0.7.0/pyproject.toml +70 -0
  4. rhinoclaw-0.7.0/setup.cfg +4 -0
  5. rhinoclaw-0.7.0/src/rhinoclaw/__init__.py +154 -0
  6. rhinoclaw-0.7.0/src/rhinoclaw/config.py +134 -0
  7. rhinoclaw-0.7.0/src/rhinoclaw/logging_setup.py +101 -0
  8. rhinoclaw-0.7.0/src/rhinoclaw/prompts/assert_general_strategy.py +25 -0
  9. rhinoclaw-0.7.0/src/rhinoclaw/server.py +437 -0
  10. rhinoclaw-0.7.0/src/rhinoclaw/static/gh_components.json +1 -0
  11. rhinoclaw-0.7.0/src/rhinoclaw/static/rhinoscriptsyntax.py +14642 -0
  12. rhinoclaw-0.7.0/src/rhinoclaw/tools/add_light.py +134 -0
  13. rhinoclaw-0.7.0/src/rhinoclaw/tools/array_linear.py +78 -0
  14. rhinoclaw-0.7.0/src/rhinoclaw/tools/array_polar.py +82 -0
  15. rhinoclaw-0.7.0/src/rhinoclaw/tools/assign_material_to_layer.py +20 -0
  16. rhinoclaw-0.7.0/src/rhinoclaw/tools/bake_grasshopper.py +115 -0
  17. rhinoclaw-0.7.0/src/rhinoclaw/tools/batch_operations.py +106 -0
  18. rhinoclaw-0.7.0/src/rhinoclaw/tools/boolean_operation.py +71 -0
  19. rhinoclaw-0.7.0/src/rhinoclaw/tools/build_and_bake_gh.py +108 -0
  20. rhinoclaw-0.7.0/src/rhinoclaw/tools/build_and_bake_recipe.py +128 -0
  21. rhinoclaw-0.7.0/src/rhinoclaw/tools/build_gh_definition.py +91 -0
  22. rhinoclaw-0.7.0/src/rhinoclaw/tools/build_gh_interactive.py +304 -0
  23. rhinoclaw-0.7.0/src/rhinoclaw/tools/capture_viewport.py +111 -0
  24. rhinoclaw-0.7.0/src/rhinoclaw/tools/chamfer_curves.py +92 -0
  25. rhinoclaw-0.7.0/src/rhinoclaw/tools/check_setup.py +189 -0
  26. rhinoclaw-0.7.0/src/rhinoclaw/tools/copy_object.py +61 -0
  27. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_angular_dimension.py +89 -0
  28. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_block.py +70 -0
  29. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_group.py +59 -0
  30. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_layer.py +49 -0
  31. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_leader.py +72 -0
  32. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_linear_dimension.py +79 -0
  33. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_material.py +34 -0
  34. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_object.py +138 -0
  35. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_objects.py +93 -0
  36. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_radial_dimension.py +75 -0
  37. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_text.py +65 -0
  38. rhinoclaw-0.7.0/src/rhinoclaw/tools/create_text_dot.py +62 -0
  39. rhinoclaw-0.7.0/src/rhinoclaw/tools/delete_layer.py +48 -0
  40. rhinoclaw-0.7.0/src/rhinoclaw/tools/delete_object.py +33 -0
  41. rhinoclaw-0.7.0/src/rhinoclaw/tools/deploy_gh_to_compute.py +348 -0
  42. rhinoclaw-0.7.0/src/rhinoclaw/tools/erp_bridge.py +67 -0
  43. rhinoclaw-0.7.0/src/rhinoclaw/tools/execute_python3_code.py +100 -0
  44. rhinoclaw-0.7.0/src/rhinoclaw/tools/execute_rhinoscript_python_code.py +54 -0
  45. rhinoclaw-0.7.0/src/rhinoclaw/tools/explode_block.py +54 -0
  46. rhinoclaw-0.7.0/src/rhinoclaw/tools/export_file.py +92 -0
  47. rhinoclaw-0.7.0/src/rhinoclaw/tools/export_mesh.py +73 -0
  48. rhinoclaw-0.7.0/src/rhinoclaw/tools/extrude_curve.py +87 -0
  49. rhinoclaw-0.7.0/src/rhinoclaw/tools/fillet_curves.py +77 -0
  50. rhinoclaw-0.7.0/src/rhinoclaw/tools/find_gh_component.py +134 -0
  51. rhinoclaw-0.7.0/src/rhinoclaw/tools/find_nearby.py +70 -0
  52. rhinoclaw-0.7.0/src/rhinoclaw/tools/find_objects.py +95 -0
  53. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_auth_status.py +83 -0
  54. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_command_history.py +56 -0
  55. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_document_info.py +23 -0
  56. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_grasshopper_outputs.py +113 -0
  57. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_logs.py +63 -0
  58. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_object_info.py +39 -0
  59. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_object_properties.py +63 -0
  60. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_objects_info.py +57 -0
  61. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_or_set_current_layer.py +48 -0
  62. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_relationships.py +71 -0
  63. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_rhinoscript_python_code_guide.py +28 -0
  64. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_rhinoscript_python_function_names.py +58 -0
  65. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_selected_objects_info.py +22 -0
  66. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_session_stats.py +84 -0
  67. rhinoclaw-0.7.0/src/rhinoclaw/tools/get_ui_state.py +183 -0
  68. rhinoclaw-0.7.0/src/rhinoclaw/tools/grasshopper_interactive.py +328 -0
  69. rhinoclaw-0.7.0/src/rhinoclaw/tools/hello.py +46 -0
  70. rhinoclaw-0.7.0/src/rhinoclaw/tools/import_mesh.py +77 -0
  71. rhinoclaw-0.7.0/src/rhinoclaw/tools/insert_block.py +81 -0
  72. rhinoclaw-0.7.0/src/rhinoclaw/tools/inspect_grasshopper_definition.py +97 -0
  73. rhinoclaw-0.7.0/src/rhinoclaw/tools/is_inside.py +68 -0
  74. rhinoclaw-0.7.0/src/rhinoclaw/tools/judge_door_placement.py +218 -0
  75. rhinoclaw-0.7.0/src/rhinoclaw/tools/list_capabilities.py +46 -0
  76. rhinoclaw-0.7.0/src/rhinoclaw/tools/list_grasshopper_definitions.py +70 -0
  77. rhinoclaw-0.7.0/src/rhinoclaw/tools/load_grasshopper_definition.py +106 -0
  78. rhinoclaw-0.7.0/src/rhinoclaw/tools/loft_curves.py +68 -0
  79. rhinoclaw-0.7.0/src/rhinoclaw/tools/log_thought.py +15 -0
  80. rhinoclaw-0.7.0/src/rhinoclaw/tools/mesh_from_brep.py +64 -0
  81. rhinoclaw-0.7.0/src/rhinoclaw/tools/mirror_object.py +71 -0
  82. rhinoclaw-0.7.0/src/rhinoclaw/tools/modify_object.py +74 -0
  83. rhinoclaw-0.7.0/src/rhinoclaw/tools/modify_objects.py +49 -0
  84. rhinoclaw-0.7.0/src/rhinoclaw/tools/offset_curve.py +77 -0
  85. rhinoclaw-0.7.0/src/rhinoclaw/tools/open_file.py +54 -0
  86. rhinoclaw-0.7.0/src/rhinoclaw/tools/orbit_camera.py +72 -0
  87. rhinoclaw-0.7.0/src/rhinoclaw/tools/ping.py +22 -0
  88. rhinoclaw-0.7.0/src/rhinoclaw/tools/place_doors.py +117 -0
  89. rhinoclaw-0.7.0/src/rhinoclaw/tools/preflight.py +156 -0
  90. rhinoclaw-0.7.0/src/rhinoclaw/tools/recall_placements.py +82 -0
  91. rhinoclaw-0.7.0/src/rhinoclaw/tools/redo.py +40 -0
  92. rhinoclaw-0.7.0/src/rhinoclaw/tools/render_view.py +80 -0
  93. rhinoclaw-0.7.0/src/rhinoclaw/tools/revolve_curve.py +89 -0
  94. rhinoclaw-0.7.0/src/rhinoclaw/tools/run_grasshopper.py +55 -0
  95. rhinoclaw-0.7.0/src/rhinoclaw/tools/run_native_command.py +68 -0
  96. rhinoclaw-0.7.0/src/rhinoclaw/tools/save_file.py +52 -0
  97. rhinoclaw-0.7.0/src/rhinoclaw/tools/scene_summary.py +50 -0
  98. rhinoclaw-0.7.0/src/rhinoclaw/tools/select_objects.py +64 -0
  99. rhinoclaw-0.7.0/src/rhinoclaw/tools/set_camera.py +74 -0
  100. rhinoclaw-0.7.0/src/rhinoclaw/tools/set_debug_mode.py +17 -0
  101. rhinoclaw-0.7.0/src/rhinoclaw/tools/set_grasshopper_parameter.py +102 -0
  102. rhinoclaw-0.7.0/src/rhinoclaw/tools/set_object_properties.py +88 -0
  103. rhinoclaw-0.7.0/src/rhinoclaw/tools/set_render_settings.py +93 -0
  104. rhinoclaw-0.7.0/src/rhinoclaw/tools/set_view.py +46 -0
  105. rhinoclaw-0.7.0/src/rhinoclaw/tools/solve_grasshopper.py +96 -0
  106. rhinoclaw-0.7.0/src/rhinoclaw/tools/stream_commands.py +497 -0
  107. rhinoclaw-0.7.0/src/rhinoclaw/tools/subscribe_events.py +111 -0
  108. rhinoclaw-0.7.0/src/rhinoclaw/tools/undo.py +43 -0
  109. rhinoclaw-0.7.0/src/rhinoclaw/tools/ungroup.py +55 -0
  110. rhinoclaw-0.7.0/src/rhinoclaw/tools/unload_grasshopper_definition.py +76 -0
  111. rhinoclaw-0.7.0/src/rhinoclaw/tools/validate_gh_definition.py +63 -0
  112. rhinoclaw-0.7.0/src/rhinoclaw/tools/visualarq.py +433 -0
  113. rhinoclaw-0.7.0/src/rhinoclaw/tools/wait_for_object_event.py +125 -0
  114. rhinoclaw-0.7.0/src/rhinoclaw/tools/zoom_extents.py +43 -0
  115. rhinoclaw-0.7.0/src/rhinoclaw/tools/zoom_selected.py +50 -0
  116. rhinoclaw-0.7.0/src/rhinoclaw/transport/wire.py +154 -0
  117. rhinoclaw-0.7.0/src/rhinoclaw/utils/door_batch.py +154 -0
  118. rhinoclaw-0.7.0/src/rhinoclaw/utils/door_judge.py +168 -0
  119. rhinoclaw-0.7.0/src/rhinoclaw/utils/errors.py +67 -0
  120. rhinoclaw-0.7.0/src/rhinoclaw/utils/gh_critic.py +188 -0
  121. rhinoclaw-0.7.0/src/rhinoclaw/utils/gh_lint.py +212 -0
  122. rhinoclaw-0.7.0/src/rhinoclaw/utils/gh_player.py +416 -0
  123. rhinoclaw-0.7.0/src/rhinoclaw/utils/gh_recipes.py +209 -0
  124. rhinoclaw-0.7.0/src/rhinoclaw/utils/interaction_logger.py +333 -0
  125. rhinoclaw-0.7.0/src/rhinoclaw/utils/recipe_distiller.py +108 -0
  126. rhinoclaw-0.7.0/src/rhinoclaw/utils/responses.py +43 -0
  127. rhinoclaw-0.7.0/src/rhinoclaw/utils/visualarq.py +76 -0
  128. rhinoclaw-0.7.0/src/rhinoclaw/websocket_client.py +639 -0
  129. rhinoclaw-0.7.0/src/rhinoclaw.egg-info/PKG-INFO +108 -0
  130. rhinoclaw-0.7.0/src/rhinoclaw.egg-info/SOURCES.txt +197 -0
  131. rhinoclaw-0.7.0/src/rhinoclaw.egg-info/dependency_links.txt +1 -0
  132. rhinoclaw-0.7.0/src/rhinoclaw.egg-info/entry_points.txt +2 -0
  133. rhinoclaw-0.7.0/src/rhinoclaw.egg-info/requires.txt +8 -0
  134. rhinoclaw-0.7.0/src/rhinoclaw.egg-info/top_level.txt +1 -0
  135. rhinoclaw-0.7.0/tests/test_add_light.py +74 -0
  136. rhinoclaw-0.7.0/tests/test_batch_operations.py +171 -0
  137. rhinoclaw-0.7.0/tests/test_boolean_operation.py +174 -0
  138. rhinoclaw-0.7.0/tests/test_build_and_bake_gh.py +142 -0
  139. rhinoclaw-0.7.0/tests/test_build_and_bake_recipe.py +101 -0
  140. rhinoclaw-0.7.0/tests/test_build_gh_definition.py +126 -0
  141. rhinoclaw-0.7.0/tests/test_build_gh_interactive.py +252 -0
  142. rhinoclaw-0.7.0/tests/test_capture_viewport.py +129 -0
  143. rhinoclaw-0.7.0/tests/test_check_setup.py +115 -0
  144. rhinoclaw-0.7.0/tests/test_connection.py +155 -0
  145. rhinoclaw-0.7.0/tests/test_create_block.py +126 -0
  146. rhinoclaw-0.7.0/tests/test_create_group.py +114 -0
  147. rhinoclaw-0.7.0/tests/test_create_object.py +218 -0
  148. rhinoclaw-0.7.0/tests/test_curve_operations.py +299 -0
  149. rhinoclaw-0.7.0/tests/test_deploy_gh_to_compute.py +297 -0
  150. rhinoclaw-0.7.0/tests/test_dimension_operations.py +152 -0
  151. rhinoclaw-0.7.0/tests/test_door_bench.py +57 -0
  152. rhinoclaw-0.7.0/tests/test_erp_bridge.py +82 -0
  153. rhinoclaw-0.7.0/tests/test_errors.py +59 -0
  154. rhinoclaw-0.7.0/tests/test_execute_python3_code.py +70 -0
  155. rhinoclaw-0.7.0/tests/test_explode_block.py +94 -0
  156. rhinoclaw-0.7.0/tests/test_export_mesh.py +115 -0
  157. rhinoclaw-0.7.0/tests/test_file_operations.py +218 -0
  158. rhinoclaw-0.7.0/tests/test_find_gh_component.py +70 -0
  159. rhinoclaw-0.7.0/tests/test_find_objects.py +97 -0
  160. rhinoclaw-0.7.0/tests/test_get_auth_status.py +95 -0
  161. rhinoclaw-0.7.0/tests/test_get_document_info.py +87 -0
  162. rhinoclaw-0.7.0/tests/test_get_objects_info.py +57 -0
  163. rhinoclaw-0.7.0/tests/test_get_ui_state.py +97 -0
  164. rhinoclaw-0.7.0/tests/test_gh_recipes.py +131 -0
  165. rhinoclaw-0.7.0/tests/test_grasshopper_api.py +562 -0
  166. rhinoclaw-0.7.0/tests/test_grasshopper_interactive.py +338 -0
  167. rhinoclaw-0.7.0/tests/test_group_operations.py +421 -0
  168. rhinoclaw-0.7.0/tests/test_import_mesh.py +101 -0
  169. rhinoclaw-0.7.0/tests/test_insert_block.py +121 -0
  170. rhinoclaw-0.7.0/tests/test_inspect_grasshopper_definition.py +319 -0
  171. rhinoclaw-0.7.0/tests/test_judge_door_placement.py +215 -0
  172. rhinoclaw-0.7.0/tests/test_list_capabilities.py +53 -0
  173. rhinoclaw-0.7.0/tests/test_mesh_from_brep.py +111 -0
  174. rhinoclaw-0.7.0/tests/test_modify_object.py +161 -0
  175. rhinoclaw-0.7.0/tests/test_object_properties.py +222 -0
  176. rhinoclaw-0.7.0/tests/test_place_doors.py +343 -0
  177. rhinoclaw-0.7.0/tests/test_preflight.py +112 -0
  178. rhinoclaw-0.7.0/tests/test_recall_placements.py +117 -0
  179. rhinoclaw-0.7.0/tests/test_render_view.py +75 -0
  180. rhinoclaw-0.7.0/tests/test_responses.py +78 -0
  181. rhinoclaw-0.7.0/tests/test_run_grasshopper.py +75 -0
  182. rhinoclaw-0.7.0/tests/test_run_native_command.py +67 -0
  183. rhinoclaw-0.7.0/tests/test_scene_analysis.py +232 -0
  184. rhinoclaw-0.7.0/tests/test_set_camera.py +66 -0
  185. rhinoclaw-0.7.0/tests/test_set_render_settings.py +73 -0
  186. rhinoclaw-0.7.0/tests/test_set_view.py +101 -0
  187. rhinoclaw-0.7.0/tests/test_stream_commands.py +143 -0
  188. rhinoclaw-0.7.0/tests/test_surface_operations.py +312 -0
  189. rhinoclaw-0.7.0/tests/test_tool_registration.py +61 -0
  190. rhinoclaw-0.7.0/tests/test_transform_operations.py +284 -0
  191. rhinoclaw-0.7.0/tests/test_transport_loopback.py +433 -0
  192. rhinoclaw-0.7.0/tests/test_undo_redo.py +115 -0
  193. rhinoclaw-0.7.0/tests/test_ungroup.py +95 -0
  194. rhinoclaw-0.7.0/tests/test_validate_gh_definition.py +102 -0
  195. rhinoclaw-0.7.0/tests/test_visualarq.py +139 -0
  196. rhinoclaw-0.7.0/tests/test_websocket_client.py +280 -0
  197. rhinoclaw-0.7.0/tests/test_wire.py +165 -0
  198. rhinoclaw-0.7.0/tests/test_zoom_extents.py +67 -0
  199. rhinoclaw-0.7.0/tests/test_zoom_selected.py +100 -0
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: rhinoclaw
3
+ Version: 0.7.0
4
+ Summary: Rhino integration through the Model Context Protocol
5
+ Author-email: Solid AI <info@solid-ai.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/McMuff86/rhinoclaw
8
+ Project-URL: Bug Tracker, https://github.com/McMuff86/rhinoclaw/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: fastmcp>=2.11.2
15
+ Requires-Dist: mcp[cli]>=1.12.4
16
+ Requires-Dist: websockets>=12.0
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
19
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
20
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
21
+
22
+ # RhinoClaw Server
23
+
24
+ Python MCP server that connects AI agents to Rhino 3D via the Model Context Protocol.
25
+
26
+ ## Overview
27
+
28
+ This server bridges AI agents (Claude, Cursor, etc.) to Rhino through:
29
+ - **FastMCP**: Python MCP implementation
30
+ - **TCP Socket**: Communication with Rhino plugin on `localhost:1999`
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ # Install uv (if not already installed)
36
+ # macOS: brew install uv
37
+ # Windows: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
38
+
39
+ # Install dependencies
40
+ cd rhinoclaw_server
41
+ uv pip install -e .
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ```bash
47
+ # Development mode
48
+ uv run python -m rhinoclaw
49
+
50
+ # Or via uvx (installed)
51
+ uvx rhinoclaw
52
+ ```
53
+
54
+ **Note:** Start the Rhino plugin first (`mcpstart` in Rhino command line).
55
+
56
+ ## Project Structure
57
+
58
+ ```
59
+ rhinoclaw_server/
60
+ ├── src/rhinoclaw/
61
+ │ ├── server.py # Main server, FastMCP setup
62
+ │ ├── tools/ # MCP tool implementations
63
+ │ │ ├── create_object.py
64
+ │ │ ├── modify_object.py
65
+ │ │ ├── get_document_info.py
66
+ │ │ └── ...
67
+ │ ├── utils/ # Helper utilities
68
+ │ └── prompts/ # AI prompts
69
+ ├── dev/ # Development scripts
70
+ │ ├── dev_test.py # Basic testing
71
+ │ └── dev_castle.py # Example: castle generation
72
+ └── pyproject.toml # Package configuration
73
+ ```
74
+
75
+ ## Available Tools
76
+
77
+ For the complete and up-to-date tool list, see [USAGE.md](../docs/USAGE.md).
78
+
79
+ **Categories:**
80
+ - **System**: ping, get_document_info, get_logs, set_debug_mode
81
+ - **Object Creation**: create_object, create_objects, create_text, create_text_dot
82
+ - **Object Manipulation**: modify_object, delete_object, select_objects, get_object_properties, set_object_properties
83
+ - **Boolean Operations**: boolean_operation (union, difference, intersection)
84
+ - **Transform**: copy_object, mirror_object, array_linear, array_polar
85
+ - **Curves**: offset_curve, fillet_curves, chamfer_curves
86
+ - **Surfaces**: loft_curves, extrude_curve, revolve_curve
87
+ - **Dimensions**: create_linear_dimension, create_angular_dimension, create_radial_dimension
88
+ - **Layers & Materials**: create_layer, create_material, assign_material_to_layer
89
+ - **Scripts**: execute_rhinoscript_python_code
90
+
91
+ ## Development
92
+
93
+ ```bash
94
+ # Run tests
95
+ uv run python dev/dev_test.py
96
+
97
+ # Build package
98
+ uv build
99
+
100
+ # Publish to PyPI
101
+ uv publish
102
+ ```
103
+
104
+ ## Documentation
105
+
106
+ - [Main README](../README.md) - Installation and usage
107
+ - [AGENTS.md](../AGENTS.md) - Agent-focused guide
108
+ - [USAGE.md](../docs/USAGE.md) - Tool reference and usage examples
@@ -0,0 +1,87 @@
1
+ # RhinoClaw Server
2
+
3
+ Python MCP server that connects AI agents to Rhino 3D via the Model Context Protocol.
4
+
5
+ ## Overview
6
+
7
+ This server bridges AI agents (Claude, Cursor, etc.) to Rhino through:
8
+ - **FastMCP**: Python MCP implementation
9
+ - **TCP Socket**: Communication with Rhino plugin on `localhost:1999`
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ # Install uv (if not already installed)
15
+ # macOS: brew install uv
16
+ # Windows: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
17
+
18
+ # Install dependencies
19
+ cd rhinoclaw_server
20
+ uv pip install -e .
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```bash
26
+ # Development mode
27
+ uv run python -m rhinoclaw
28
+
29
+ # Or via uvx (installed)
30
+ uvx rhinoclaw
31
+ ```
32
+
33
+ **Note:** Start the Rhino plugin first (`mcpstart` in Rhino command line).
34
+
35
+ ## Project Structure
36
+
37
+ ```
38
+ rhinoclaw_server/
39
+ ├── src/rhinoclaw/
40
+ │ ├── server.py # Main server, FastMCP setup
41
+ │ ├── tools/ # MCP tool implementations
42
+ │ │ ├── create_object.py
43
+ │ │ ├── modify_object.py
44
+ │ │ ├── get_document_info.py
45
+ │ │ └── ...
46
+ │ ├── utils/ # Helper utilities
47
+ │ └── prompts/ # AI prompts
48
+ ├── dev/ # Development scripts
49
+ │ ├── dev_test.py # Basic testing
50
+ │ └── dev_castle.py # Example: castle generation
51
+ └── pyproject.toml # Package configuration
52
+ ```
53
+
54
+ ## Available Tools
55
+
56
+ For the complete and up-to-date tool list, see [USAGE.md](../docs/USAGE.md).
57
+
58
+ **Categories:**
59
+ - **System**: ping, get_document_info, get_logs, set_debug_mode
60
+ - **Object Creation**: create_object, create_objects, create_text, create_text_dot
61
+ - **Object Manipulation**: modify_object, delete_object, select_objects, get_object_properties, set_object_properties
62
+ - **Boolean Operations**: boolean_operation (union, difference, intersection)
63
+ - **Transform**: copy_object, mirror_object, array_linear, array_polar
64
+ - **Curves**: offset_curve, fillet_curves, chamfer_curves
65
+ - **Surfaces**: loft_curves, extrude_curve, revolve_curve
66
+ - **Dimensions**: create_linear_dimension, create_angular_dimension, create_radial_dimension
67
+ - **Layers & Materials**: create_layer, create_material, assign_material_to_layer
68
+ - **Scripts**: execute_rhinoscript_python_code
69
+
70
+ ## Development
71
+
72
+ ```bash
73
+ # Run tests
74
+ uv run python dev/dev_test.py
75
+
76
+ # Build package
77
+ uv build
78
+
79
+ # Publish to PyPI
80
+ uv publish
81
+ ```
82
+
83
+ ## Documentation
84
+
85
+ - [Main README](../README.md) - Installation and usage
86
+ - [AGENTS.md](../AGENTS.md) - Agent-focused guide
87
+ - [USAGE.md](../docs/USAGE.md) - Tool reference and usage examples
@@ -0,0 +1,70 @@
1
+ [project]
2
+ name = "rhinoclaw"
3
+ version = "0.7.0"
4
+ description = "Rhino integration through the Model Context Protocol"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ authors = [
8
+ {name = "Solid AI", email = "info@solid-ai.ai"}
9
+ ]
10
+ license = {text = "MIT"}
11
+ classifiers = [
12
+ "Programming Language :: Python :: 3",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Operating System :: OS Independent",
15
+ ]
16
+ dependencies = [
17
+ "fastmcp>=2.11.2",
18
+ "mcp[cli]>=1.12.4",
19
+ "websockets>=12.0",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ dev = [
24
+ "pytest>=8.0.0",
25
+ "pytest-asyncio>=0.23.0",
26
+ "pytest-cov>=4.0.0",
27
+ ]
28
+
29
+ [project.scripts]
30
+ rhinoclaw = "rhinoclaw.server:main"
31
+
32
+ [build-system]
33
+ requires = ["setuptools>=61.0", "wheel"]
34
+ build-backend = "setuptools.build_meta"
35
+
36
+ [tool.setuptools]
37
+ package-dir = {"" = "src"}
38
+
39
+ [tool.setuptools.package-data]
40
+ # The GH component catalog (introspected ground truth for graph authoring)
41
+ # must ship inside the wheel — find_gh_component reads it at runtime.
42
+ rhinoclaw = ["static/*.json"]
43
+
44
+ [project.urls]
45
+ "Homepage" = "https://github.com/McMuff86/rhinoclaw"
46
+ "Bug Tracker" = "https://github.com/McMuff86/rhinoclaw/issues"
47
+
48
+ [tool.pytest.ini_options]
49
+ asyncio_mode = "auto"
50
+ asyncio_default_fixture_loop_scope = "function"
51
+
52
+ [tool.ruff]
53
+ line-length = 120
54
+ target-version = "py310"
55
+
56
+ [tool.ruff.lint]
57
+ # Keep ruleset conservative for now; expand (I, UP, B) once codebase settles.
58
+ select = ["E", "F", "W"]
59
+ ignore = [
60
+ "E501", # line too long — long docstring tables are common
61
+ "E701", # multiple statements on one line — used for compact param flattening
62
+ "W291", # trailing whitespace — pre-existing, will sweep separately
63
+ "W293", # blank line whitespace — pre-existing
64
+ ]
65
+
66
+ [tool.ruff.lint.per-file-ignores]
67
+ # __init__.py re-exports tool modules so FastMCP picks up @mcp.tool() decorators.
68
+ # Imports look "unused" but are required side-effects.
69
+ "src/rhinoclaw/__init__.py" = ["F401"]
70
+ "tests/**" = ["F401", "F841"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,154 @@
1
+ """Rhino integration through the Model Context Protocol."""
2
+
3
+ __version__ = "0.7.0"
4
+
5
+ # Expose key classes and functions for easier imports
6
+ from .prompts.assert_general_strategy import asset_general_strategy
7
+ from .server import RhinoConnection, get_rhino_connection, logger, mcp
8
+ from .static.rhinoscriptsyntax import rhinoscriptsyntax_json
9
+ from .tools.array_linear import array_linear
10
+ from .tools.array_polar import array_polar
11
+ from .tools.assign_material_to_layer import assign_material_to_layer
12
+ from .tools.batch_operations import batch_operations
13
+ from .tools.boolean_operation import boolean_operation
14
+ from .tools.build_and_bake_gh import build_and_bake_gh
15
+ from .tools.build_and_bake_recipe import build_and_bake_recipe
16
+ from .tools.build_gh_definition import build_gh_definition
17
+ from .tools.build_gh_interactive import build_gh_interactive
18
+ from .tools.chamfer_curves import chamfer_curves
19
+ from .tools.check_setup import rhinoclaw_doctor
20
+ from .tools.copy_object import copy_object
21
+ from .tools.create_angular_dimension import create_angular_dimension
22
+ from .tools.create_layer import create_layer
23
+ from .tools.create_leader import create_leader
24
+ from .tools.create_linear_dimension import create_linear_dimension
25
+ from .tools.create_material import create_material
26
+ from .tools.create_object import create_object
27
+ from .tools.create_objects import create_objects
28
+ from .tools.create_radial_dimension import create_radial_dimension
29
+ from .tools.create_text import create_text
30
+ from .tools.create_text_dot import create_text_dot
31
+ from .tools.delete_layer import delete_layer
32
+ from .tools.delete_object import delete_object
33
+ from .tools.deploy_gh_to_compute import deploy_gh_to_compute
34
+ from .tools.erp_bridge import erp_invoke, erp_list_tools
35
+ from .tools.execute_python3_code import execute_python3_code, get_script_capabilities
36
+ from .tools.execute_rhinoscript_python_code import execute_rhinoscript_python_code
37
+ from .tools.export_file import export_file
38
+ from .tools.export_mesh import export_mesh
39
+ from .tools.extrude_curve import extrude_curve
40
+ from .tools.fillet_curves import fillet_curves
41
+ from .tools.find_gh_component import find_gh_component
42
+ from .tools.find_nearby import find_nearby
43
+ from .tools.find_objects import find_objects
44
+ from .tools.get_auth_status import get_auth_status
45
+ from .tools.get_command_history import get_command_history
46
+ from .tools.hello import hello
47
+ from .tools.get_document_info import get_document_info
48
+ from .tools.judge_door_placement import judge_door_placement
49
+ from .tools.get_logs import clear_logs, get_logs
50
+ from .tools.get_object_info import get_object_info
51
+ from .tools.get_objects_info import get_objects_info
52
+ from .tools.get_relationships import get_relationships
53
+
54
+ # Object Properties
55
+ from .tools.get_object_properties import get_object_properties
56
+ from .tools.get_or_set_current_layer import get_or_set_current_layer
57
+ from .tools.get_rhinoscript_python_code_guide import get_rhinoscript_python_code_guide
58
+ from .tools.get_rhinoscript_python_function_names import (
59
+ get_rhinoscript_python_function_names,
60
+ )
61
+ from .tools.get_selected_objects_info import get_selected_objects_info
62
+ from .tools.get_session_stats import get_session_stats, new_session, set_logging_enabled
63
+ from .tools.get_ui_state import get_ui_state, wait_until_ready
64
+ from .tools.import_mesh import import_mesh
65
+ from .tools.loft_curves import loft_curves
66
+ from .tools.log_thought import log_thought
67
+ from .tools.mesh_from_brep import mesh_from_brep
68
+ from .tools.mirror_object import mirror_object
69
+ from .tools.modify_object import modify_object
70
+ from .tools.modify_objects import modify_objects
71
+ from .tools.offset_curve import offset_curve
72
+
73
+ # File Operations
74
+ from .tools.open_file import open_file
75
+ from .tools.ping import ping
76
+ from .tools.place_doors import place_doors
77
+ from .tools.preflight import preflight
78
+ from .tools.recall_placements import recall_placements
79
+ from .tools.redo import redo
80
+ from .tools.revolve_curve import revolve_curve
81
+ from .tools.run_native_command import run_native_command
82
+ from .tools.save_file import save_file
83
+ from .tools.select_objects import select_objects
84
+ from .tools.set_debug_mode import set_debug_mode, set_debug_mode_tool
85
+ from .tools.set_object_properties import set_object_properties
86
+ from .tools.set_render_settings import set_render_settings
87
+ from .tools.set_view import set_view
88
+ from .tools.set_camera import set_camera
89
+ from .tools.orbit_camera import orbit_camera
90
+ from .tools.zoom_extents import zoom_extents
91
+ from .tools.zoom_selected import zoom_selected
92
+ from .tools.capture_viewport import capture_viewport
93
+ from .tools.render_view import render_view
94
+ from .tools.add_light import add_light
95
+ from .tools.create_block import create_block
96
+ from .tools.create_group import create_group
97
+ from .tools.explode_block import explode_block
98
+ from .tools.insert_block import insert_block
99
+ from .tools.is_inside import is_inside
100
+ from .tools.list_capabilities import list_capabilities
101
+ from .tools.scene_summary import scene_summary
102
+ from .tools.subscribe_events import subscribe_events
103
+ from .tools.undo import undo
104
+ from .tools.ungroup import ungroup
105
+ from .tools.validate_gh_definition import validate_gh_definition
106
+ from .tools.wait_for_object_event import wait_for_object_event
107
+
108
+ # Grasshopper Operations
109
+ from .tools.bake_grasshopper import bake_grasshopper
110
+ from .tools.get_grasshopper_outputs import get_grasshopper_outputs
111
+ from .tools.inspect_grasshopper_definition import inspect_grasshopper_definition
112
+ from .tools.list_grasshopper_definitions import list_grasshopper_definitions
113
+ from .tools.load_grasshopper_definition import load_grasshopper_definition
114
+ from .tools.run_grasshopper import run_grasshopper
115
+ from .tools.set_grasshopper_parameter import set_grasshopper_parameter
116
+ from .tools.solve_grasshopper import solve_grasshopper
117
+ from .tools.unload_grasshopper_definition import unload_grasshopper_definition
118
+ from .tools.grasshopper_interactive import (
119
+ run_door_script,
120
+ run_grasshopper_interactive,
121
+ )
122
+
123
+ # VisualARQ BIM (graceful degradation without the plugin)
124
+ from .tools.visualarq import (
125
+ va_add_level,
126
+ va_create_door,
127
+ va_create_wall,
128
+ va_ifc_export,
129
+ va_ifc_import,
130
+ va_list_levels,
131
+ va_list_styles,
132
+ va_status,
133
+ )
134
+
135
+ # WebSocket Streaming (Real-Time Events)
136
+ from .tools.stream_commands import (
137
+ cancel_rhino_command,
138
+ clear_stream_buffer,
139
+ connect_rhino_stream,
140
+ disconnect_rhino_stream,
141
+ get_stream_events,
142
+ get_stream_status,
143
+ run_script_async,
144
+ send_rhino_input,
145
+ wait_for_prompt,
146
+ )
147
+
148
+ # WebSocket Client
149
+ from .websocket_client import (
150
+ RhinoWebSocketClient,
151
+ WebSocketEvent,
152
+ get_websocket_client,
153
+ reset_websocket_client,
154
+ )
@@ -0,0 +1,134 @@
1
+ """
2
+ Runtime configuration for the RhinoClaw MCP server.
3
+
4
+ All values can be overridden via environment variables prefixed with
5
+ ``RHINOCLAW_``. Stored as a frozen dataclass so the same instance can be
6
+ shared between threads without surprises.
7
+
8
+ Examples
9
+ --------
10
+ ::
11
+
12
+ RHINOCLAW_HOST=192.168.1.20 RHINOCLAW_PORT=1999 uvx rhinoclaw
13
+
14
+ ::
15
+
16
+ RHINOCLAW_AUTH_TOKEN=$(openssl rand -hex 16) uvx rhinoclaw
17
+
18
+ The defaults match the historical behaviour (loopback, port 1999, 15s
19
+ timeout, no auth) so existing setups keep working when the variables
20
+ are unset.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import os
26
+ from dataclasses import dataclass
27
+ from pathlib import Path
28
+ from typing import Optional
29
+
30
+ _TRUE_VALUES = {"1", "true", "yes", "on"}
31
+
32
+
33
+ def _env_str(name: str, default: str) -> str:
34
+ return os.environ.get(name, default)
35
+
36
+
37
+ def _env_int(name: str, default: int) -> int:
38
+ raw = os.environ.get(name)
39
+ if raw is None or raw == "":
40
+ return default
41
+ try:
42
+ return int(raw)
43
+ except ValueError:
44
+ return default
45
+
46
+
47
+ def _env_float(name: str, default: float) -> float:
48
+ raw = os.environ.get(name)
49
+ if raw is None or raw == "":
50
+ return default
51
+ try:
52
+ return float(raw)
53
+ except ValueError:
54
+ return default
55
+
56
+
57
+ def _env_bool(name: str, default: bool) -> bool:
58
+ raw = os.environ.get(name)
59
+ if raw is None:
60
+ return default
61
+ return raw.strip().lower() in _TRUE_VALUES
62
+
63
+
64
+ def _default_log_dir() -> Path:
65
+ """Pick a sane per-OS log directory, fall back to a project-local one."""
66
+ explicit = os.environ.get("RHINOCLAW_LOG_DIR")
67
+ if explicit:
68
+ return Path(explicit).expanduser()
69
+
70
+ if os.name == "nt":
71
+ base = os.environ.get("LOCALAPPDATA") or os.environ.get("APPDATA")
72
+ if base:
73
+ return Path(base) / "rhinoclaw" / "logs"
74
+ else:
75
+ xdg = os.environ.get("XDG_STATE_HOME")
76
+ if xdg:
77
+ return Path(xdg) / "rhinoclaw" / "logs"
78
+ home = os.environ.get("HOME")
79
+ if home:
80
+ return Path(home) / ".local" / "state" / "rhinoclaw" / "logs"
81
+
82
+ # Last-resort fallback: project-local (legacy behaviour).
83
+ return Path(__file__).resolve().parent.parent.parent / "logs"
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class Settings:
88
+ """Runtime configuration. Access via :func:`get_settings`."""
89
+
90
+ host: str
91
+ port: int
92
+ ws_port: int
93
+ timeout_seconds: float
94
+ max_timeout_seconds: float
95
+ debug: bool
96
+ auth_token: Optional[str]
97
+ log_format: str # "text" or "json"
98
+ log_dir: Path
99
+
100
+ @property
101
+ def auth_required(self) -> bool:
102
+ return bool(self.auth_token)
103
+
104
+ @classmethod
105
+ def from_env(cls) -> "Settings":
106
+ return cls(
107
+ host=_env_str("RHINOCLAW_HOST", "127.0.0.1"),
108
+ port=_env_int("RHINOCLAW_PORT", 1999),
109
+ ws_port=_env_int("RHINOCLAW_WS_PORT", 2000),
110
+ timeout_seconds=_env_float("RHINOCLAW_TIMEOUT", 15.0),
111
+ max_timeout_seconds=_env_float("RHINOCLAW_MAX_TIMEOUT", 120.0),
112
+ debug=_env_bool("RHINOCLAW_DEBUG", False),
113
+ auth_token=os.environ.get("RHINOCLAW_AUTH_TOKEN") or None,
114
+ log_format=_env_str("RHINOCLAW_LOG_FORMAT", "text").lower(),
115
+ log_dir=_default_log_dir(),
116
+ )
117
+
118
+
119
+ _cached: Optional[Settings] = None
120
+
121
+
122
+ def get_settings() -> Settings:
123
+ """Return the process-wide settings, loading from env on first call."""
124
+ global _cached
125
+ if _cached is None:
126
+ _cached = Settings.from_env()
127
+ return _cached
128
+
129
+
130
+ def reload_settings() -> Settings:
131
+ """Force re-read from environment. Useful for tests."""
132
+ global _cached
133
+ _cached = Settings.from_env()
134
+ return _cached
@@ -0,0 +1,101 @@
1
+ """
2
+ Logging configuration for RhinoClaw.
3
+
4
+ Two formats are supported:
5
+
6
+ * ``text`` (default) — human-readable, single-line, includes the
7
+ request_id when one is attached via ``logger.<level>(msg, extra={...})``.
8
+ * ``json`` — newline-delimited JSON for log shippers.
9
+
10
+ Switch via ``RHINOCLAW_LOG_FORMAT=json``. Once a process has called
11
+ :func:`configure_logging` the root logger is set up; subsequent calls
12
+ re-apply the format and level (handy for tests).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import logging
19
+ import sys
20
+ from datetime import datetime, timezone
21
+ from typing import TYPE_CHECKING
22
+
23
+ if TYPE_CHECKING:
24
+ from rhinoclaw.config import Settings
25
+
26
+
27
+ # Fields that the stdlib LogRecord always carries; everything else on the
28
+ # record dict is treated as user-supplied "extra" metadata and merged into
29
+ # the structured payload.
30
+ _STD_RECORD_FIELDS = frozenset({
31
+ "name", "msg", "args", "levelname", "levelno", "pathname", "filename",
32
+ "module", "exc_info", "exc_text", "stack_info", "lineno", "funcName",
33
+ "created", "msecs", "relativeCreated", "thread", "threadName",
34
+ "processName", "process", "message", "asctime", "taskName",
35
+ })
36
+
37
+
38
+ class _JsonFormatter(logging.Formatter):
39
+ def format(self, record: logging.LogRecord) -> str:
40
+ payload: dict[str, object] = {
41
+ "ts": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(timespec="milliseconds"),
42
+ "level": record.levelname,
43
+ "logger": record.name,
44
+ "message": record.getMessage(),
45
+ }
46
+
47
+ for key, value in record.__dict__.items():
48
+ if key in _STD_RECORD_FIELDS or key.startswith("_"):
49
+ continue
50
+ try:
51
+ json.dumps(value)
52
+ payload[key] = value
53
+ except (TypeError, ValueError):
54
+ payload[key] = repr(value)
55
+
56
+ if record.exc_info:
57
+ payload["exception"] = self.formatException(record.exc_info)
58
+
59
+ return json.dumps(payload, ensure_ascii=False)
60
+
61
+
62
+ class _TextFormatter(logging.Formatter):
63
+ """Human-readable formatter that surfaces the request_id when present."""
64
+
65
+ _BASE_FMT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
66
+
67
+ def __init__(self) -> None:
68
+ super().__init__(self._BASE_FMT)
69
+
70
+ def format(self, record: logging.LogRecord) -> str:
71
+ base = super().format(record)
72
+ request_id = getattr(record, "request_id", None)
73
+ if request_id:
74
+ return f"{base} [req={request_id}]"
75
+ return base
76
+
77
+
78
+ def _build_handler(log_format: str) -> logging.Handler:
79
+ handler = logging.StreamHandler(sys.stderr)
80
+ if log_format == "json":
81
+ handler.setFormatter(_JsonFormatter())
82
+ else:
83
+ handler.setFormatter(_TextFormatter())
84
+ return handler
85
+
86
+
87
+ def configure_logging(settings: "Settings") -> None:
88
+ """Configure the root logger according to ``settings``.
89
+
90
+ Idempotent — handlers installed by previous calls are removed.
91
+ """
92
+ root = logging.getLogger()
93
+ for existing in list(root.handlers):
94
+ # Only remove handlers we installed, identified by a marker attribute.
95
+ if getattr(existing, "_rhinoclaw_handler", False):
96
+ root.removeHandler(existing)
97
+
98
+ handler = _build_handler(settings.log_format)
99
+ handler._rhinoclaw_handler = True # type: ignore[attr-defined]
100
+ root.addHandler(handler)
101
+ root.setLevel(logging.DEBUG if settings.debug else logging.INFO)
@@ -0,0 +1,25 @@
1
+ from rhinoclaw.server import mcp
2
+
3
+
4
+ @mcp.prompt()
5
+ def asset_general_strategy() -> str:
6
+ """Defines the preferred strategy for creating assets in Rhino"""
7
+ return """
8
+
9
+ QUERY STRATEGY:
10
+ - if the id of the object is known, use the id to query the object.
11
+ - if the id is not known, use the name of the object to query the object.
12
+
13
+
14
+ CREATION STRATEGY:
15
+
16
+ 0. Before anything, always check the document from get_document_info().
17
+ 1. If the execute_rhinoscript_python_code() function is not able to create the objects, use the create_objects() function.
18
+ 2. If there are multiple objects, use the method create_objects() to create multiple objects at once. Do not attempt to create them one by one if they are more than 10.
19
+ 3. When including an object into document, ALWAYS make sure that the name of the object is meanful.
20
+ 4. Try to include as many objects as possible accurately and efficiently. If the command is not able to include so many data, try to create the objects in batches.
21
+
22
+ When creating rhinoscript python code:
23
+ - do not hallucinate, only use the syntax that is supported by rhinoscriptsyntax or Rhino.Geometry.
24
+ - double check the code if any of the code is not correct, and fix it.
25
+ """