#
tokens: 24875/50000 34/34 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .DS_Store
├── .env
├── .env.example
├── .gitignore
├── .python-version
├── examples
│   ├── compute_line.py
│   ├── rhinopython.py
│   ├── test_CL_GH.py
│   ├── test_CodeListener.py
│   ├── test_GH.py
│   ├── test_rhino3dm.3dm
│   ├── test_rhino3dm.3dmbak
│   ├── test_rhino3dm.py
│   ├── test01.gh
│   ├── test02.gh
│   ├── test03.gh
│   ├── zaha01.png
│   └── zaha01.py
├── grasshopper_mcp
│   ├── __init__.py
│   ├── .DS_Store
│   ├── config.py
│   ├── prompts
│   │   ├── __init__.py
│   │   ├── grasshopper_prompts.py
│   │   └── templates.py
│   ├── resources
│   │   ├── __init__.py
│   │   └── model_data.py
│   ├── rhino
│   │   ├── __init__.py
│   │   └── connection.py
│   ├── server.py
│   ├── tools
│   │   ├── __init__.py
│   │   ├── advanced_grasshopper.py
│   │   ├── analysis.py
│   │   ├── grasshopper.py
│   │   ├── modeling.py
│   │   └── rhino_code_gen.py
│   └── utils
│       ├── __init__.py
│       └── request.py
├── LICENSE
├── main.py
├── pyproject.toml
├── README.md
├── run_server.py
├── scripts
│   └── install.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
3.13

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv

```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
# Rhino/Grasshopper configuration
RHINO_PATH=C:/Program Files/Rhino 7/System
COMPUTE_API_KEY=your_compute_key_here
COMPUTE_URL=https://compute.rhino3d.com

# MCP server configuration
SERVER_NAME=Grasshopper MCP
SERVER_PORT=8080

```

--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------

```
# Rhino/Grasshopper configuration
USE_RHINO3DM=true # using Rhino3dm (for mac), switch to true
RHINO_PATH=C:/Program Files/Rhino 7/System
USE_COMPUTE_API=false
COMPUTE_API_KEY=your_compute_key_here
COMPUTE_URL=https://compute.rhino3d.com

# MCP server configuration
SERVER_NAME=Grasshopper_MCP
SERVER_PORT=8080




```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# GH_mcp_server

GH_mcp_server provides an approach that allows designer to interact with Rhino and Grasshopper directly via LLMs, including to analyse .3dm file, do 3D modeling and generate GHPython automatically in Grasshopper based on user’s guidance.

>  This project is **still under construction** — and we’d love your help!
>
> - Feel free to **open an issue** if you encounter bugs or have ideas.
> - Pull requests are always welcome.
> - If you're interested in collaborating long-term, feel free to reach out to [email protected] — we’d love to **have you on the team**!

![Alt text](examples/zaha01.png)

## Requirements

- Rhino 7 or 8

- Install `RhinoPython`: https://github.com/jingcheng-chen/RhinoPythonForVscode/tree/master?tab=readme-ov-file

- `uv`

  - ```
    # For MacOS and Linux
    curl -LsSf https://astral.sh/uv/install.sh | sh
    ```

  - ``````
    # For Windows
    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
    ``````

- Claude Desktop

## Installation

### 1. Clone the repository

```
git clone [email protected]:veoery/GH_mcp_server.git
cd GH_mcp_server
```

------

### 2. Set up the environment

We recommend using `uv`:

#### macOS/Linux

```
uv venv
source .venv/bin/activate
uv pip install -e .
```

#### Windows

```
uv venv
.venv\Scripts\activate
uv pip install -e .
```

> Make sure the virtual environment is activated before running or developing the project.

### 3. Configuration

1. In the Claude Desktop, Navigate to Settings->Developer. You will see ```Edit Config```.

2. Click the ```Edit Config``` and open the file ```claude_desktop_config.json```

3. Place the following code to the json file:
   ```python
   {
     "mcpServers": {
       "grasshopper": {
         "command": "path_to_GH_mcp_server/.venv/bin/python",
         "args": [
           "path_to_GH_mcp_server/run_server.py"
         ]
       }
     }
   }
   ```

4. Restart the Claude Desktop. If you are able to see a hammer icon, the configuration is successful. Click the hammer icon to check all the attached MCP tools.

## Usage

1. Start Rhino

2. Type command `CodeListener`. You should see `VS Code Listener Started...`.

3. Open the Claude Desktop and type the prompts to interact with GH_mcp_server tools. Please also check the file `examples\zaha01.gh` as a reference for interacting with Grasshopper. Here are some examples:

   ```
   Read the file "D:\test01.3dm" first and analyse the objects in this file.
   ```

   ```
   write GHpython to create a tower referring to zaha and write the ghpython code to "D:\zaha01.py"
   ```

​	
```

--------------------------------------------------------------------------------
/grasshopper_mcp/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/grasshopper_mcp/prompts/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/grasshopper_mcp/resources/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/grasshopper_mcp/rhino/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/grasshopper_mcp/tools/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/grasshopper_mcp/utils/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------

```python
def main():
    print("Hello from gh-mcp-server!")


if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/run_server.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python
import sys
import os

# Set up the Python path to find the package
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

# Import and run the server module
from grasshopper_mcp.server import main

if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/examples/test_CL_GH.py:
--------------------------------------------------------------------------------

```python
import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
import System
from Rhino.Geometry import *


def main():
    center = Point3d(10, 50, 0)
    circle = Circle(Plane.WorldXY, center, 20)
    return circle


if __name__ == "__main__":
    circle = main()

```

--------------------------------------------------------------------------------
/examples/rhinopython.py:
--------------------------------------------------------------------------------

```python
import rhinoscriptsyntax as rs
import ghpythonlib.components as ghcomp
import scriptcontext

#points = rs.GetPoints(True, True)
#if points:
#    curves = ghcomp.Voronoi(points)
#    for curve in curves:
#        scriptcontext.doc.Objects.AddCurve(curve)
#    for point in points:
#        scriptcontext.doc.Objects.AddPoint(point)
#    scriptcontext.doc.Views.Redraw()
    
p=ghcomp.ConstructPoint(0,0,0)
scriptcontext.doc.Objects.AddPoint(p)
#scriptcontext.doc.Views.Redraw()
```

--------------------------------------------------------------------------------
/examples/test_GH.py:
--------------------------------------------------------------------------------

```python
# Grasshopper Python Component: CreateCircle
# Generated from prompt: Create a circle with center point at coordinates (10,20,30) and radius of 50

import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino.Geometry as rg
import ghpythonlib.components as ghcomp
import math

# Create a circle based on prompt: Create a circle with center point at coordinates (10,20,30) and radius of 50
center = rg.Point3d(10, 20, 30)
circle = rg.Circle(rg.Plane.WorldXY, center, 50)
print("Created a circle!")

```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "grasshopper-mcp"
version = "0.1.0"
description = "MCP server for Grasshopper 3D modeling"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
dependencies = [
    "mcp>=1.4.0",
    "rhinoinside; platform_system=='Windows'",  # For Windows
    "rhino3dm>=7.15.0",                        
    "requests",
    "python-dotenv",
]

[project.optional-dependencies]
dev = [
    "pytest",
    "black",
    "isort",
]

[project.scripts]
grasshopper-mcp = "grasshopper_mcp.server:main"

```

--------------------------------------------------------------------------------
/grasshopper_mcp/prompts/templates.py:
--------------------------------------------------------------------------------

```python
from mcp.server.fastmcp import FastMCP
import mcp.types as types


def register_prompts(mcp: FastMCP) -> None:
    """Register prompt templates with the MCP server."""

    @mcp.prompt()
    def create_parametric_model(component_description: str) -> str:
        """Create a parametric model based on a description."""
        return f"""
Please help me create a parametric 3D model based on this description:

{component_description}

First, analyze what I want to build. Then generate the Python code to create this model using Rhino's geometry classes. Focus on creating a parametric design where key dimensions can be changed easily.
"""

```

--------------------------------------------------------------------------------
/examples/test_rhino3dm.py:
--------------------------------------------------------------------------------

```python
import rhino3dm

model = rhino3dm.File3dm()

# create geometry
sphere1 = rhino3dm.Sphere(rhino3dm.Point3d(0, 0, 0), 10)
sphere2 = rhino3dm.Sphere(rhino3dm.Point3d(10, 10, 10), 4)
geometry = (sphere1.ToBrep(), sphere2.ToBrep())

# create attributes
attr1 = rhino3dm.ObjectAttributes()
attr1.Name = "Sphere 1"
attr2 = rhino3dm.ObjectAttributes()
attr2.Name = "Sphere 2"
attributes = (attr1, attr2)
basepoint = rhino3dm.Point3d(0, 0, 0)

# create idef
index = model.InstanceDefinitions.Add("name", "description", "url", "urltag", basepoint, geometry, attributes)
print("Index of new idef: " + str(index))

# create iref
idef = model.InstanceDefinitions.FindIndex(index)
xf = rhino3dm.Transform(10.00)
iref = rhino3dm.InstanceReference(idef.Id, xf)
uuid = model.Objects.Add(iref, None)
print("id of new iref: " + str(uuid))

# save file
model.Write("./examples/test_rhino3dm.3dm", 7)

```

--------------------------------------------------------------------------------
/grasshopper_mcp/tools/rhino_code_gen.py:
--------------------------------------------------------------------------------

```python
from mcp.server.fastmcp import FastMCP
from typing import Dict, Optional, Any


def register_rhino_code_generation_tools(mcp: FastMCP) -> None:
    """Register code generation tools with the MCP server."""

    @mcp.tool()
    async def generate_rhino_code(prompt: str, parameters: Optional[Dict[str, Any]] = None) -> str:
        """Generate and execute Rhino Python code based on a description.

        Args:
            prompt: Description of what you want the code to do
            parameters: Optional parameters to use in the code generation

        Returns:
            Result of the operation
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.generate_and_execute_rhino_code(prompt, parameters)

        if result["result"] == "error":
            return f"Error generating or executing code: {result['error']}"

        return f"""Generated and executed code successfully:
{result['code']}"""

```

--------------------------------------------------------------------------------
/examples/compute_line.py:
--------------------------------------------------------------------------------

```python
import compute_rhino3d.Util
import compute_rhino3d.Grasshopper as gh
import rhino3dm
import json

compute_rhino3d.Util.url = "http://localhost:8000/"
# compute_rhino3d.Util.apiKey = ""

pt1 = rhino3dm.Point3d(0, 0, 0)
circle = rhino3dm.Circle(pt1, 5)
angle = 20

# convert circle to curve and stringify
curve = json.dumps(circle.ToNurbsCurve().Encode())

# create list of input trees
curve_tree = gh.DataTree("curve")
curve_tree.Append([0], [curve])
rotate_tree = gh.DataTree("rotate")
rotate_tree.Append([0], [angle])
trees = [curve_tree, rotate_tree]

output = gh.EvaluateDefinition("twisty.gh", trees)
print(output)

# decode results
branch = output["values"][0]["InnerTree"]["{0;0}"]
lines = [rhino3dm.CommonObject.Decode(json.loads(item["data"])) for item in branch]

filename = "twisty.3dm"

print("Writing {} lines to {}".format(len(lines), filename))

# create a 3dm file with results
model = rhino3dm.File3dm()
for l in lines:
    model.Objects.AddCurve(l)  # they're actually LineCurves...

model.Write(filename)

```

--------------------------------------------------------------------------------
/grasshopper_mcp/config.py:
--------------------------------------------------------------------------------

```python
import os
from dataclasses import dataclass
from typing import Optional


@dataclass
class ServerConfig:
    """Server configuration."""

    # Rhino/Grasshopper configuration
    rhino_path: Optional[str] = None  # Path to Rhino installation
    use_compute_api: bool = False  # Whether to use compute.rhino3d.com
    use_rhino3dm: bool = False  # Whether to use rhino3dm library
    compute_url: Optional[str] = None  # Compute API URL
    compute_api_key: Optional[str] = None  # Compute API key

    # Server configuration
    server_name: str = "Grasshopper MCP"
    server_port: int = 8080

    @classmethod
    def from_env(cls) -> "ServerConfig":
        """Create configuration from environment variables."""
        use_compute = os.getenv("USE_COMPUTE_API", "false").lower() == "true"
        use_rhino3dm = os.getenv("USE_RHINO3DM", "true").lower() == "true"

        return cls(
            rhino_path=os.getenv("RHINO_PATH"),
            use_compute_api=use_compute,
            use_rhino3dm=use_rhino3dm,
            compute_url=os.getenv("COMPUTE_URL"),
            compute_api_key=os.getenv("COMPUTE_API_KEY"),
            server_name=os.getenv("SERVER_NAME", "Grasshopper MCP"),
            server_port=int(os.getenv("SERVER_PORT", "8080")),
        )

```

--------------------------------------------------------------------------------
/grasshopper_mcp/prompts/grasshopper_prompts.py:
--------------------------------------------------------------------------------

```python
from mcp.server.fastmcp import FastMCP


def register_grasshopper_code_prompts(mcp: FastMCP) -> None:
    """Register prompt templates with the MCP server."""

    @mcp.prompt()
    def grasshopper_GHpython_generation_prompt(task_description: str) -> str:
        """Creates a prompt template for generating Grasshopper Python code with proper imports and grammar."""
        return """
