Skip to main content

Lambda MCP: teach your LLM to do Grasshopper tricks.

Project description

LAMCP

LAmbda MCP: teach your LLM to do Grasshopper tricks.

Lets Claude Code (or any MCP client) introspect and mutate a live Grasshopper session in real time: inspect the canvas, wire components, read/write slider values, run RhinoCommon calls, hot-reload modules -all from inside an AI agent loop, without rebuilding userobjects or restarting Rhino.

Architecture

LLM ──MCP stdio──▶ lamcp (Python 3.10+)
                          │
                          │  HTTP POST /exec  {"code": "...", "timeout": 30}
                          ▼
                  LAMCP Bridge GH component (Rhino 8 CPython 3.9)
                     ├─ http.server on 127.0.0.1:8765
                     ├─ exec() with shared globals
                     └─ returns {stdout, stderr, result, error}

Why split: Rhino 8's CPython runtime is pinned to 3.9. fastmcp and the underlying mcp SDK require 3.10+. So the MCP-speaking half runs in a system Python and forwards over loopback HTTP to a stdlib-only HTTP server living inside Rhino as a regular Grasshopper component.

Setup

1. Install the MCP server

git clone https://github.com/gramaziokohler/lamcp.git
cd lamcp
uv pip install -e .
# or: pip install -e .

2. Register with Claude Code (or whatever LLM you use)

Add to ~/.claude.json:

{
  "mcpServers": {
    "lamcp": {
      "command": "lamcp"
    }
  }
}

If lamcp isn't on PATH, use the absolute path to the venv's script:

{
  "mcpServers": {
    "lamcp": {
      "command": "/absolute/path/to/.venv/bin/lamcp"
    }
  }
}

Restart Claude Code so it discovers the new MCP server.

3. Install the bridge in Grasshopper

Option A — drop the pre-built userobject (recommended).

  1. Download Lamcp_Bridge.ghuser from the latest release.
  2. In Grasshopper: File → Special Folders → Components Folder. Move the .ghuser file there.
  3. Restart Grasshopper. LAMCP Bridge appears under the LAMCP tab.
  4. Drop it on the canvas, wire a Boolean Toggle (set to True) into enable. The status output reads listening on http://127.0.0.1:8765.

Option B — paste the source manually (for hacking).

  1. Drop a Python 3 Script component on the canvas. Paste the contents of grasshopper/Lamcp_Bridge/code.py in.
  2. Add two inputs: enable (bool) and port (int). Add one output: status.
  3. Wire a Boolean Toggle (set to True) into enable.
  4. The status output should read listening on http://127.0.0.1:8765.

Either way, your MCP client now has a run_python_script tool that exec()s code inside your live Rhino session.

Tools exposed

Tool Purpose
run_python_script exec() arbitrary Python inside Rhino, capture stdout / stderr / repr(_)
unload_python_modules drop sys.modules[prefix.*] so the next import re-reads from disk
bridge_health ping the bridge to verify it's reachable

Return contract for run_python_script

{
  "stdout": "...",            // captured stdout
  "stderr": "...",            // captured stderr
  "result": "repr of _",      // assign to `_` to return a value
  "error": null               // formatted traceback if exception raised
}

Globals persist between calls, so you can import once and reuse:

# call 1
import scriptcontext as sc; doc = sc.doc.ActiveDoc
# call 2
print(doc.Name)   # `doc` is still bound

Environment variables

Variable Default Purpose
LAMCP_BRIDGE_URL http://127.0.0.1:8765 URL of the bridge's HTTP server

Caveats

  • UI thread: code runs on the HTTP server thread, not the Rhino UI thread. Most read-only RhinoCommon / Grasshopper access works cross-thread, but heavy mutations (bulk RemoveObject, etc.) can crash Rhino. Eto-based UI marshalling is a planned addition.
  • isinstance doesn't always work: in Rhino 8 CPython, isinstance against concrete .NET types often returns False due to interface interop. Use obj.GetType().Name == "..." instead.
  • RemoveSource(IGH_Param) is a silent no-op: use the RemoveSource(Guid) overload.
  • float(System.Decimal) raises: wrap with System.Convert.ToDouble(x) or float(str(x)).

Security

The bridge listens on 127.0.0.1 only and accepts no auth: it runs arbitrary Python in your Rhino with no sandboxing. Never expose it beyond localhost, and stop it (enable=False) when you're done.

Development

Install with the dev extra to pull in ruff:

pip install -e ".[dev]"

Lint + format checks (same commands CI runs):

ruff check .                # lint
ruff format --check .       # formatting (non-destructive)

Auto-fix:

ruff check . --fix          # fix lint issues
ruff format .               # reformat

For one-off runs without installing into your env, uvx ruff ... works identically.

Releases are tag-driven — see RELEASING.md.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lamcp-0.1.0.tar.gz (106.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lamcp-0.1.0-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

Details for the file lamcp-0.1.0.tar.gz.

File metadata

  • Download URL: lamcp-0.1.0.tar.gz
  • Upload date:
  • Size: 106.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for lamcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8f754c604917f8fc29993e2ef03df015037d2ccd68aff18c46d902934c4490f2
MD5 a5029657b60ff971716d6d7ab70e66fb
BLAKE2b-256 c99c3f5ddb9b5990be914833641723a7f615465474efa240b69775aee6e6e919

See more details on using hashes here.

Provenance

The following attestation bundles were made for lamcp-0.1.0.tar.gz:

Publisher: release.yml on gramaziokohler/lamcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lamcp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: lamcp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for lamcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 025c13578834dce48fd6080958dfdeffe406b38bb1208e9d68782f4a8c4c3af9
MD5 f36bf8e681c4c2a13fcc8043dc7b9a43
BLAKE2b-256 7cd79435dc4ccdb554a1b528b5ff544ea5b2a53b6d74837c8db1ac33422fd74a

See more details on using hashes here.

Provenance

The following attestation bundles were made for lamcp-0.1.0-py3-none-any.whl:

Publisher: release.yml on gramaziokohler/lamcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page