A local HTTP eval server for live SketchUp model inspection
Claude Bridge is a development-only SketchUp extension. It exposes a tiny HTTP server on
127.0.0.1 that accepts Ruby source code, evaluates it inside the running SketchUp process, and returns
the result as JSON. Its purpose is to let a local coding agent (Claude Code) or any shell script read and manipulate
the model you currently have open, without the copy-paste round trip through the Ruby console.
This is an arbitrary code execution server. Any process running under your user account can reach it while it is started, and evaluated code has the full power of the SketchUp API — including saving, deleting and overwriting your work. Start it when you need it, stop it when you are done, and never ship it to anyone as part of a product.
ladb_claude_bridge-1.0.0.rbz.A Claude Bridge toolbar appears, and a Claude Bridge item is added to the Extensions menu. The extension does not auto-start the server on load.
For day-to-day development, use the Extension Sources extension by ThomThom to add this project's
src/ folder as an extra load path, so the extension runs straight from the working tree.
Failing that, copy or symlink ladb_claude_bridge.rb and the ladb_claude_bridge/
folder into SketchUp's Plugins directory.
Three equivalent ways:
Ladb::ClaudeBridge.start # default port 7857
Ladb::ClaudeBridge.start(port: 7900)
Ladb::ClaudeBridge.stop
Ladb::ClaudeBridge.running? # => true / false
Ladb::ClaudeBridge.toggle
Starting the server opens the Ruby console and logs the listening address. Requests are logged there too, which is the quickest way to confirm the bridge sees your calls.
The server listens on http://127.0.0.1:7857 and answers two routes. Every request must carry the
X-Claude-Bridge: 1 header — requests without it get 403.
| Route | Purpose |
|---|---|
GET /ping |
Liveness check. Returns the SketchUp version, the Ruby version, and the title and path of the active model. |
POST /eval |
The request body is Ruby source code, evaluated in the SketchUp process. Returns the value of the last expression plus anything the script printed to stdout. |
curl -s -H 'X-Claude-Bridge: 1' http://127.0.0.1:7857/ping
{
"ok": true,
"sketchup": "26.0.108",
"ruby": "3.2.2",
"model_title": "cabinet",
"model_path": "/Users/me/models/cabinet.skp"
}
Send the script as the raw request body. Writing it to a file first and using --data-binary @file
avoids all shell-quoting problems:
cat > /tmp/script.rb <<'RUBY'
model = Sketchup.active_model
model.selection.map { |e| [ e.class.name, e.entityID ] }
RUBY
curl -s -X POST -H 'X-Claude-Bridge: 1' \
--data-binary @/tmp/script.rb \
http://127.0.0.1:7857/eval
Always JSON, always HTTP 200 for /eval — success and script failure are distinguished by the
ok field:
{ "ok": true, "result": [ [ "Sketchup::Group", 1234 ] ], "stdout": "" }
{ "ok": false, "error": "NoMethodError: undefined method `foo' for nil",
"backtrace": [ "claude-bridge-eval:2:in `<main>'" ], "stdout": "partial output\n" }
| Field | Meaning |
|---|---|
ok | true if the script ran to completion. |
result | Value of the last expression, round-tripped through JSON. Values that
cannot be serialized fall back to their inspect string. |
stdout | Everything the script wrote to $stdout (puts,
print, …), captured separately from the result. |
error | Class and message of the raised exception. SyntaxError and
SystemStackError are reported too, not just StandardError. |
backtrace | First ten backtrace lines. The eval frame is named
claude-bridge-eval. |
result goes through JSON.generate. Arrays, hashes, strings, numbers and booleans travel
intact; SketchUp entities do not. Map what you need explicitly:
Sketchup.active_model.definitions.map { |d|
{ name: d.name, count: d.count_used_instances, entities: d.entities.length }
}
Coordinates of a Point3d are Length objects, and they serialize as
"12\""-style strings, not numbers. Call .to_f on anything you intend to compare or
feed to a native library.
Evaluated code runs with full write access to the model. Grouping changes into a single undoable operation keeps the model recoverable:
model = Sketchup.active_model
model.start_operation('Claude Bridge tweak', true)
begin
model.selection.grep(Sketchup::ComponentInstance).each { |i| i.material = 'red' }
model.commit_operation
rescue
model.abort_operation
raise
end
The server is driven by a UI.start_timer tick and handles requests on SketchUp's main thread. That
is what makes the SketchUp API safe to call — and it also means a slow script freezes the SketchUp window until it
returns. The client-side read timeout is 3 seconds for receiving a request; there is no cap on evaluation time, so a
runaway loop needs a force quit. Prefer many small probes over one long batch.
Do not use the bridge to dlclose and re-load a Fiddle native library (for example
Fiddle::Meshy.unload followed by another call after replacing the .dylib on disk). It
kills the SketchUp process instantly and takes the unsaved model with it. After rebuilding a native library,
restart SketchUp; test C++ changes in a standalone headless harness instead.
127.0.0.1, so nothing outside the
machine can connect.X-Claude-Bridge. A custom
header forces a CORS preflight in browsers, and the server never answers one — so a malicious web page you
happen to have open cannot drive the bridge.| Symptom | Cause and fix |
|---|---|
| Message box: port 7857 already in use |
Another bridge instance is running — often a second SketchUp window, or a previous session whose socket
was not closed. Stop the other instance, or start on another port with
Ladb::ClaudeBridge.start(port: 7900). |
curl reportsconnection refused |
The server is stopped. Click the toolbar button and check it becomes checked, or evaluate
Ladb::ClaudeBridge.running? in the Ruby console. |
403 Missing x-claude-bridge header |
The -H 'X-Claude-Bridge: 1' argument is missing from the request. |
| Toolbar button check state looks stale | SketchUp re-runs validation procs only on certain UI events. On SketchUp 2017 (no
UI.refresh_toolbars) the state can lag one action behind; the console log is authoritative. |
| No toolbar after install | The loading policy blocked the unsigned extension. Set Extension Manager › Manage › Loading policy to Unrestricted and restart SketchUp. |
| SketchUp is frozen | An evaluated script is still running on the main thread. Wait for it, or force quit if it is an infinite loop. |
ladb_claude_bridge.rb extension registrar (Ladb::ClaudeBridge)
ladb_claude_bridge/
main.rb HTTP server, eval, toolbar and menu
img/icon.svg toolbar icon
doc/ladb_claude_bridge.pdf this document