When writing Python code for Grasshopper, please follow these guidelines:

0. Add "Used the prompts from mcp.prompt()" at the beginning of python file.

1. Always start by including the following import statements:
```python
import Rhino.Geometry as rg
import ghpythonlib.components as ghcomp
import rhinoscriptsyntax as rs

2. Structure your code with the following sections:
Import statements at the top
Global variables and constants
Function definitions if needed
Main execution code

3. Use descriptive variable names that follow Python naming conventions
Use snake_case for variables and functions
Use UPPER_CASE for constants

4. Include comments explaining complex logic or non-obvious operations
5. Carefully check grammar in all comments and docstrings
6. Ensure proper indentation and consistent code style
7. Use proper error handling when appropriate
8. Optimize for Grasshopper's data tree structure when handling multiple data items
9. Save the output to "result".
"""

```

--------------------------------------------------------------------------------
/grasshopper_mcp/utils/request.py:
--------------------------------------------------------------------------------

```python
import socket
import json
import tempfile
import os


def test_codelistener_with_file(host="127.0.0.1", port=614):
    """Test CodeListener by creating a temporary file and sending its path."""
    try:
        # Create a temporary Python file
        fd, temp_path = tempfile.mkstemp(suffix=".py")
        try:
            # Write Python code to the file
            with os.fdopen(fd, "w") as f:
                f.write(
                    """
import Rhino
import scriptcontext as sc

# Get Rhino version
version = Rhino.RhinoApp.Version
print("Hello from CodeListener!")
print("Rhino version: ", version)

# Access the active document
doc = sc.doc
if doc is not None:
    print("Active document: ", doc.Name)
else:
    print("No active document")
"""
                )

            # Create JSON message object
            msg_obj = {"filename": temp_path, "run": True, "reset": False, "temp": True}

            # Convert to JSON
            json_msg = json.dumps(msg_obj)

            # Connect to CodeListener
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(10)
            sock.connect((host, port))

            # Send the JSON message
            print(f"Sending request to execute file: {temp_path}")
            sock.sendall(json_msg.encode("utf-8"))

            # Receive response
            print("Waiting for response...")
            response = sock.recv(4096).decode("utf-8")
            print(f"Response received: {response}")

            sock.close()
            return True

        finally:
            # Clean up - remove temporary file
            try:
                os.unlink(temp_path)
            except:
                pass

    except Exception as e:
        print(f"Error: {e}")
        return False


# Run the test
if __name__ == "__main__":
    test_codelistener_with_file()

```

--------------------------------------------------------------------------------
/grasshopper_mcp/server.py:
--------------------------------------------------------------------------------

```python
from contextlib import asynccontextmanager
from dataclasses import dataclass
from typing import AsyncIterator
import os

from mcp.server.fastmcp import Context, FastMCP
from dotenv import load_dotenv

from grasshopper_mcp.rhino.connection import RhinoConnection
from grasshopper_mcp.config import ServerConfig


import sys

print("Rhino MCP Server starting up...", file=sys.stderr)

load_dotenv()  # Load environment variables from .env file


@dataclass
class AppContext:
    """Application context with initialized connections."""

    rhino: RhinoConnection
    config: ServerConfig


@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
    """Initialize and manage server resources."""
    # Load configuration
    config = ServerConfig.from_env()

    # Initialize Rhino/Grasshopper connection
    rhino_connection = RhinoConnection(config)
    try:
        await rhino_connection.initialize()

        # Provide context to request handlers
        yield AppContext(rhino=rhino_connection, config=config)
    finally:
        # Cleanup on shutdown
        await rhino_connection.close()


# Create the MCP server
mcp = FastMCP("Grasshopper 3D Modeling", lifespan=app_lifespan)

# Import tool definitions
from grasshopper_mcp.tools.modeling import register_modeling_tools
from grasshopper_mcp.tools.analysis import register_analysis_tools
from grasshopper_mcp.resources.model_data import register_model_resources
from grasshopper_mcp.tools.grasshopper import register_grasshopper_tools
from grasshopper_mcp.tools.advanced_grasshopper import register_advanced_grasshopper_tools
from grasshopper_mcp.tools.rhino_code_gen import register_rhino_code_generation_tools

# Import prompt definitions
from grasshopper_mcp.prompts.grasshopper_prompts import register_grasshopper_code_prompts

# Register tools
register_modeling_tools(mcp)
register_analysis_tools(mcp)
register_model_resources(mcp)

register_grasshopper_tools(mcp)

# register_advanced_grasshopper_tools(mcp)
# register_rhino_code_generation_tools(mcp)

# Register prompts
register_grasshopper_code_prompts(mcp)


def main():
    """Run the server."""
    mcp.run()


if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/scripts/install.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python3
"""
Helper script to install the Grasshopper MCP server in Claude Desktop.
"""
import os
import json
import platform
import argparse
import sys
from pathlib import Path


def get_config_path():
    """Get the path to the Claude Desktop config file."""
    if platform.system() == "Darwin":  # macOS
        return os.path.expanduser("~/Library/Application Support/Claude/claude_desktop_config.json")
    elif platform.system() == "Windows":
        return os.path.join(os.environ.get("APPDATA", ""), "Claude", "claude_desktop_config.json")
    else:
        print("Unsupported platform. Only macOS and Windows are supported.")
        sys.exit(1)


def main():
    parser = argparse.ArgumentParser(description="Install Grasshopper MCP server in Claude Desktop")
    parser.add_argument("--name", default="grasshopper", help="Name for the server in Claude Desktop")
    args = parser.parse_args()

    # Get the path to this script's directory
    script_dir = Path(__file__).parent.absolute()
    project_dir = script_dir.parent

    # Get the path to the server script
    server_script = project_dir / "grasshopper_mcp" / "server.py"

    if not server_script.exists():
        print(f"Server script not found at {server_script}")
        sys.exit(1)

    config_path = get_config_path()
    config_dir = os.path.dirname(config_path)

    # Create config directory if it doesn't exist
    os.makedirs(config_dir, exist_ok=True)

    # Load existing config or create new one
    if os.path.exists(config_path):
        with open(config_path, "r") as f:
            try:
                config = json.load(f)
            except json.JSONDecodeError:
                config = {}
    else:
        config = {}

    # Ensure mcpServers exists
    if "mcpServers" not in config:
        config["mcpServers"] = {}

    # Add our server
    python_path = sys.executable
    config["mcpServers"][args.name] = {"command": python_path, "args": [str(server_script)]}

    # Write updated config
    with open(config_path, "w") as f:
        json.dump(config, f, indent=2)

    print(f"Grasshopper MCP server installed as '{args.name}' in Claude Desktop")
    print(f"Configuration written to: {config_path}")


if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/examples/test_CodeListener.py:
--------------------------------------------------------------------------------

```python
# import os
# import sys

# print(1)


# scriptcontext_path = os.path.join(
#     os.environ["APPDATA"],
#     "McNeel",
#     "Rhinoceros",
#     "7.0",
#     "Plug-ins",
#     "IronPython (814d908a-e25c-493d-97e9-ee3861957f49)",
#     "settings",
#     "lib",
# )

# sys.path.append(scriptcontext_path)

# # import rhinoinside

# # rhinoinside.load()
# import rhinoscriptsyntax as rs
# import scriptcontext as sc
# import Rhino

################################################################################
# SampleAddNurbsCurves.py
# Copyright (c) 2017 Robert McNeel & Associates.
# See License.md in the root of this repository for details.
################################################################################
import Rhino
import scriptcontext


def SampleAddNurbsCurves():

    # World 3-D, or Euclidean, locations
    pt0 = Rhino.Geometry.Point3d(-8.0, -3.0, 0.0)
    pt1 = Rhino.Geometry.Point3d(-4.0, 3.0, 2.0)
    pt2 = Rhino.Geometry.Point3d(4.0, 3.0, 2.0)
    pt3 = Rhino.Geometry.Point3d(8.0, -3.0, 0.0)

    # Create NURBS curve:
    #   Dimension = 3
    #   Rational = False
    #   Order (Degree + 1) = 4
    #   Control point count = 4
    #   Knot count = Control point count + degree - 1
    nc0 = Rhino.Geometry.NurbsCurve(3, False, 4, 4)
    # World 3-D, or Euclidean, control points,
    nc0.Points[0] = Rhino.Geometry.ControlPoint(pt0)
    nc0.Points[1] = Rhino.Geometry.ControlPoint(pt1)
    nc0.Points[2] = Rhino.Geometry.ControlPoint(pt2)
    nc0.Points[3] = Rhino.Geometry.ControlPoint(pt3)
    # Clamped knots
    nc0.Knots[0] = 0
    nc0.Knots[1] = 0
    nc0.Knots[2] = 0
    nc0.Knots[3] = 1
    nc0.Knots[4] = 1
    nc0.Knots[5] = 1

    # Create NURBS curve:
    #   Dimension = 3
    #   Rational = True
    #   Order (Degree + 1) = 4
    #   Control point count = 4
    #   Knot count = Control point count + degree - 1
    nc1 = Rhino.Geometry.NurbsCurve(3, True, 4, 4)
    # Control points from a world 3-D, or Euclidean, locations and a weight
    nc1.Points[0] = Rhino.Geometry.ControlPoint(pt0, 1.0)
    nc1.Points[1] = Rhino.Geometry.ControlPoint(pt1, 2.0)
    nc1.Points[2] = Rhino.Geometry.ControlPoint(pt2, 4.0)
    nc1.Points[3] = Rhino.Geometry.ControlPoint(pt3, 1.0)
    # Clamped knots
    nc1.Knots[0] = 0
    nc1.Knots[1] = 0
    nc1.Knots[2] = 0
    nc1.Knots[3] = 1
    nc1.Knots[4] = 1
    nc1.Knots[5] = 1

    # Create NURBS curve:
    #   Dimension = 3
    #   Rational = True
    #   Order (Degree + 1) = 4
    #   Control point count = 4
    #   Knot count = Control point count + degree - 1
    nc2 = Rhino.Geometry.NurbsCurve(3, True, 4, 4)
    # Homogeneous control points
    nc2.Points[0] = Rhino.Geometry.ControlPoint(-8.0, -3.0, 0.0, 1.0)
    nc2.Points[1] = Rhino.Geometry.ControlPoint(-4.0, 3.0, 2.0, 2.0)
    nc2.Points[2] = Rhino.Geometry.ControlPoint(4.0, 3.0, 2.0, 4.0)
    nc2.Points[3] = Rhino.Geometry.ControlPoint(8.0, -3.0, 0.0, 1.0)
    # Clamped knots
    nc2.Knots[0] = 0
    nc2.Knots[1] = 0
    nc2.Knots[2] = 0
    nc2.Knots[3] = 1
    nc2.Knots[4] = 1
    nc2.Knots[5] = 1

    # Add to document
    scriptcontext.doc.Objects.Add(nc0)
    scriptcontext.doc.Objects.Add(nc1)
    scriptcontext.doc.Objects.Add(nc2)
    scriptcontext.doc.Views.Redraw()


