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.
Quick start
-
Register with Claude Code:
claude mcp add lamcp --scope user -- uvx lamcp
-
Install it in Grasshopper: download
Lamcp_Bridge.ghuserand drop it into your Grasshopper Components folder (Grasshopper → File → Special Folders → Components Folder). -
Activate it: restart Grasshopper, drop the
LAMCP Bridgecomponent (under theLAMCPtab) on the canvas, wire aBoolean Toggleset toTrueintoenable.
Done!
See Setup for alternative install paths
(without uvx, project-scoped, paste-from-source) and the
Tools exposed section for what's available.
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. Register with Claude Code
LAMCP is a dev tool you'll want available everywhere, so register it at
user scope. The friction-free path uses
uvx so no explicit install of lamcp is
needed — it'll be fetched and cached on first invocation:
claude mcp add lamcp --scope user -- uvx lamcp
Or, if you'd rather install lamcp into your environment first:
pip install lamcp # or: uv tool install lamcp
claude mcp add lamcp --scope user -- lamcp
Pick the right
--scope. Without--scope,claude mcp adddefaults to local scope, which only loads the server when Claude Code is launched from the exact directory you ran the command in. That's almost never what you want for a dev tool. Your options:
Scope Stored in Use when user~/.claude.json(top-levelmcpServers)You want LAMCP available in every project project<repo>/.mcp.json(committed to git, shared with collaborators)Your whole team should get LAMCP for one project local~/.claude.jsonunder the current project entry (default)You're temporarily trying it in one directory
Verify the registration:
claude mcp list
The new tools are available in any new Claude Code conversation.
Using a different MCP client? Point it at the
lamcpcommand (oruvx lamcp) over stdio.
2. Install the bridge in Grasshopper
Option A — drop the pre-built userobject (recommended).
- Download
Lamcp_Bridge.ghuserfrom the latest release. - In Grasshopper: File → Special Folders → Components Folder. Move the
.ghuserfile there. - Restart Grasshopper.
LAMCP Bridgeappears under theLAMCPtab. - Drop it on the canvas, wire a
Boolean Toggle(set toTrue) intoenable. Thestatusoutput readslistening on http://127.0.0.1:8765.
Option B — paste the source manually (for hacking).
- Drop a Python 3 Script component on the canvas. Paste the contents of
grasshopper/Lamcp_Bridge/code.pyin. - Add two inputs:
enable(bool) andport(int). Add one output:status. - Wire a
Boolean Toggle(set toTrue) intoenable. - The
statusoutput should readlistening 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 |
list_grasshopper_objects |
enumerate canvas objects (type, nickname, GUID, pivot, runtime messages) |
add_python_component |
drop a Rhino 8 Python 3 script component (code + I/O params) onto canvas |
add_reloader_component |
drop the COMPAS side-by-side hot-reload bootstrap (enable_reloader()) |
set_script_venv |
repoint # venv: directive of script components at one environment |
solve_grasshopper |
re-solve safely via ScheduleSolution on the UI thread |
save_grasshopper_document |
save the active document to disk |
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/Grasshopperaccess works cross-thread, but heavy mutations (bulkRemoveObject, etc.) can crash Rhino. Eto-based UI marshalling is a planned addition. isinstancedoesn't always work: in Rhino 8 CPython,isinstanceagainst concrete .NET types often returns False due to interface interop. Useobj.GetType().Name == "..."instead.RemoveSource(IGH_Param)is a silent no-op: use theRemoveSource(Guid)overload.float(System.Decimal)raises: wrap withSystem.Convert.ToDouble(x)orfloat(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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lamcp-0.4.0.tar.gz.
File metadata
- Download URL: lamcp-0.4.0.tar.gz
- Upload date:
- Size: 117.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b3233dc18a80d3ef8a4e1347902826ac341a69e60e71b785d9385efca765ecaf
|
|
| MD5 |
c8d21a56fe952c2df2119cba3a57c4bb
|
|
| BLAKE2b-256 |
799d1b9f8529c7c62ddb78183d3cd300f0c2da5863f1af46f766a11f6f361eea
|
Provenance
The following attestation bundles were made for lamcp-0.4.0.tar.gz:
Publisher:
release.yml on gramaziokohler/lamcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lamcp-0.4.0.tar.gz -
Subject digest:
b3233dc18a80d3ef8a4e1347902826ac341a69e60e71b785d9385efca765ecaf - Sigstore transparency entry: 1685798310
- Sigstore integration time:
-
Permalink:
gramaziokohler/lamcp@4502cf8ed869efbc5c9023c037c003f9c3b1d337 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/gramaziokohler
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4502cf8ed869efbc5c9023c037c003f9c3b1d337 -
Trigger Event:
push
-
Statement type:
File details
Details for the file lamcp-0.4.0-py3-none-any.whl.
File metadata
- Download URL: lamcp-0.4.0-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75ee6ff5d3057d2b1f1b1a5e8a059c81afd2b4606e58ead4576d3fbe016d3dc6
|
|
| MD5 |
37be7ddccbd498c87fa23577120c1f6c
|
|
| BLAKE2b-256 |
e2aabf99b51a4252658c48a53842bef6ebc1c782e372e724eef106daf8c1f17d
|
Provenance
The following attestation bundles were made for lamcp-0.4.0-py3-none-any.whl:
Publisher:
release.yml on gramaziokohler/lamcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lamcp-0.4.0-py3-none-any.whl -
Subject digest:
75ee6ff5d3057d2b1f1b1a5e8a059c81afd2b4606e58ead4576d3fbe016d3dc6 - Sigstore transparency entry: 1685798778
- Sigstore integration time:
-
Permalink:
gramaziokohler/lamcp@4502cf8ed869efbc5c9023c037c003f9c3b1d337 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/gramaziokohler
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4502cf8ed869efbc5c9023c037c003f9c3b1d337 -
Trigger Event:
push
-
Statement type: