WorldBridge MCP
A standalone, portable MCP server that lets any AI assistant act as the Gaia of an
OpenSimulator region -- build, sculpt terrain, script, move avatars, and read the world -- through the
WorldBridge (worldbridge/) region module's control channel, and see the world through its own viewer.
Nothing here is tied to any one host or product: it is pure Python stdlib (no pip install),
and every connection detail is an environment variable. Load the WorldBridge module into your
OpenSim, point this MCP at your channel, and you have the same powers on your own world.
A full setup, from building the server to connecting an AI, is in
../handbook/quickstart.md.
Configure
| env var | default | meaning |
|---|---|---|
WORLDBRIDGE_URL |
http://127.0.0.1:9002 |
the WorldBridge channel base URL |
WORLDBRIDGE_TOKEN |
(none) | the bearer token, given directly |
WORLDBRIDGE_TOKEN_FILE |
(none) | a file holding the token (read when WORLDBRIDGE_TOKEN is unset). With no AuthToken in its config, WorldBridge generates a token on its first start into WorldBridge.token beside OpenSim: point this at that file |
VIEWER_URL |
http://127.0.0.1:9004 |
the viewer bridge (tools/leap_bridge.py), for the viewer_* tools |
PARACOSM_VIEWER_TOKEN |
(none) | the viewer bridge's bearer token, when it is set explicitly. Normally leave it unset: tools/run_viewer.py makes a fresh random token on every launch and the bridge writes it to the token file below, which this MCP reads on every call |
PARACOSM_VIEWER_TOKEN_FILE |
%LOCALAPPDATA%/ParacosmViewer_x64/leap_bridge/bridge.token (Windows), ~/Library/Application Support/... (macOS), $XDG_CONFIG_HOME or ~/.config/... (Linux) |
where the bridge writes its token |
VIEWER_TOKEN |
(none) | legacy name, read only when neither of the above gives a token |
viewer_look asks the bridge for a snapshot by bare file name; the bridge writes it inside its own snapshot folder
(PARACOSM_SNAPSHOT_DIR, see tools/PORTABILITY.md) and answers with the full path, which the MCP reads and deletes.
This server does not read the Paracosm .env; give it its variables in the MCP client's configuration. It reads
the WorldBridge token once, when it starts (start OpenSim once first, so the token file exists); the viewer
bridge's token is read on every call, so a relaunched viewer is picked up.
Add to an MCP client
{
"mcpServers": {
"worldbridge": {
"command": "python",
"args": ["/path/to/paracosm/mcp/worldbridge_mcp.py"],
"env": {
"WORLDBRIDGE_URL": "http://127.0.0.1:9002",
"WORLDBRIDGE_TOKEN_FILE": "/path/to/opensim/bin/WorldBridge.token"
}
}
}
}
Speaks MCP over stdio (newline-delimited JSON-RPC 2.0). Quick smoke test without a client (the second call needs the server running):
printf '%s\n%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"world_state","arguments":{}}}' \
| WORLDBRIDGE_TOKEN_FILE=/path/to/opensim/bin/WorldBridge.token python worldbridge_mcp.py
Tools (version 0.2): 26
| tool | args | does |
|---|---|---|
world_state |
-- | region name, size, avatar count, object count |
world_list |
-- | every object: uuid, name, shape, pos, size, colour |
world_heightmap |
-- | downsampled terrain height grid (64 x 64) |
world_avatars |
-- | every avatar: name, position, heading, camera look-at |
world_chat |
limit |
recent local chat typed by avatars |
world_raycast |
x,y,z, dx,dy,dz, length, land |
cast a physics ray; the hits and the objects that own them |
world_spawn |
x,y,z (req); shape box/cylinder/sphere; sx,sy,sz; r,g,b,a; name |
create a prim |
world_spawn_mesh |
file, x,y,z (req); size, name, texture |
a mesh from a meshdata JSON file on the region's disk (inside WorldBridge's FileRoots) |
world_upload_texture |
file (req), name |
store a J2C texture from the region's disk; returns its uuid |
world_set_script |
uuid, script (req), name |
put an LSL script into an object and run it |
world_move |
uuid, x,y,z (req) |
move an object |
world_recolor |
uuid (req); r,g,b,a |
repaint an object |
world_despawn |
uuid (req) |
delete an object |
world_terrain |
cx,cy,radius (req); amount; op raise/lower/set/flatten |
sculpt a circular area (smooth falloff) |
world_teleport |
x,y,z (req); lx,ly,lz; avatar |
teleport an avatar within the region |
world_save |
-- | back the scene up to the region's database |
world_contents |
uuid (req) |
what an object holds: each item's name, type (object, notecard, script, ...), item and asset ids, description, whether it can be copied |
world_rez_item |
box, item, x,y,z (req); rx,ry,rz, count, spacing, atRoot, param |
rez a copy of an object item from a box anywhere in the region; the item stays in the box and the copy's scripts start |
world_put_notecard |
uuid, name, text (req); description |
write a notecard (up to 64 KB) into an object; one of the same name is replaced |
world_roam |
uuid, walk_anim, pauses, speed, cx,cy, radius, leader, ...; or status; or uuid + off |
a rigged creature's nomadic wander over the ground |
world_call |
command (req), body |
any other WorldBridge command by name (the handbook's worldbridge.md lists them all) |
viewer_look |
focus, camera or distance/azimuth/elevation; width, height, settle |
place the AI viewer's camera and return a snapshot as an image |
viewer_camera_reset |
-- | the camera follows the avatar again |
viewer_walk |
x,y (req), z |
walk the viewer's avatar there (autopilot) |
viewer_say |
text (req), channel |
say something in local chat as the viewer's avatar |
viewer_call |
pump, op (req), ... |
any LEAP operation in the viewer |
The viewer_* tools need the AI's own viewer running with its bridge: build and start it as described in
../handbook/viewer.md.
Security
The WorldBridge module binds 127.0.0.1 only and requires the bearer token; this MCP runs on the same host and talks to that loopback channel, so there is no public attack surface by default.
RBAC (built in): the module supports role-scoped tokens -- observer (read-only: state, list,
heightmap, avatars, chat, inspect, worn, contents, roam_notecard), builder (+ spawn, recolor, rez_item,
put_notecard), gaia (everything else) --
configured as TokenObserver/TokenBuilder/TokenGaia (16 characters or more) in the module's [WorldBridge]
ini section (in Paracosm, from the .env keys WORLDBRIDGE_OBSERVER_TOKEN/_BUILDER_TOKEN/_GAIA_TOKEN through
tools/make_config.py). The generated token file holds a gaia token. Whichever token you give this MCP determines what it
can do: a builder token gets a 403 forbidden (naming the role needed) if it calls world_terrain. Give an
assistant the lowest role it needs. A leaked low token cannot wreck the world.
world_call can reach every command the token's role allows, including console when the module's
AllowConsole is true (it is false by default).
If you ever expose the channel beyond localhost, do NOT do so without also adding TLS + rate-limiting on top
of the token/RBAC (see PLAN.md "SECURITY OF THE CONTROL CHANNEL").
Not yet here
Driving a LibreMetaverse avatar (walking the world as a player) is a separate process today (see
bot/); folding it into this MCP is a planned addition. The viewer_* tools drive a real viewer
instead.