# Check to see if this file is being executed as the "main" python
# script instead of being used as a module by some other python script
# This allows us to use the module which ever way we want.
if __name__ == "__main__":
    SampleAddNurbsCurves()


# rhino_path = "C:\\Program Files\\Rhino 7\\System"
# sys.path.append(rhino_path)
# # print(sys.path)
# # import scriptcontext as sc

# # import rhinoscriptsyntax as rs
# import rhinoinside

# rhinoinside.load()
# print("rhinoinside installed.")
# # Import Rhino components
# import Rhino

# print("Rhino installed.")

```

--------------------------------------------------------------------------------
/grasshopper_mcp/tools/analysis.py:
--------------------------------------------------------------------------------

```python
from mcp.server.fastmcp import FastMCP, Context


def register_analysis_tools(mcp: FastMCP) -> None:
    """Register analysis tools with the MCP server."""

    @mcp.tool()
    async def analyze_rhino_file(file_path: str) -> str:
        """Analyze a Rhino (.3dm) file.

        Args:
            file_path: Path to the .3dm file

        Returns:
            Analysis of the file contents
        """
        # Get context using the FastMCP mechanism
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.read_3dm_file(file_path)

        if result["result"] == "error":
            return f"Error: {result['error']}"

        model = result["model"]

        # Use r3d directly for rhino3dm mode
        if rhino.rhino_instance.get("use_rhino3dm", False):
            r3d = rhino.rhino_instance["r3d"]

            # Collect file information
            info = {
                "unit_system": str(model.Settings.ModelUnitSystem),
                "object_count": len(model.Objects),
                "layer_count": len(model.Layers),
            }

            # Get object types
            object_types = {}
            for obj in model.Objects:
                geom = obj.Geometry
                if geom:
                    geom_type = str(geom.ObjectType)
                    object_types[geom_type] = object_types.get(geom_type, 0) + 1

            info["object_types"] = object_types

            # Format output
            output = [f"Analysis of {file_path}:"]
            output.append(f"- Unit System: {info['unit_system']}")
            output.append(f"- Total Objects: {info['object_count']}")
            output.append(f"- Total Layers: {info['layer_count']}")
            output.append("- Object Types:")
            for obj_type, count in info["object_types"].items():
                output.append(f"  - {obj_type}: {count}")

            return "\n".join(output)
        else:
            # RhinoInside mode (Windows)
            # Similar implementation using Rhino SDK
            return "RhinoInside implementation not provided"

    @mcp.tool()
    async def list_objects(file_path: str) -> str:
        """List all objects in a Rhino file.

        Args:
            file_path: Path to the .3dm file

        Returns:
            Information about objects in the file
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.read_3dm_file(file_path)

        if result["result"] == "error":
            return f"Error: {result['error']}"

        model = result["model"]

        # Use rhino3dm for cross-platform support
        if rhino.rhino_instance.get("use_rhino3dm", False):
            # Gather object information
            objects_info = []
            for i, obj in enumerate(model.Objects):
                geom = obj.Geometry
                if geom:
                    attrs = obj.Attributes
                    name = attrs.Name or f"Object {i}"
                    layer_index = attrs.LayerIndex

                    # Get layer name if available
                    layer_name = "Unknown"
                    if 0 <= layer_index < len(model.Layers):
                        layer_name = model.Layers[layer_index].Name

                    obj_info = {"name": name, "type": str(geom.ObjectType), "layer": layer_name, "index": i}
                    objects_info.append(obj_info)

            # Format output
            output = [f"Objects in {file_path}:"]
            for info in objects_info:
                output.append(f"{info['index']}. {info['name']} (Type: {info['type']}, Layer: {info['layer']})")

            return "\n".join(output)
        else:
            # RhinoInside mode
            return "RhinoInside implementation not provided"

```

--------------------------------------------------------------------------------
/grasshopper_mcp/resources/model_data.py:
--------------------------------------------------------------------------------

```python
from mcp.server.fastmcp import FastMCP


def register_model_resources(mcp: FastMCP) -> None:
    """Register model data resources with the MCP server."""

    @mcp.resource("rhino://{file_path}")
    async def get_rhino_file_info(file_path: str) -> str:
        """Get information about a Rhino file."""
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.read_3dm_file(file_path)

        if result["result"] == "error":
            return f"Error: {result['error']}"

        model = result["model"]

        # Use rhino3dm
        if rhino.rhino_instance.get("use_rhino3dm", False):
            # Basic file information
            info = {
                "file_path": file_path,
                "unit_system": str(model.Settings.ModelUnitSystem),
                "object_count": len(model.Objects),
                "layer_count": len(model.Layers),
                "material_count": len(model.Materials),
                "notes": model.Notes or "No notes",
            }

            # Format output
            output = [f"# Rhino File: {file_path}"]
            output.append(f"- Unit System: {info['unit_system']}")
            output.append(f"- Objects: {info['object_count']}")
            output.append(f"- Layers: {info['layer_count']}")
            output.append(f"- Materials: {info['material_count']}")
            output.append(f"- Notes: {info['notes']}")

            return "\n".join(output)
        else:
            # RhinoInside mode
            return "RhinoInside implementation not provided"

    @mcp.resource("rhino://{file_path}/object/{index}")
    async def get_object_info(file_path: str, index: int) -> str:
        """Get information about a specific object in a Rhino file."""
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.read_3dm_file(file_path)

        if result["result"] == "error":
            return f"Error: {result['error']}"

        model = result["model"]
        index = int(index)  # Convert to integer

        # Check if index is valid
        if index < 0 or index >= len(model.Objects):
            return f"Error: Invalid object index. File has {len(model.Objects)} objects."

        # Use rhino3dm
        if rhino.rhino_instance.get("use_rhino3dm", False):
            r3d = rhino.rhino_instance["r3d"]
            obj = model.Objects[index]
            geom = obj.Geometry
            attrs = obj.Attributes

            # Basic object information
            info = {
                "name": attrs.Name or f"Object {index}",
                "type": str(geom.ObjectType),
                "layer_index": attrs.LayerIndex,
                "material_index": attrs.MaterialIndex,
                "visible": not attrs.IsHidden,
            }

            # Get bounding box
            bbox = geom.BoundingBox if hasattr(geom, "BoundingBox") else geom.GetBoundingBox()
            if bbox:
                info["bounding_box"] = {
                    "min": [bbox.Min.X, bbox.Min.Y, bbox.Min.Z],
                    "max": [bbox.Max.X, bbox.Max.Y, bbox.Max.Z],
                }

            # Type-specific properties
            if hasattr(geom, "ObjectType"):
                if geom.ObjectType == r3d.ObjectType.Curve:
                    info["length"] = geom.GetLength() if hasattr(geom, "GetLength") else "Unknown"
                    info["is_closed"] = geom.IsClosed if hasattr(geom, "IsClosed") else "Unknown"
                elif geom.ObjectType == r3d.ObjectType.Brep:
                    info["faces"] = len(geom.Faces) if hasattr(geom, "Faces") else "Unknown"
                    info["edges"] = len(geom.Edges) if hasattr(geom, "Edges") else "Unknown"
                    info["is_solid"] = geom.IsSolid if hasattr(geom, "IsSolid") else "Unknown"
                    info["volume"] = geom.GetVolume() if hasattr(geom, "GetVolume") else "Unknown"
                elif geom.ObjectType == r3d.ObjectType.Mesh:
                    info["vertices"] = len(geom.Vertices) if hasattr(geom, "Vertices") else "Unknown"
                    info["faces"] = len(geom.Faces) if hasattr(geom, "Faces") else "Unknown"

            # Format output
            output = [f"# Object {index}: {info['name']}"]
            output.append(f"- Type: {info['type']}")
            output.append(f"- Layer Index: {info['layer_index']}")
            output.append(f"- Material Index: {info['material_index']}")
            output.append(f"- Visible: {info['visible']}")

            if "bounding_box" in info:
                bbox = info["bounding_box"]
                output.append("- Bounding Box:")
                output.append(f"  - Min: ({bbox['min'][0]}, {bbox['min'][1]}, {bbox['min'][2]})")
                output.append(f"  - Max: ({bbox['max'][0]}, {bbox['max'][1]}, {bbox['max'][2]})")

            for key, value in info.items():
                if key not in ["name", "type", "layer_index", "material_index", "visible", "bounding_box"]:
                    output.append(f"- {key.replace('_', ' ').title()}: {value}")

            return "\n".join(output)
        else:
            # RhinoInside mode
            return "RhinoInside implementation not provided"

```

--------------------------------------------------------------------------------
/examples/zaha01.py:
--------------------------------------------------------------------------------

```python
import Rhino.Geometry as rg
import ghpythonlib.components as ghcomp
import math
import random

# === INPUT PARAMETERS ===
height = 200.0           # Total height of the tower
base_radius = 30.0       # Radius at the base of the tower
top_radius = 15.0        # Radius at the top of the tower
twist_angle = 75.0       # Total twist angle from base to top (degrees)
floors = 35              # Number of floors
curvature_factor = 0.3   # Factor controlling central spine curvature (0-1)
organic_factor = 0.4     # Factor controlling the organic deformation of floor plates (0-1)

# === OUTPUTS ===
tower_surfaces = []      # Collection of surfaces forming the tower
floor_curves = []        # Collection of floor plate curves
central_spine = None     # Central spine curve

# === HELPER FUNCTIONS ===
def create_organic_floor_curve(center, radius, segments, organic_factor, phase):
    """
    Creates an organic floor curve with controlled deformation.
    
    Args:
        center: Center point of the floor curve
        radius: Base radius of the floor curve
        segments: Number of segments for the curve (smoothness)
        organic_factor: Amount of organic deformation (0-1)
        phase: Phase shift for the organic deformation pattern
        
    Returns:
        A closed curve representing the floor shape
    """
    points = []
    for i in range(segments):
        angle = (math.pi * 2.0 * i) / segments
        
        # Create organic variation using multiple sine waves with different frequencies
        variation = 1.0 + organic_factor * (
            0.4 * math.sin(angle * 2 + phase) + 
            0.3 * math.sin(angle * 3 + phase * 1.7) +
            0.2 * math.sin(angle * 5 + phase * 0.8)
        )
        
        # Calculate point coordinates
        x = center.X + radius * variation * math.cos(angle)
        y = center.Y + radius * variation * math.sin(angle)
        point = rg.Point3d(x, y, center.Z)
        points.append(point)
    
    # Close the curve by adding the first point again
    points.append(points[0])
    
    # Create interpolated curve through points
    # Degree 3 for smooth, flowing curves characteristic of Zaha Hadid's work
    return rg.Curve.CreateInterpolatedCurve(points, 3)

def ease_in_out(t):
    """
    Provides a smooth ease-in-out interpolation.
    Used for more natural transitions characteristic of Hadid's fluid forms.
    
    Args:
        t: Input value (0-1)
        
    Returns:
        Eased value (0-1)
    """
    return 0.5 - 0.5 * math.cos(t * math.pi)

# === MAIN ALGORITHM ===

# 1. Create the central spine with a gentle S-curve (Hadid's sinuous forms)
spine_points = []
for i in range(floors + 1):
    # Calculate height position
    z = i * (height / floors)
    t = z / height  # Normalized height (0-1)
    
    # Create an S-curve using sine function
    # This creates the flowing, undulating central spine typical in Hadid's work
    curve_x = math.sin(t * math.pi) * base_radius * curvature_factor
    curve_y = math.sin(t * math.pi * 0.5) * base_radius * curvature_factor * 0.7
    
    spine_points.append(rg.Point3d(curve_x, curve_y, z))

# Create a smooth interpolated curve through the spine points
central_spine = rg.Curve.CreateInterpolatedCurve(spine_points, 3)

# 2. Create floor curves with organic shapes and twisting
for i in range(floors + 1):
    # Calculate height position
    z = i * (height / floors)
    t = z / height  # Normalized height (0-1)
    
    # Get point on spine at this height
    spine_param = central_spine.Domain.ParameterAt(t)
    center = central_spine.PointAt(spine_param)
    
    # Calculate radius with smooth transition from base to top
    # Using ease_in_out for more natural, fluid transition
    eased_t = ease_in_out(t)
    radius = base_radius * (1 - eased_t) + top_radius * eased_t
    
    # Add Hadid-like bulges at strategic points
    if 0.3 < t < 0.7:
        # Create a subtle bulge in the middle section
        bulge_factor = math.sin((t - 0.3) * math.pi / 0.4) * 0.15
        radius *= (1 + bulge_factor)
    
    # Calculate twist angle based on height
    angle_rad = math.radians(twist_angle * t)
    
    # Create a plane for the floor curve
    # First get the spine's tangent at this point
    tangent = central_spine.TangentAt(spine_param)
    tangent.Unitize()
    
    # Create perpendicular vectors for the plane
    x_dir = rg.Vector3d.CrossProduct(tangent, rg.Vector3d.ZAxis)
    if x_dir.Length < 0.001:
        x_dir = rg.Vector3d.XAxis
    x_dir.Unitize()
    
    y_dir = rg.Vector3d.CrossProduct(tangent, x_dir)
    y_dir.Unitize()
    
    # Apply twist rotation
    rotated_x = x_dir * math.cos(angle_rad) - y_dir * math.sin(angle_rad)
    rotated_y = x_dir * math.sin(angle_rad) + y_dir * math.cos(angle_rad)
    
    floor_plane = rg.Plane(center, rotated_x, rotated_y)
    
    # Phase shift creates variation in organic patterns between floors
    # This creates the flowing, continuous aesthetic of Hadid's work
    phase_shift = t * 8.0
    
    # Create organic floor curve
    segments = 24  # Number of segments for smoothness
    curve = create_organic_floor_curve(floor_plane.Origin, radius, segments, 
                                       organic_factor * (1 + 0.5 * math.sin(t * math.pi)), 
                                       phase_shift)
    
    floor_curves.append(curve)

# 3. Create surfaces between floor curves
for i in range(len(floor_curves) - 1):
    # Create loft surface between consecutive floors
    # Using Tight loft type for more fluid transitions
    loft_curves = [floor_curves[i], floor_curves[i+1]]
    loft_type = rg.LoftType.Tight
    
    try:
        # Create loft surfaces
        loft = ghcomp.Loft(loft_curves, loft_type)
        if isinstance(loft, list):
            tower_surfaces.extend(loft)
        else:
            tower_surfaces.append(loft)
    except:
        # Skip if loft creation fails
        pass

# === ASSIGN OUTPUTS ===
a = tower_surfaces  # Tower surfaces
b = floor_curves    # Floor curves
c = central_spine   # Central spine curve
```

--------------------------------------------------------------------------------
/grasshopper_mcp/tools/grasshopper.py:
--------------------------------------------------------------------------------

```python
from mcp.server.fastmcp import FastMCP
from typing import Dict, List, Any, Optional


def register_grasshopper_tools(mcp: FastMCP) -> None:
    """Register Grasshopper-specific tools with the MCP server."""

    #     @mcp.tool()
    #     async def generate_grasshopper_code(
    #         description: str,
    #         file_path: str,
    #         parameters: Dict[str, Any] = None,
    #         component_name: Optional[str] = None,
    #     ) -> str:
    #         """Generate Python code for a Grasshopper component based on a description.

    #         Args:
    #             description: Description of what the code should do
    #             file_path: Path where the generated code will be saved
    #             parameters: Dictionary of parameters to use in the code generation.
    #                         Can include the following keys:
    #                         - code_override: String containing complete code to use instead of generating
    #                         - center_x, center_y, center_z: Numeric values for geometric operations
    #                         - radius: Numeric value for circles or spheres
    #                         - width, height, depth: Dimensions for rectangular forms
    #                         - [Other commonly used parameters...]
    #             component_name: Optional name for the GH component

    #         Returns:
    #             Result of the operation including the file path to the generated code
    #         """
    #         ctx = mcp.get_context()
    #         rhino = ctx.request_context.lifespan_context.rhino

    #         result = await rhino.generate_and_execute_gh_code(description, file_path, parameters, component_name)

    #         if result["result"] == "error":
    #             return f"Error generating Grasshopper code: {result['error']}"

    #         return f"""Generated Grasshopper Python code successfully:
    # {result['code']}"""

    @mcp.tool()
    async def execute_grasshopper_code(code: str, file_path: str) -> str:
        """Execute given Python code.

        Args:
        code: The given code to execute
        file_path: Path where the generated code will be saved

        Returns:
            Result of the executing code
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.send_code_to_gh(code, file_path)

    @mcp.tool()
    async def add_grasshopper_component(
        component_name: str, component_type: str, parameters: Dict[str, Any]
    ) -> str:
        """Add a component from an existing Grasshopper plugin.

        Args:
            component_name: Name of the component
            component_type: Type/category of the component
            parameters: Component parameters and settings

        Returns:
            Result of the operation
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.add_gh_component(
            component_name=component_name, component_type=component_type, parameters=parameters
        )

        if result["result"] == "error":
            return f"Error adding component: {result['error']}"

        return f"""Successfully added Grasshopper component:
- Component: {component_name} ({component_type})
- Component ID: {result.get('component_id', 'Unknown')}
"""

    @mcp.tool()
    async def connect_grasshopper_components(
        source_id: str, source_param: str, target_id: str, target_param: str
    ) -> str:
        """Connect parameters between Grasshopper components.

        Args:
            source_id: Source component ID
            source_param: Source parameter name
            target_id: Target component ID
            target_param: Target parameter name

        Returns:
            Result of the operation
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.connect_gh_components(
            source_id=source_id, source_param=source_param, target_id=target_id, target_param=target_param
        )

        if result["result"] == "error":
            return f"Error connecting components: {result['error']}"

        return f"""Successfully connected Grasshopper components:
