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.
- rhinoclaw-0.7.0/PKG-INFO +108 -0
- rhinoclaw-0.7.0/README.md +87 -0
- rhinoclaw-0.7.0/pyproject.toml +70 -0
- rhinoclaw-0.7.0/setup.cfg +4 -0
- rhinoclaw-0.7.0/src/rhinoclaw/__init__.py +154 -0
- rhinoclaw-0.7.0/src/rhinoclaw/config.py +134 -0
- rhinoclaw-0.7.0/src/rhinoclaw/logging_setup.py +101 -0
- rhinoclaw-0.7.0/src/rhinoclaw/prompts/assert_general_strategy.py +25 -0
- rhinoclaw-0.7.0/src/rhinoclaw/server.py +437 -0
- rhinoclaw-0.7.0/src/rhinoclaw/static/gh_components.json +1 -0
- rhinoclaw-0.7.0/src/rhinoclaw/static/rhinoscriptsyntax.py +14642 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/add_light.py +134 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/array_linear.py +78 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/array_polar.py +82 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/assign_material_to_layer.py +20 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/bake_grasshopper.py +115 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/batch_operations.py +106 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/boolean_operation.py +71 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/build_and_bake_gh.py +108 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/build_and_bake_recipe.py +128 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/build_gh_definition.py +91 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/build_gh_interactive.py +304 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/capture_viewport.py +111 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/chamfer_curves.py +92 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/check_setup.py +189 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/copy_object.py +61 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_angular_dimension.py +89 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_block.py +70 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_group.py +59 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_layer.py +49 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_leader.py +72 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_linear_dimension.py +79 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_material.py +34 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_object.py +138 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_objects.py +93 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_radial_dimension.py +75 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_text.py +65 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/create_text_dot.py +62 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/delete_layer.py +48 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/delete_object.py +33 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/deploy_gh_to_compute.py +348 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/erp_bridge.py +67 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/execute_python3_code.py +100 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/execute_rhinoscript_python_code.py +54 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/explode_block.py +54 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/export_file.py +92 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/export_mesh.py +73 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/extrude_curve.py +87 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/fillet_curves.py +77 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/find_gh_component.py +134 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/find_nearby.py +70 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/find_objects.py +95 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_auth_status.py +83 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_command_history.py +56 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_document_info.py +23 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_grasshopper_outputs.py +113 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_logs.py +63 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_object_info.py +39 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_object_properties.py +63 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_objects_info.py +57 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_or_set_current_layer.py +48 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_relationships.py +71 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_rhinoscript_python_code_guide.py +28 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_rhinoscript_python_function_names.py +58 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_selected_objects_info.py +22 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_session_stats.py +84 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/get_ui_state.py +183 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/grasshopper_interactive.py +328 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/hello.py +46 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/import_mesh.py +77 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/insert_block.py +81 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/inspect_grasshopper_definition.py +97 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/is_inside.py +68 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/judge_door_placement.py +218 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/list_capabilities.py +46 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/list_grasshopper_definitions.py +70 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/load_grasshopper_definition.py +106 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/loft_curves.py +68 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/log_thought.py +15 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/mesh_from_brep.py +64 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/mirror_object.py +71 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/modify_object.py +74 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/modify_objects.py +49 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/offset_curve.py +77 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/open_file.py +54 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/orbit_camera.py +72 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/ping.py +22 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/place_doors.py +117 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/preflight.py +156 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/recall_placements.py +82 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/redo.py +40 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/render_view.py +80 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/revolve_curve.py +89 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/run_grasshopper.py +55 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/run_native_command.py +68 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/save_file.py +52 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/scene_summary.py +50 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/select_objects.py +64 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/set_camera.py +74 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/set_debug_mode.py +17 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/set_grasshopper_parameter.py +102 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/set_object_properties.py +88 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/set_render_settings.py +93 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/set_view.py +46 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/solve_grasshopper.py +96 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/stream_commands.py +497 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/subscribe_events.py +111 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/undo.py +43 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/ungroup.py +55 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/unload_grasshopper_definition.py +76 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/validate_gh_definition.py +63 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/visualarq.py +433 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/wait_for_object_event.py +125 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/zoom_extents.py +43 -0
- rhinoclaw-0.7.0/src/rhinoclaw/tools/zoom_selected.py +50 -0
- rhinoclaw-0.7.0/src/rhinoclaw/transport/wire.py +154 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/door_batch.py +154 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/door_judge.py +168 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/errors.py +67 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/gh_critic.py +188 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/gh_lint.py +212 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/gh_player.py +416 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/gh_recipes.py +209 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/interaction_logger.py +333 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/recipe_distiller.py +108 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/responses.py +43 -0
- rhinoclaw-0.7.0/src/rhinoclaw/utils/visualarq.py +76 -0
- rhinoclaw-0.7.0/src/rhinoclaw/websocket_client.py +639 -0
- rhinoclaw-0.7.0/src/rhinoclaw.egg-info/PKG-INFO +108 -0
- rhinoclaw-0.7.0/src/rhinoclaw.egg-info/SOURCES.txt +197 -0
- rhinoclaw-0.7.0/src/rhinoclaw.egg-info/dependency_links.txt +1 -0
- rhinoclaw-0.7.0/src/rhinoclaw.egg-info/entry_points.txt +2 -0
- rhinoclaw-0.7.0/src/rhinoclaw.egg-info/requires.txt +8 -0
- rhinoclaw-0.7.0/src/rhinoclaw.egg-info/top_level.txt +1 -0
- rhinoclaw-0.7.0/tests/test_add_light.py +74 -0
- rhinoclaw-0.7.0/tests/test_batch_operations.py +171 -0
- rhinoclaw-0.7.0/tests/test_boolean_operation.py +174 -0
- rhinoclaw-0.7.0/tests/test_build_and_bake_gh.py +142 -0
- rhinoclaw-0.7.0/tests/test_build_and_bake_recipe.py +101 -0
- rhinoclaw-0.7.0/tests/test_build_gh_definition.py +126 -0
- rhinoclaw-0.7.0/tests/test_build_gh_interactive.py +252 -0
- rhinoclaw-0.7.0/tests/test_capture_viewport.py +129 -0
- rhinoclaw-0.7.0/tests/test_check_setup.py +115 -0
- rhinoclaw-0.7.0/tests/test_connection.py +155 -0
- rhinoclaw-0.7.0/tests/test_create_block.py +126 -0
- rhinoclaw-0.7.0/tests/test_create_group.py +114 -0
- rhinoclaw-0.7.0/tests/test_create_object.py +218 -0
- rhinoclaw-0.7.0/tests/test_curve_operations.py +299 -0
- rhinoclaw-0.7.0/tests/test_deploy_gh_to_compute.py +297 -0
- rhinoclaw-0.7.0/tests/test_dimension_operations.py +152 -0
- rhinoclaw-0.7.0/tests/test_door_bench.py +57 -0
- rhinoclaw-0.7.0/tests/test_erp_bridge.py +82 -0
- rhinoclaw-0.7.0/tests/test_errors.py +59 -0
- rhinoclaw-0.7.0/tests/test_execute_python3_code.py +70 -0
- rhinoclaw-0.7.0/tests/test_explode_block.py +94 -0
- rhinoclaw-0.7.0/tests/test_export_mesh.py +115 -0
- rhinoclaw-0.7.0/tests/test_file_operations.py +218 -0
- rhinoclaw-0.7.0/tests/test_find_gh_component.py +70 -0
- rhinoclaw-0.7.0/tests/test_find_objects.py +97 -0
- rhinoclaw-0.7.0/tests/test_get_auth_status.py +95 -0
- rhinoclaw-0.7.0/tests/test_get_document_info.py +87 -0
- rhinoclaw-0.7.0/tests/test_get_objects_info.py +57 -0
- rhinoclaw-0.7.0/tests/test_get_ui_state.py +97 -0
- rhinoclaw-0.7.0/tests/test_gh_recipes.py +131 -0
- rhinoclaw-0.7.0/tests/test_grasshopper_api.py +562 -0
- rhinoclaw-0.7.0/tests/test_grasshopper_interactive.py +338 -0
- rhinoclaw-0.7.0/tests/test_group_operations.py +421 -0
- rhinoclaw-0.7.0/tests/test_import_mesh.py +101 -0
- rhinoclaw-0.7.0/tests/test_insert_block.py +121 -0
- rhinoclaw-0.7.0/tests/test_inspect_grasshopper_definition.py +319 -0
- rhinoclaw-0.7.0/tests/test_judge_door_placement.py +215 -0
- rhinoclaw-0.7.0/tests/test_list_capabilities.py +53 -0
- rhinoclaw-0.7.0/tests/test_mesh_from_brep.py +111 -0
- rhinoclaw-0.7.0/tests/test_modify_object.py +161 -0
- rhinoclaw-0.7.0/tests/test_object_properties.py +222 -0
- rhinoclaw-0.7.0/tests/test_place_doors.py +343 -0
- rhinoclaw-0.7.0/tests/test_preflight.py +112 -0
- rhinoclaw-0.7.0/tests/test_recall_placements.py +117 -0
- rhinoclaw-0.7.0/tests/test_render_view.py +75 -0
- rhinoclaw-0.7.0/tests/test_responses.py +78 -0
- rhinoclaw-0.7.0/tests/test_run_grasshopper.py +75 -0
- rhinoclaw-0.7.0/tests/test_run_native_command.py +67 -0
- rhinoclaw-0.7.0/tests/test_scene_analysis.py +232 -0
- rhinoclaw-0.7.0/tests/test_set_camera.py +66 -0
- rhinoclaw-0.7.0/tests/test_set_render_settings.py +73 -0
- rhinoclaw-0.7.0/tests/test_set_view.py +101 -0
- rhinoclaw-0.7.0/tests/test_stream_commands.py +143 -0
- rhinoclaw-0.7.0/tests/test_surface_operations.py +312 -0
- rhinoclaw-0.7.0/tests/test_tool_registration.py +61 -0
- rhinoclaw-0.7.0/tests/test_transform_operations.py +284 -0
- rhinoclaw-0.7.0/tests/test_transport_loopback.py +433 -0
- rhinoclaw-0.7.0/tests/test_undo_redo.py +115 -0
- rhinoclaw-0.7.0/tests/test_ungroup.py +95 -0
- rhinoclaw-0.7.0/tests/test_validate_gh_definition.py +102 -0
- rhinoclaw-0.7.0/tests/test_visualarq.py +139 -0
- rhinoclaw-0.7.0/tests/test_websocket_client.py +280 -0
- rhinoclaw-0.7.0/tests/test_wire.py +165 -0
- rhinoclaw-0.7.0/tests/test_zoom_extents.py +67 -0
- rhinoclaw-0.7.0/tests/test_zoom_selected.py +100 -0
rhinoclaw-0.7.0/PKG-INFO
ADDED
|
@@ -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,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
|
+
"""
|