- Connected: {source_id}.{source_param} → {target_id}.{target_param}
"""

    @mcp.tool()
    async def run_grasshopper_definition(
        file_path: Optional[str] = None, save_output: bool = False, output_path: Optional[str] = None
    ) -> str:
        """Run a Grasshopper definition.

        Args:
            file_path: Path to the .gh file (or None for current definition)
            save_output: Whether to save the output
            output_path: Path to save the output (if save_output is True)

        Returns:
            Result of the operation
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.run_gh_definition(
            file_path=file_path, save_output=save_output, output_path=output_path
        )

        if result["result"] == "error":
            return f"Error running definition: {result['error']}"

        return f"""Successfully ran Grasshopper definition:
- Execution time: {result.get('execution_time', 'Unknown')} seconds
- Outputs: {result.get('output_summary', 'No output summary available')}
"""


async def generate_python_code(
    rhino_connection, description: str, inputs: List[Dict[str, Any]], outputs: List[Dict[str, Any]]
) -> Dict[str, Any]:
    """Generate Python code for Grasshopper based on description and parameters.

    This is a simplified implementation. In a production system,
    this might call an LLM or use templates.
    """
    # Build code header with imports
    code = "import Rhino.Geometry as rg\n"
    code += "import scriptcontext as sc\n"
    code += "import ghpythonlib.components as ghcomp\n\n"

    # Add description as comment
    code += f"# {description}\n\n"

    # Process inputs
    input_vars = []
    for i, inp in enumerate(inputs):
        var_name = inp["name"]
        input_vars.append(var_name)

        # Add comments about input parameters
        code += f"# Input: {var_name} ({inp['type']}) - {inp.get('description', '')}\n"

    code += "\n# Processing\n"

    # Add basic implementation based on description
    # This is where you might want to call an LLM or use more sophisticated templates
    if "circle" in description.lower():
        code += """if radius is not None:
    circle = rg.Circle(rg.Point3d(0, 0, 0), radius)
    circle = circle.ToNurbsCurve()
else:
    circle = None
"""
    elif "box" in description.lower():
        code += """if width is not None and height is not None and depth is not None:
    box = rg.Box(
        rg.Plane.WorldXY,
        rg.Interval(0, width),
        rg.Interval(0, height),
        rg.Interval(0, depth)
    )
else:
    box = None
"""
    else:
        # Generic code template
        code += "# Add your implementation here based on the description\n"
        code += "# Use the input parameters to generate the desired output\n\n"

    # Process outputs
    output_assignments = []
    for output in outputs:
        var_name = output["name"]
        # Assign a dummy value to each output
        output_assignments.append(f"{var_name} = {var_name}")

    # Add output assignments
    code += "\n# Outputs\n"
    code += "\n".join(output_assignments)

    return {"result": "success", "code": code}

```

--------------------------------------------------------------------------------
/grasshopper_mcp/tools/modeling.py:
--------------------------------------------------------------------------------

```python
from mcp.server.fastmcp import FastMCP


def register_modeling_tools(mcp: FastMCP) -> None:
    """Register geometry access tools with the MCP server."""

    @mcp.tool()
    async def extract_geometry(file_path: str, object_index: int) -> str:
        """Extract geometric data from an existing object.

        Args:
            file_path: Path to the .3dm file
            object_index: Index of the object to extract data from

        Returns:
            Geometric data in a readable format
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.read_3dm_file(file_path)

        if result["result"] == "error":
            return f"Error: {result['error']}"

        model = result["model"]

        # Check if index is valid
        try:
            index = int(object_index)
            if index < 0 or index >= len(model.Objects):
                return f"Error: Invalid object index. File has {len(model.Objects)} objects."
        except ValueError:
            return f"Error: Object index must be a number."

        # Extract geometry data using rhino3dm
        obj = model.Objects[index]
        geom = obj.Geometry

        if not geom:
            return f"Error: No geometry found for object at index {index}."

        r3d = rhino.rhino_instance["r3d"]

        # Extract data based on geometry type
        geometry_data = {"type": str(geom.ObjectType), "id": str(obj.Id) if hasattr(obj, "Id") else "Unknown"}

        # Get bounding box
        bbox = geom.GetBoundingBox() if hasattr(geom, "GetBoundingBox") else None
        if bbox:
            geometry_data["bounding_box"] = {
                "min": [bbox.Min.X, bbox.Min.Y, bbox.Min.Z],
                "max": [bbox.Max.X, bbox.Max.Y, bbox.Max.Z],
                "dimensions": [bbox.Max.X - bbox.Min.X, bbox.Max.Y - bbox.Min.Y, bbox.Max.Z - bbox.Min.Z],
            }

        # Type-specific data extraction
        if hasattr(geom, "ObjectType"):
            if geom.ObjectType == r3d.ObjectType.Point:
                point = geom.Location
                geometry_data["coordinates"] = [point.X, point.Y, point.Z]

            elif geom.ObjectType == r3d.ObjectType.Curve:
                # For curves, extract key points
                geometry_data["length"] = geom.GetLength() if hasattr(geom, "GetLength") else "Unknown"
                geometry_data["is_closed"] = geom.IsClosed if hasattr(geom, "IsClosed") else "Unknown"

                # Get start and end points if not closed
                if hasattr(geom, "PointAtStart") and hasattr(geom, "PointAtEnd"):
                    start = geom.PointAtStart
                    end = geom.PointAtEnd
                    geometry_data["start_point"] = [start.X, start.Y, start.Z]
                    geometry_data["end_point"] = [end.X, end.Y, end.Z]

            elif geom.ObjectType == r3d.ObjectType.Brep:
                # For solids, extract volume and surface area
                geometry_data["volume"] = geom.GetVolume() if hasattr(geom, "GetVolume") else "Unknown"
                geometry_data["area"] = geom.GetArea() if hasattr(geom, "GetArea") else "Unknown"
                geometry_data["is_solid"] = geom.IsSolid if hasattr(geom, "IsSolid") else "Unknown"

                # Count faces, edges, vertices
                if hasattr(geom, "Faces") and hasattr(geom, "Edges"):
                    geometry_data["face_count"] = len(geom.Faces)
                    geometry_data["edge_count"] = len(geom.Edges)

            elif geom.ObjectType == r3d.ObjectType.Mesh:
                # For meshes, extract vertex and face counts
                if hasattr(geom, "Vertices") and hasattr(geom, "Faces"):
                    geometry_data["vertex_count"] = len(geom.Vertices)
                    geometry_data["face_count"] = len(geom.Faces)

        # Format output as readable text
        output = [f"# Geometry Data for Object {index}"]
        output.append(f"- Type: {geometry_data['type']}")
        output.append(f"- ID: {geometry_data['id']}")

        if "bounding_box" in geometry_data:
            bbox = geometry_data["bounding_box"]
            output.append("- Bounding Box:")
            output.append(f"  - Min: ({bbox['min'][0]:.2f}, {bbox['min'][1]:.2f}, {bbox['min'][2]:.2f})")
            output.append(f"  - Max: ({bbox['max'][0]:.2f}, {bbox['max'][1]:.2f}, {bbox['max'][2]:.2f})")
            output.append(
                f"  - Dimensions: {bbox['dimensions'][0]:.2f} × {bbox['dimensions'][1]:.2f} × {bbox['dimensions'][2]:.2f}"
            )

        # Add remaining data with nice formatting
        for key, value in geometry_data.items():
            if key not in ["type", "id", "bounding_box"]:
                # Format key nicely
                formatted_key = key.replace("_", " ").title()

                # Format value based on type
                if isinstance(value, list) and len(value) == 3:
                    formatted_value = f"({value[0]:.2f}, {value[1]:.2f}, {value[2]:.2f})"
                elif isinstance(value, float):
                    formatted_value = f"{value:.4f}"
                else:
                    formatted_value = str(value)

                output.append(f"- {formatted_key}: {formatted_value}")

        return "\n".join(output)

    @mcp.tool()
    async def measure_distance(file_path: str, object_index1: int, object_index2: int) -> str:
        """Measure the distance between two objects in a Rhino file.

        Args:
            file_path: Path to the .3dm file
            object_index1: Index of the first object
            object_index2: Index of the second object

        Returns:
            Distance measurement information
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        result = await rhino.read_3dm_file(file_path)

        if result["result"] == "error":
            return f"Error: {result['error']}"

        model = result["model"]
        r3d = rhino.rhino_instance["r3d"]

        # Validate indices
        try:
            idx1 = int(object_index1)
            idx2 = int(object_index2)

            if idx1 < 0 or idx1 >= len(model.Objects) or idx2 < 0 or idx2 >= len(model.Objects):
                return (
                    f"Error: Invalid object indices. File has {len(model.Objects)} objects (0-{len(model.Objects)-1})."
                )
        except ValueError:
            return "Error: Object indices must be numbers."

        # Get geometries
        obj1 = model.Objects[idx1]
        obj2 = model.Objects[idx2]
        geom1 = obj1.Geometry
        geom2 = obj2.Geometry

        if not geom1 or not geom2:
            return "Error: One or both objects don't have geometry."

        # Calculate distances using bounding boxes (simple approach)
        bbox1 = geom1.GetBoundingBox() if hasattr(geom1, "GetBoundingBox") else None
        bbox2 = geom2.GetBoundingBox() if hasattr(geom2, "GetBoundingBox") else None

        if not bbox1 or not bbox2:
            return "Error: Couldn't get bounding boxes for the objects."

        # Calculate center points
        center1 = r3d.Point3d(
            (bbox1.Min.X + bbox1.Max.X) / 2, (bbox1.Min.Y + bbox1.Max.Y) / 2, (bbox1.Min.Z + bbox1.Max.Z) / 2
        )

        center2 = r3d.Point3d(
            (bbox2.Min.X + bbox2.Max.X) / 2, (bbox2.Min.Y + bbox2.Max.Y) / 2, (bbox2.Min.Z + bbox2.Max.Z) / 2
        )

        # Calculate distance between centers
        distance = center1.DistanceTo(center2)

        # Get object names
        name1 = obj1.Attributes.Name or f"Object {idx1}"
        name2 = obj2.Attributes.Name or f"Object {idx2}"

        return f"""Measurement between '{name1}' and '{name2}':
- Center-to-center distance: {distance:.4f} units
- Object 1 center: ({center1.X:.2f}, {center1.Y:.2f}, {center1.Z:.2f})
- Object 2 center: ({center2.X:.2f}, {center2.Y:.2f}, {center2.Z:.2f})

Note: This is an approximate center-to-center measurement using bounding boxes.
"""

```

--------------------------------------------------------------------------------
/grasshopper_mcp/tools/advanced_grasshopper.py:
--------------------------------------------------------------------------------

```python
from mcp.server.fastmcp import FastMCP
from typing import Dict, List, Any, Optional


def register_advanced_grasshopper_tools(mcp: FastMCP) -> None:
    """Register advanced Grasshopper operations with the MCP server."""

    @mcp.tool()
    async def create_parametric_definition(
        description: str, parameters: Dict[str, Any], output_file: Optional[str] = None
    ) -> str:
        """Create a complete parametric definition in Grasshopper based on a description.

        Args:
            description: Detailed description of the parametric model to create
            parameters: Dictionary of parameter names and values
            output_file: Optional path to save the definition

        Returns:
            Result of the operation
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        # First, analyze the description to determine required components
        # This would ideally be done with an LLM or a sophisticated parsing system
        # Here we're using a simplified approach

        # Generate a basic workflow based on the description
        workflow = await generate_grasshopper_workflow(rhino, description, parameters)

        if workflow["result"] == "error":
            return f"Error generating workflow: {workflow['error']}"

        # Create the components in the definition
        component_ids = {}

        # Parameter components (sliders, panels, etc.)
        for param_name, param_info in workflow["parameters"].items():
            result = await rhino.add_gh_component(
                component_name=param_info["component"],
                component_type="Params",
                parameters={"NickName": param_name, "Value": param_info.get("value")},
            )

            if result["result"] == "error":
                return f"Error creating parameter component: {result['error']}"

            component_ids[param_name] = result["component_id"]

        # Processing components
        for comp_name, comp_info in workflow["components"].items():
            result = await rhino.add_gh_component(
                component_name=comp_info["component"],
                component_type=comp_info["type"],
                parameters={"NickName": comp_name},
            )

            if result["result"] == "error":
                return f"Error creating component: {result['error']}"

            component_ids[comp_name] = result["component_id"]

        # Python script components
        for script_name, script_info in workflow["scripts"].items():
            result = await rhino.create_gh_script_component(
                description=script_name,
                inputs=script_info["inputs"],
                outputs=script_info["outputs"],
                code=script_info["code"],
            )

            if result["result"] == "error":
                return f"Error creating script component: {result['error']}"

            component_ids[script_name] = result["component_id"]

        # Connect the components
        for connection in workflow["connections"]:
            source = connection["from"].split(".")
            target = connection["to"].split(".")

            source_id = component_ids.get(source[0])
            target_id = component_ids.get(target[0])

            if not source_id or not target_id:
                continue

            result = await rhino.connect_gh_components(
                source_id=source_id, source_param=source[1], target_id=target_id, target_param=target[1]
            )

            if result["result"] == "error":
                return f"Error connecting components: {result['error']}"

        # Run the definition to validate
        result = await rhino.run_gh_definition()

        if result["result"] == "error":
            return f"Error running definition: {result['error']}"

        # Save if output file is specified
        if output_file:
            save_result = await rhino.run_gh_definition(file_path=None, save_output=True, output_path=output_file)

            if save_result["result"] == "error":
                return f"Error saving definition: {save_result['error']}"

        return f"""Successfully created parametric Grasshopper definition:
- Description: {description}
- Components: {len(component_ids)} created
- Parameters: {len(workflow['parameters'])}
- Saved to: {output_file if output_file else 'Not saved to file'}
"""

    @mcp.tool()
    async def call_grasshopper_plugin(
        plugin_name: str, component_name: str, inputs: Dict[str, Any], file_path: Optional[str] = None
    ) -> str:
        """Call a specific component from a Grasshopper plugin.

        Args:
            plugin_name: Name of the plugin (e.g., 'Kangaroo', 'Ladybug')
            component_name: Name of the component to use
            inputs: Dictionary of input parameter names and values
            file_path: Optional path to a GH file to append to

        Returns:
            Result of the operation
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        # If file path provided, open that definition
        if file_path:
            open_result = await rhino.run_gh_definition(file_path=file_path)
            if open_result["result"] == "error":
                return f"Error opening file: {open_result['error']}"

        # Add the plugin component
        plugin_comp_result = await rhino.add_gh_component(
            component_name=component_name, component_type=plugin_name, parameters={}
        )

        if plugin_comp_result["result"] == "error":
            return f"Error adding plugin component: {plugin_comp_result['error']}"

        plugin_comp_id = plugin_comp_result["component_id"]

        # Add input parameters
        input_comp_ids = {}
        for input_name, input_value in inputs.items():
            # Determine the appropriate parameter component
            if isinstance(input_value, (int, float)):
                comp_type = "Number"
            elif isinstance(input_value, str):
                comp_type = "Text"
            elif isinstance(input_value, bool):
                comp_type = "Boolean"
            else:
                return f"Unsupported input type for {input_name}: {type(input_value)}"

            # Create the parameter component
            input_result = await rhino.add_gh_component(
                component_name=comp_type,
                component_type="Params",
                parameters={"NickName": input_name, "Value": input_value},
            )

            if input_result["result"] == "error":
                return f"Error creating input parameter {input_name}: {input_result['error']}"

            input_comp_ids[input_name] = input_result["component_id"]

            # Connect to the plugin component
            connect_result = await rhino.connect_gh_components(
                source_id=input_result["component_id"],
                source_param="output",
                target_id=plugin_comp_id,
                target_param=input_name,
            )

            if connect_result["result"] == "error":
                return f"Error connecting {input_name}: {connect_result['error']}"

        # Run the definition
        run_result = await rhino.run_gh_definition()

        if run_result["result"] == "error":
            return f"Error running definition with plugin: {run_result['error']}"

        return f"""Successfully called Grasshopper plugin component:
- Plugin: {plugin_name}
- Component: {component_name}
- Inputs: {', '.join(inputs.keys())}
- Execution time: {run_result.get('execution_time', 'Unknown')} seconds
"""

    @mcp.tool()
    async def edit_gh_script_component(file_path: str, component_id: str, new_code: str) -> str:
        """Edit the code in an existing Python script component.

        Args:
            file_path: Path to the Grasshopper file
            component_id: ID of the component to edit
            new_code: New Python script for the component

        Returns:
            Result of the operation
        """
        ctx = mcp.get_context()
        rhino = ctx.request_context.lifespan_context.rhino

        # Open the file
        open_result = await rhino.run_gh_definition(file_path=file_path)
        if open_result["result"] == "error":
            return f"Error opening file: {open_result['error']}"

        # Edit the component
        execution_code = """
        import Rhino
        import Grasshopper
        
        # Access the current Grasshopper document
        gh_doc = Grasshopper.Instances.ActiveCanvas.Document
        
        # Find the component by ID
        target_component = None
        for obj in gh_doc.Objects:
            if str(obj.ComponentGuid) == component_id:
                target_component = obj
                break
        
        if target_component is None:
            raise ValueError(f"Component with ID {component_id} not found")
            
        # Check if it's a Python component
        if not hasattr(target_component, "ScriptSource"):
            raise ValueError(f"Component is not a Python script component")
            
        # Update the code
        target_component.ScriptSource = new_code
        
        # Update the document
        gh_doc.NewSolution(True)
        
        result = {
            "component_name": target_component.NickName,
            "success": True
        }
        """

        edit_result = await rhino._execute_rhino(execution_code, {"component_id": component_id, "new_code": new_code})

        if edit_result["result"] == "error":
            return f"Error editing component: {edit_result['error']}"

        return f"""Successfully edited Python script component:
- Component: {edit_result.get('data', {}).get('component_name', 'Unknown')}
- Updated code length: {len(new_code)} characters
"""


async def generate_grasshopper_workflow(
    rhino_connection, description: str, parameters: Dict[str, Any]
) -> Dict[str, Any]:
    """Generate a Grasshopper workflow based on a description.

    This is a simplified implementation that parses the description to determine
    the necessary components, parameters, and connections.

    In a production system, this would likely use an LLM to generate the workflow.
    """
    # Initialize workflow structure
    workflow = {"parameters": {}, "components": {}, "scripts": {}, "connections": [], "result": "success"}

    # Analyze description to determine what we're building
    description_lower = description.lower()

    # Default to a basic parametric box if no specific shape is mentioned
    if "box" in description_lower or "cube" in description_lower:
        # Create a parametric box
        workflow["parameters"] = {
            "Width": {"component": "Number Slider", "value": parameters.get("Width", 10)},
            "Height": {"component": "Number Slider", "value": parameters.get("Height", 10)},
            "Depth": {"component": "Number Slider", "value": parameters.get("Depth", 10)},
        }

        workflow["components"] = {
            "BoxOrigin": {"component": "Construct Point", "type": "Vector"},
            "Box": {"component": "Box", "type": "Surface"},
        }

        workflow["connections"] = [
            {"from": "Width.output", "to": "Box.X Size"},
            {"from": "Height.output", "to": "Box.Y Size"},
            {"from": "Depth.output", "to": "Box.Z Size"},
            {"from": "BoxOrigin.Point", "to": "Box.Base Point"},
        ]

    elif "cylinder" in description_lower:
        # Create a parametric cylinder
        workflow["parameters"] = {
            "Radius": {"component": "Number Slider", "value": parameters.get("Radius", 5)},
            "Height": {"component": "Number Slider", "value": parameters.get("Height", 20)},
        }

        workflow["components"] = {
            "BasePoint": {"component": "Construct Point", "type": "Vector"},
            "Circle": {"component": "Circle", "type": "Curve"},
            "Cylinder": {"component": "Extrude", "type": "Surface"},
        }

        workflow["connections"] = [
            {"from": "Radius.output", "to": "Circle.Radius"},
            {"from": "BasePoint.Point", "to": "Circle.Base"},
            {"from": "Circle.Circle", "to": "Cylinder.Base"},
            {"from": "Height.output", "to": "Cylinder.Direction"},
        ]

    elif "loft" in description_lower or "surface" in description_lower:
        # Create a lofted surface between curves
        workflow["parameters"] = {
            "Points": {"component": "Number Slider", "value": parameters.get("Points", 5)},
            "Height": {"component": "Number Slider", "value": parameters.get("Height", 20)},
            "RadiusBottom": {"component": "Number Slider", "value": parameters.get("RadiusBottom", 10)},
            "RadiusTop": {"component": "Number Slider", "value": parameters.get("RadiusTop", 5)},
        }

        workflow["components"] = {
            "BasePoint": {"component": "Construct Point", "type": "Vector"},
            "TopPoint": {"component": "Construct Point", "type": "Vector"},
            "CircleBottom": {"component": "Circle", "type": "Curve"},
            "CircleTop": {"component": "Circle", "type": "Curve"},
            "Loft": {"component": "Loft", "type": "Surface"},
        }

        # For more complex workflows, we can use Python script components
        workflow["scripts"] = {
            "HeightVector": {
                "inputs": [{"name": "height", "type": "float", "description": "Height of the loft"}],
                "outputs": [{"name": "vector", "type": "vector", "description": "Height vector"}],
                "code": """
import Rhino.Geometry as rg

# Create a vertical vector for the height
vector = rg.Vector3d(0, 0, height)
""",
            }
        }

        workflow["connections"] = [
            {"from": "Height.output", "to": "HeightVector.height"},
            {"from": "HeightVector.vector", "to": "TopPoint.Z"},
            {"from": "RadiusBottom.output", "to": "CircleBottom.Radius"},
            {"from": "RadiusTop.output", "to": "CircleTop.Radius"},
            {"from": "BasePoint.Point", "to": "CircleBottom.Base"},
            {"from": "TopPoint.Point", "to": "CircleTop.Base"},
            {"from": "CircleBottom.Circle", "to": "Loft.Curves"},
            {"from": "CircleTop.Circle", "to": "Loft.Curves"},
        ]

    else:
        # Generic parametric object with Python script
        workflow["parameters"] = {
            "Parameter1": {"component": "Number Slider", "value": parameters.get("Parameter1", 10)},
            "Parameter2": {"component": "Number Slider", "value": parameters.get("Parameter2", 20)},
        }

        workflow["scripts"] = {
            "CustomGeometry": {
                "inputs": [
                    {"name": "param1", "type": "float", "description": "First parameter"},
                    {"name": "param2", "type": "float", "description": "Second parameter"},
                ],
                "outputs": [{"name": "geometry", "type": "geometry", "description": "Resulting geometry"}],
                "code": """
import Rhino.Geometry as rg
import math

# Create custom geometry based on parameters
point = rg.Point3d(0, 0, 0)
radius = param1
height = param2

# Default to a simple cylinder if nothing specific is mentioned
cylinder = rg.Cylinder(
    new rg.Circle(point, radius),
    height
)

geometry = cylinder.ToBrep(True, True)
""",
            }
        }

        workflow["connections"] = [
            {"from": "Parameter1.output", "to": "CustomGeometry.param1"},
            {"from": "Parameter2.output", "to": "CustomGeometry.param2"},
        ]

    return workflow

```

--------------------------------------------------------------------------------
/grasshopper_mcp/rhino/connection.py:
--------------------------------------------------------------------------------

```python
import platform
import os
import json
import uuid
from typing import Any, Dict, List, Optional
import time
import platform
import sys
import socket
import tempfile

from ..config import ServerConfig


def find_scriptcontext_path():
    scriptcontext_path = os.path.join(
        os.environ["APPDATA"],
        "McNeel",
        "Rhinoceros",
        "7.0",
        "Plug-ins",
        "IronPython (814d908a-e25c-493d-97e9-ee3861957f49)",
        "settings",
        "lib",
    )

    if not os.path.exists(scriptcontext_path):
        # If the specific path doesn't exist, try to find it
        import glob

        appdata = os.environ["APPDATA"]
        potential_paths = glob.glob(
            os.path.join(appdata, "McNeel", "Rhinoceros", "7.0", "Plug-ins", "IronPython*", "settings", "lib")
        )
        if potential_paths:
            scriptcontext_path
            # sys.path.append(potential_paths[0])

    return scriptcontext_path


def find_RhinoPython_path(rhino_path):
    appdata = os.environ["APPDATA"]
    rhino_python_paths = [
        # Standard Rhino Python lib paths
        os.path.join(os.path.dirname(rhino_path), "Plug-ins", "IronPython"),
        os.path.join(os.path.dirname(rhino_path), "Plug-ins", "IronPython", "Lib"),
        os.path.join(os.path.dirname(rhino_path), "Plug-ins", "PythonPlugins"),
        os.path.join(os.path.dirname(rhino_path), "Scripts"),
        # Try to find RhinoPython in various locations
        os.path.join(os.path.dirname(rhino_path), "Plug-ins"),
        os.path.join(appdata, "McNeel", "Rhinoceros", "7.0", "Plug-ins"),
        os.path.join(appdata, "McNeel", "Rhinoceros", "7.0", "Scripts"),
        # Common Rhino installation paths for plugins
        "C:\\Program Files\\Rhino 7\\Plug-ins",
        "C:\\Program Files\\Rhino 7\\Plug-ins\\PythonPlugins",
    ]

    return rhino_python_paths


class RhinoConnection:
    """Connection to Rhino/Grasshopper."""

    def __init__(self, config: ServerConfig):
        self.config = config
        self.connected = False
        self.rhino_instance = None
        self.is_mac = platform.system() == "Darwin"

        self.codelistener_host = "127.0.0.1"
        self.codelistener_port = 614  # Default CodeListener port

    async def initialize(self) -> None:
        """Initialize connection to Rhino/Grasshopper."""
        if self.config.use_compute_api:
            # Setup compute API connection
            self._initialize_compute()
        else:
            # Setup direct connection
            self._initialize_rhino()

        self.connected = True

    def _initialize_rhino(self) -> None:
        """Initialize Rhino geometry access."""
        if platform.system() == "Windows" and not self.config.use_rhino3dm:
            # Windows-specific RhinoInside implementation
            import sys

            rhino_path = self.config.rhino_path

            if not rhino_path or not os.path.exists(rhino_path):
                raise ValueError(f"Invalid Rhino path: {rhino_path}")
            # print(rhino_path)
            sys.path.append(rhino_path)

            # Add the specific path for scriptcontext
            scriptcontext_path = find_scriptcontext_path()
            sys.path.append(scriptcontext_path)

            RhinoPython_path = find_RhinoPython_path(rhino_path)
            for path in RhinoPython_path:
                if os.path.exists(path):
                    sys.path.append(path)
                    print(path)

            try:
                import rhinoinside

                rhinoinside.load()
                print("rhinoinside installed.")
                # Import Rhino components
                import Rhino

                print("Rhino installed.")
                import Rhino.Geometry as rg

                print("Rhino.Geometry installed.")
                import scriptcontext as sc

                # Store references
                self.rhino_instance = {"Rhino": Rhino, "rg": rg, "sc": sc, "use_rhino3dm": False}
            except ImportError as e:
                raise ImportError(f"Error importing RhinoInside or Rhino components: {e}")
        else:
            # Cross-platform rhino3dm implementation
            try:
                import rhino3dm as r3d

                self.rhino_instance = {"r3d": r3d, "use_rhino3dm": True}
            except ImportError:
                raise ImportError("Please install rhino3dm: uv add rhino3dm")

    async def send_code_to_rhino(self, code: str) -> Dict[str, Any]:
        """Send Python code to Rhino via CodeListener.

        Args:
            code: Python code to execute in Rhino

        Returns:
            Dictionary with result and response or error
        """
        try:
            # Create a temporary Python file
            fd, temp_path = tempfile.mkstemp(suffix=".py")
            try:
                # Write the code to the file
                with os.fdopen(fd, "w") as f:
                    f.write(code)

                # Create message object
                msg_obj = {"filename": temp_path, "run": True, "reset": False, "temp": True}

                # Convert to JSON
                json_msg = json.dumps(msg_obj)

                # Create a TCP socket
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.settimeout(10)
                sock.connect((self.codelistener_host, self.codelistener_port))

                # Send the JSON message
                sock.sendall(json_msg.encode("utf-8"))

                # Receive the response
                response = sock.recv(4096).decode("utf-8")

                # Close the socket
                sock.close()

                return {"result": "success", "response": response}

            finally:
                # Clean up - remove temporary file after execution
                try:
                    os.unlink(temp_path)
                except Exception as cleanup_error:
                    print(f"Error cleaning up temporary file: {cleanup_error}")

        except Exception as e:
            return {"result": "error", "error": str(e)}

    async def generate_and_execute_rhino_code(
        self, prompt: str, model_context: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """Generate Rhino Python code from a prompt and execute it.

        Args:
            prompt: Description of what code to generate
            model_context: Optional context about the model (dimensions, parameters, etc.)

        Returns:
            Result dictionary with code, execution result, and any output
        """
        # Step 1: Generate Python code based on the prompt
        code = await self._generate_code_from_prompt(prompt, model_context)

        # Step 2: Execute the generated code in Rhino
        result = await self.send_code_to_rhino(code)

        # Return both the code and the execution result
        return {
            "result": result.get("result", "error"),
            "code": code,
            "response": result.get("response", ""),
            "error": result.get("error", ""),
        }

    async def _generate_code_from_prompt(
        self, prompt: str, model_context: Optional[Dict[str, Any]] = None
    ) -> str:
        """Generate Rhino Python code from a text prompt.

        Args:
            prompt: Description of what the code should do
            model_context: Optional context about the model

        Returns:
            Generated Python code as a string
        """
        # Add standard imports for Rhino Python code
        code = """
import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
import System
from Rhino.Geometry import *

# Disable redraw to improve performance
rs.EnableRedraw(False)
"""

        # Add code based on the prompt
        prompt_lower = prompt.lower()

        if "circle" in prompt_lower:
            radius = model_context.get("radius", 10.0) if model_context else 10.0
            center_x = model_context.get("center_x", 0.0) if model_context else 0.0
            center_y = model_context.get("center_y", 0.0) if model_context else 0.0
            center_z = model_context.get("center_z", 0.0) if model_context else 0.0
            code += f"""
# Create a circle based on prompt: {prompt}
center = Point3d({center_x}, {center_y}, {center_z})
circle = Circle(Plane.WorldXY, center, {radius})
circle_id = sc.doc.Objects.AddCircle(circle)
if circle_id:
    rs.ObjectName(circle_id, "GeneratedCircle")
    print("Created a circle!")
else:
    print("Failed to create circle")
"""
        return code

    async def send_code_to_gh(self, code: str, file_path: str) -> Dict[str, Any]:
        """Send Python code to a file for Grasshopper to use.

        Args:
            code: Python code to save for Grasshopper
            file_path: Path where the Python file should be saved

        Returns:
            Dictionary with result and file path
        """
        try:
            # Write the code to the file
            with open(file_path, "w") as f:
                f.write(code)

            return {
                "result": "success",
                "file_path": file_path,
                "message": f"Grasshopper Python file created at {file_path}",
            }

        except Exception as e:
            return {"result": "error", "error": str(e)}

    async def generate_and_execute_gh_code(
        self,
        prompt: str,
        file_path: str,
        model_context: Optional[Dict[str, Any]] = None,
        component_name: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Generate Grasshopper Python code from a prompt and save it for execution.

        Args:
            prompt: Description of what code to generate
            model_context: Optional context about the model (dimensions, parameters, etc.)
            component_name: Optional name for the GH Python component

        Returns:
            Result dictionary with code, file path, and any output
        """
        # Step 1: Generate Python code based on the prompt
        code = await self._generate_gh_code_from_prompt(prompt, model_context, component_name)

        # Step 2: Save the generated code for Grasshopper to use
        result = await self.send_code_to_gh(code, file_path)

        # Return both the code and the result
        return {
            "result": result.get("result", "error"),
            "code": code,
            "file_path": result.get("file_path", ""),
            "response": result.get("response", ""),
            "error": result.get("error", ""),
        }

    async def _generate_gh_code_from_prompt(
        self,
        prompt: str,
        model_context: Optional[Dict[str, Any]] = None,
        component_name: Optional[str] = None,
    ) -> str:
        """Generate Grasshopper Python code from a text prompt.

        Args:
            prompt: Description of what the code should do
            model_context: Optional context about the model
            component_name: Optional name for the component

        Returns:
            Generated Python code as a string
        """
        # Add component name as a comment
        if component_name:
            code = f"""# Grasshopper Python Component: {component_name}
# Generated from prompt: {prompt}
"""
        else:
            code = f"""# Grasshopper Python Component
# Generated from prompt: {prompt}
"""

        # Add standard imports for Grasshopper Python code
        code += """
import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino.Geometry as rg
import ghpythonlib.components as ghcomp
import math
"""
        # Add code based on the prompt
        prompt_lower = prompt.lower()

        if "circle" in prompt_lower:
            radius = model_context.get("radius", 10.0) if model_context else 10.0
            center_x = model_context.get("center_x", 0.0) if model_context else 0.0
            center_y = model_context.get("center_y", 0.0) if model_context else 0.0
            center_z = model_context.get("center_z", 0.0) if model_context else 0.0
            code += f"""
# Create a circle based on prompt: {prompt}
center = rg.Point3d({center_x}, {center_y}, {center_z})
circle = rg.Circle(rg.Plane.WorldXY, center, {radius})
print("Created a circle!")
"""
        return code

    def _initialize_compute(self) -> None:
        """Initialize connection to compute.rhino3d.com."""
        if not self.config.compute_url or not self.config.compute_api_key:
            raise ValueError("Compute API URL and key required for compute API connection")

        # We'll use requests for API calls

    async def close(self) -> None:
        """Close connection to Rhino/Grasshopper."""
        # Cleanup as needed
        self.connected = False

    async def execute_code(self, code: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """Execute operations on Rhino geometry."""
        if not self.connected:
            raise RuntimeError("Not connected to Rhino geometry system")

        if self.config.use_compute_api:
            return await self._execute_compute(code, parameters)
        else:
            # Check if we're using rhino3dm
            if self.rhino_instance.get("use_rhino3dm", False):
                return await self._execute_rhino3dm(code, parameters)
            else:
                return await self._execute_rhino(code, parameters)

    async def _execute_rhino(self, code: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """Execute code directly in Rhino (Windows only)."""
        # Existing Rhino execution code
        globals_dict = dict(self.rhino_instance)

        # Add parameters to context
        if parameters:
            globals_dict.update(parameters)

        # Execute the code
        locals_dict = {}
        try:
            exec(code, globals_dict, locals_dict)
            return {"result": "success", "data": locals_dict.get("result", None)}
        except Exception as e:
            # More detailed error reporting for Windows
            import traceback

            error_trace = traceback.format_exc()
            return {"result": "error", "error": str(e), "traceback": error_trace}

    async def _execute_rhino3dm(
        self, code: str, parameters: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """Execute code using rhino3dm library."""
        # Create execution context with rhino3dm
        r3d = self.rhino_instance["r3d"]
        globals_dict = {"r3d": r3d, "parameters": parameters or {}}

        # Add parameters to context
        if parameters:
            globals_dict.update(parameters)

        # Execute the code
        locals_dict = {}
        try:
            exec(code, globals_dict, locals_dict)
            return {"result": "success", "data": locals_dict.get("result", None)}
        except Exception as e:
            return {"result": "error", "error": str(e)}

    async def _execute_compute(
        self, code: str, parameters: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """Execute code via compute.rhino3d.com API."""
        # Existing Compute API code
        import requests

        url = f"{self.config.compute_url}/grasshopper"

        # Prepare request payload
        payload = {"algo": code, "pointer": None, "values": parameters or {}}

        headers = {
            "Authorization": f"Bearer {self.config.compute_api_key}",
            "Content-Type": "application/json",
        }

        try:
            response = requests.post(url, json=payload, headers=headers)
            response.raise_for_status()
            return {"result": "success", "data": response.json()}
        except Exception as e:
            return {"result": "error", "error": str(e)}

    async def read_3dm_file(self, file_path: str) -> Dict[str, Any]:
        """Read a .3dm file and return its model."""
        if not self.connected:
            raise RuntimeError("Not connected to Rhino geometry system")

        try:
            if self.rhino_instance.get("use_rhino3dm", False):
                r3d = self.rhino_instance["r3d"]
                model = r3d.File3dm.Read(file_path)
                if model:
                    return {"result": "success", "model": model}
                else:
                    return {"result": "error", "error": f"Failed to open file: {file_path}"}
            else:
                # For RhinoInside on Windows, use a different approach
                code = """
                import Rhino
                result = {
                    "model": Rhino.FileIO.File3dm.Read(file_path)
                }
                """
                return await self._execute_rhino(code, {"file_path": file_path})
        except Exception as e:
            return {"result": "error", "error": str(e)}

    ### For Grasshopper
    async def create_gh_script_component(
        self, description: str, inputs: List[Dict[str, Any]], outputs: List[Dict[str, Any]], code: str
    ) -> Dict[str, Any]:
        """Create a Python script component in a Grasshopper definition.

        Args:
            description: Description of the component
            inputs: List of input parameters
            outputs: List of output parameters
            code: Python code for the component

        Returns:
            Result dictionary with component_id on success
        """
        if not self.connected:
            return {"result": "error", "error": "Not connected to Rhino/Grasshopper"}

        # Generate a unique component ID
        component_id = f"py_{str(uuid.uuid4())[:8]}"

        if self.config.use_compute_api:
            # Implementation for compute API
            return await self._create_gh_script_component_compute(
                component_id, description, inputs, outputs, code
            )
        elif platform.system() == "Windows" and not self.rhino_instance.get("use_rhino3dm", True):
            # Implementation for RhinoInside (Windows)
            return await self._create_gh_script_component_rhinoinside(
                component_id, description, inputs, outputs, code
            )
        else:
            # We can't directly create Grasshopper components with rhino3dm
            return {
                "result": "error",
                "error": "Creating Grasshopper components requires RhinoInside or Compute API",
            }

    async def _create_gh_script_component_rhinoinside(
        self,
        component_id: str,
        description: str,
        inputs: List[Dict[str, Any]],
        outputs: List[Dict[str, Any]],
        code: str,
    ) -> Dict[str, Any]:
        """Create a Python script component using RhinoInside."""
        # Using the RhinoInside context
        execution_code = """
        import Rhino
        import Grasshopper
        import GhPython
        from Grasshopper.Kernel import GH_Component
        
        # Access the current Grasshopper document
        gh_doc = Grasshopper.Instances.ActiveCanvas.Document
        
        # Create a new Python component
        py_comp = GhPython.Component.PythonComponent()
        py_comp.NickName = description
        py_comp.Name = description
        py_comp.Description = description
        py_comp.ComponentGuid = System.Guid(component_id)
        
        # Set up inputs
        for i, inp in enumerate(inputs):
            name = inp["name"]
            param_type = inp.get("type", "object")
            # Convert param_type to Grasshopper parameter type
            access_type = 0  # 0 = item access
            py_comp.Params.Input[i].Name = name
            py_comp.Params.Input[i].NickName = name
            py_comp.Params.Input[i].Description = inp.get("description", "")
        
        # Set up outputs
        for i, out in enumerate(outputs):
            name = out["name"]
            param_type = out.get("type", "object")
            # Convert param_type to Grasshopper parameter type
            py_comp.Params.Output[i].Name = name
            py_comp.Params.Output[i].NickName = name
            py_comp.Params.Output[i].Description = out.get("description", "")
        
        # Set the Python code
        py_comp.ScriptSource = code
        
        # Add the component to the document
        gh_doc.AddObject(py_comp, False)
        
        # Set the position on canvas (centered)
        py_comp.Attributes.Pivot = Grasshopper.Kernel.GH_Convert.ToPoint(
            Rhino.Geometry.Point2d(gh_doc.Bounds.Center.X, gh_doc.Bounds.Center.Y)
        )
        
        # Update the document
        gh_doc.NewSolution(True)
        
        # Store component for reference
        result = {
            "component_id": str(component_id)
        }
        """

        # Execute the code
        return await self._execute_rhino(
            execution_code,
            {
                "component_id": component_id,
                "description": description,
                "inputs": inputs,
                "outputs": outputs,
                "code": code,
            },
        )

    async def _create_gh_script_component_compute(
        self,
        component_id: str,
        description: str,
        inputs: List[Dict[str, Any]],
        outputs: List[Dict[str, Any]],
        code: str,
    ) -> Dict[str, Any]:
        """Create a Python script component using Compute API."""
        import requests

        url = f"{self.config.compute_url}/grasshopper/scriptcomponent"

        # Prepare payload
        payload = {
            "id": component_id,
            "name": description,
            "description": description,
            "inputs": inputs,
            "outputs": outputs,
            "code": code,
        }

        headers = {
            "Authorization": f"Bearer {self.config.compute_api_key}",
            "Content-Type": "application/json",
        }

        try:
            response = requests.post(url, json=payload, headers=headers)
            response.raise_for_status()
            return {"result": "success", "component_id": component_id, "data": response.json()}
        except Exception as e:
            return {"result": "error", "error": str(e)}

    async def add_gh_component(
        self, component_name: str, component_type: str, parameters: Dict[str, Any]
    ) -> Dict[str, Any]:
        """Add a component from an existing Grasshopper plugin.

        Args:
            component_name: Name of the component
            component_type: Type/category of the component
            parameters: Component parameters and settings

        Returns:
            Result dictionary with component_id on success
        """
        if not self.connected:
            return {"result": "error", "error": "Not connected to Rhino/Grasshopper"}

        # Generate a unique component ID
        component_id = f"comp_{str(uuid.uuid4())[:8]}"

        if self.config.use_compute_api:
            # Implementation for compute API
            return await self._add_gh_component_compute(
                component_id, component_name, component_type, parameters
            )
        elif platform.system() == "Windows" and not self.rhino_instance.get("use_rhino3dm", True):
            # Implementation for RhinoInside
            return await self._add_gh_component_rhinoinside(
                component_id, component_name, component_type, parameters
            )
        else:
            return {
                "result": "error",
                "error": "Adding Grasshopper components requires RhinoInside or Compute API",
            }

    async def _add_gh_component_rhinoinside(
        self, component_id: str, component_name: str, component_type: str, parameters: Dict[str, Any]
    ) -> Dict[str, Any]:
        """Add a Grasshopper component using RhinoInside."""
        execution_code = """
        import Rhino
        import Grasshopper
        from Grasshopper.Kernel import GH_ComponentServer
        
        # Access the current Grasshopper document
        gh_doc = Grasshopper.Instances.ActiveCanvas.Document
        
        # Find the component by name and type
        server = GH_ComponentServer.FindServer(component_name, component_type)
        if server is None:
            raise ValueError(f"Component '{component_name}' of type '{component_type}' not found")
        
        # Create the component instance
        component = server.Create()
        component.ComponentGuid = System.Guid(component_id)
        
        # Set parameters
        for param_name, param_value in parameters.items():
            if hasattr(component, param_name):
                setattr(component, param_name, param_value)
        
        # Add the component to the document
        gh_doc.AddObject(component, False)
        
        # Set the position on canvas
        component.Attributes.Pivot = Grasshopper.Kernel.GH_Convert.ToPoint(
            Rhino.Geometry.Point2d(gh_doc.Bounds.Center.X, gh_doc.Bounds.Center.Y)
        )
        
        # Update the document
        gh_doc.NewSolution(True)
        
        # Return component info
        result = {
            "component_id": str(component_id)
        }
        """

        return await self._execute_rhino(
            execution_code,
            {
                "component_id": component_id,
                "component_name": component_name,
                "component_type": component_type,
                "parameters": parameters,
            },
        )

    async def _add_gh_component_compute(
        self, component_id: str, component_name: str, component_type: str, parameters: Dict[str, Any]
    ) -> Dict[str, Any]:
        """Add a Grasshopper component using Compute API."""
        import requests

        url = f"{self.config.compute_url}/grasshopper/component"

        # Prepare payload
        payload = {
            "id": component_id,
            "name": component_name,
            "type": component_type,
            "parameters": parameters,
        }

        headers = {
            "Authorization": f"Bearer {self.config.compute_api_key}",
            "Content-Type": "application/json",
        }

        try:
            response = requests.post(url, json=payload, headers=headers)
            response.raise_for_status()
            return {"result": "success", "component_id": component_id, "data": response.json()}
        except Exception as e:
            return {"result": "error", "error": str(e)}

    async def connect_gh_components(
        self, source_id: str, source_param: str, target_id: str, target_param: str
    ) -> Dict[str, Any]:
        """Connect parameters between Grasshopper components.

        Args:
            source_id: Source component ID
            source_param: Source parameter name
            target_id: Target component ID
            target_param: Target parameter name

        Returns:
            Result dictionary
        """
        if not self.connected:
            return {"result": "error", "error": "Not connected to Rhino/Grasshopper"}

        if self.config.use_compute_api:
            return await self._connect_gh_components_compute(source_id, source_param, target_id, target_param)
        elif platform.system() == "Windows" and not self.rhino_instance.get("use_rhino3dm", True):
            return await self._connect_gh_components_rhinoinside(
                source_id, source_param, target_id, target_param
            )
        else:
            return {
                "result": "error",
                "error": "Connecting Grasshopper components requires RhinoInside or Compute API",
            }

    async def _connect_gh_components_rhinoinside(
        self, source_id: str, source_param: str, target_id: str, target_param: str
    ) -> Dict[str, Any]:
        """Connect Grasshopper components using RhinoInside."""
        execution_code = """
        import Rhino
        import Grasshopper
        from Grasshopper.Kernel import GH_Document
        
        # Access the current Grasshopper document
        gh_doc = Grasshopper.Instances.ActiveCanvas.Document
        
        # Find the source component
        source = None
        for obj in gh_doc.Objects:
            if str(obj.ComponentGuid) == source_id:
                source = obj
                break
        
        if source is None:
            raise ValueError(f"Source component with ID {source_id} not found")
        
        # Find the target component
        target = None
        for obj in gh_doc.Objects:
            if str(obj.ComponentGuid) == target_id:
                target = obj
                break
        
        if target is None:
            raise ValueError(f"Target component with ID {target_id} not found")
        
        # Find the source output parameter
        source_output = None
        for i, param in enumerate(source.Params.Output):
            if param.Name == source_param:
                source_output = param
                break
        
        if source_output is None:
            raise ValueError(f"Source parameter {source_param} not found on component {source_id}")
        
        # Find the target input parameter
        target_input = None
        for i, param in enumerate(target.Params.Input):
            if param.Name == target_param:
                target_input = param
                break
        
        if target_input is None:
            raise ValueError(f"Target parameter {target_param} not found on component {target_id}")
        
        # Connect the parameters
        gh_doc.GraftIO(source_output.Recipients, target_input.Sources)
        
        # Update the document
        gh_doc.NewSolution(True)
        
        result = {
            "success": True
        }
        """

        return await self._execute_rhino(
            execution_code,
            {
                "source_id": source_id,
                "source_param": source_param,
                "target_id": target_id,
                "target_param": target_param,
            },
        )

    async def _connect_gh_components_compute(
        self, source_id: str, source_param: str, target_id: str, target_param: str
    ) -> Dict[str, Any]:
        """Connect Grasshopper components using Compute API."""
        import requests

        url = f"{self.config.compute_url}/grasshopper/connect"

        # Prepare payload
        payload = {
            "source_id": source_id,
            "source_param": source_param,
            "target_id": target_id,
            "target_param": target_param,
        }

        headers = {
            "Authorization": f"Bearer {self.config.compute_api_key}",
            "Content-Type": "application/json",
        }

        try:
            response = requests.post(url, json=payload, headers=headers)
            response.raise_for_status()
            return {"result": "success", "data": response.json()}
        except Exception as e:
            return {"result": "error", "error": str(e)}

    async def run_gh_definition(
        self, file_path: Optional[str] = None, save_output: bool = False, output_path: Optional[str] = None
    ) -> Dict[str, Any]:
        """Run a Grasshopper definition.

        Args:
            file_path: Path to the .gh file (or None for current definition)
            save_output: Whether to save the output
            output_path: Path to save the output (if save_output is True)

        Returns:
            Result dictionary with execution information
        """
        if not self.connected:
            return {"result": "error", "error": "Not connected to Rhino/Grasshopper"}

        if self.config.use_compute_api:
            return await self._run_gh_definition_compute(file_path, save_output, output_path)
        elif platform.system() == "Windows" and not self.rhino_instance.get("use_rhino3dm", True):
            return await self._run_gh_definition_rhinoinside(file_path, save_output, output_path)
        else:
            return {
                "result": "error",
                "error": "Running Grasshopper definitions requires RhinoInside or Compute API",
            }

    async def _run_gh_definition_rhinoinside(
        self, file_path: Optional[str] = None, save_output: bool = False, output_path: Optional[str] = None
    ) -> Dict[str, Any]:
        """Run a Grasshopper definition using RhinoInside."""
        execution_code = """
        import Rhino
        import Grasshopper
        import time
        
        start_time = time.time()
        
        if file_path:
            # Open the specified Grasshopper definition
            gh_doc = Grasshopper.Kernel.GH_Document()
            gh_doc.LoadDocumentObject(file_path)
        else:
            # Use the current document
            gh_doc = Grasshopper.Instances.ActiveCanvas.Document
        
        # Run the solution
        gh_doc.NewSolution(True)
        
        # Wait for solution to complete
        while gh_doc.SolutionState != Grasshopper.Kernel.GH_ProcessStep.Finished:
            time.sleep(0.1)
        
        execution_time = time.time() - start_time
        
        # Save if requested
        if save_output and output_path:
            gh_doc.SaveAs(output_path, False)
        
        # Get a summary of the outputs
        output_summary = []
        for obj in gh_doc.Objects:
            if obj.Attributes.GetTopLevel.DocObject is not None:
                for param in obj.Params.Output:
                    if param.VolatileDataCount > 0:
                        output_summary.append({
                            "component": obj.NickName,
                            "param": param.Name,
                            "data_count": param.VolatileDataCount
                        })
        
        result = {
            "execution_time": execution_time,
            "output_summary": output_summary
        }
        """

        return await self._execute_rhino(
            execution_code, {"file_path": file_path, "save_output": save_output, "output_path": output_path}
        )

    async def _run_gh_definition_compute(
        self, file_path: Optional[str] = None, save_output: bool = False, output_path: Optional[str] = None
    ) -> Dict[str, Any]:
        """Run a Grasshopper definition using Compute API."""
        import requests

        url = f"{self.config.compute_url}/grasshopper/run"

        # Prepare payload
        payload = {"file_path": file_path, "save_output": save_output, "output_path": output_path}

        headers = {
            "Authorization": f"Bearer {self.config.compute_api_key}",
            "Content-Type": "application/json",
        }

        try:
            response = requests.post(url, json=payload, headers=headers)
            response.raise_for_status()
            return {"result": "success", "data": response.json()}
        except Exception as e:
            return {"result": "error", "error": str(e)}

```