| name | opensim-mcp |
| description | Control OpenSimulator via MCP - start/stop the simulator, manage regions, terrain, users, and assets through REST console commands |
| version | 1.0.0 |
| category | gaming |
OpenSimulator MCP Server Skill
Controls OpenSimulator via REST console. The MCP server translates tool calls into OpenSim console commands via HTTP.
Local Setup
- OpenSim install:
~/opensimulator/
- MCP repo:
~/Documents/git/opensim-server-mcp/ (symlinked)
- REST port: 9000 (http_listener_port)
- Console auth: user=
Test, pass=secret
MCP Server Invocation (Critical)
The MCP server has NO __main__ module. The pyproject.toml entry point is opensim_mcp.server:main, not opensim_mcp.__main__. All startup methods must invoke it as:
cd ~/Documents/git/opensim-server-mcp
OPENSIM_CONSOLE_USER=Test OPENSIM_CONSOLE_PASS=secret \
.venv/bin/python -c "from opensim_mcp.server import main; main()"
Or via the installed script (which works correctly):
~/.hermes/skills/opensim/opensim-mcp/scripts/start-mcp.sh
Never use: python -m opensim_mcp (fails with No module named opensim_mcp.__main__).
Startup/Shutdown Scripts
Scripts are in scripts/ subdirectory.
| Script | Purpose |
|---|
scripts/start-opensim.sh | Launch OpenSimulator in background with REST console |
scripts/stop-opensim.sh | Graceful shutdown via console quit command |
scripts/restart-opensim.sh | Stop then start |
scripts/opensim-status.sh | Check if running and show info |
scripts/start-mcp.sh | Start the MCP bridge server |
Directory Structure (Updated)
The OpenSim skill library is organized under a single umbrella:
~/.hermes/skills/opensim/
├── opensim-mcp/ # MCP REST bridge — console control, world queries
│ ├── SKILL.md
│ ├── scripts/ # start/stop/status scripts, lsl-search.sh
│ ├── references/ # LSL docs, console commands, prim creation XML
│ └── opensim-server-mcp/ # symlink → ~/Documents/git/opensim-server-mcp
└── opensim-asset-creation/ # In-world LSL/OSSL scripts, IAR/BVH tools
├── SKILL.md
├── scripts/ # master-controller.lsl, rezzer, trigger, give, etc.
└── references/ # asset/anim/library docs, script fixes
The opensim-asset-creation skill contains ready-to-deploy LSL scripts for in-world object creation, animation triggers, particle systems, inventory give, and media URL. The opensim-mcp skill provides console-level control via REST.
Path migration (2025-05-09): Both skills were moved from minecraft/opensim-* to opensim/*. All internal script paths and SKILL.md references were updated.
sed -i 's/\r$//' ~/opensimulator/bin/opensim.sh
chmod +x ~/opensimulator/bin/opensim.sh
cd ~/opensimulator/bin && ./opensim.sh -console=rest &
sleep 5 && curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9000/
~/.hermes/skills/opensim/opensim-mcp/scripts/start-mcp.sh
Or use the helper scripts:
~/.hermes/skills/opensim/opensim-mcp/scripts/start-opensim.sh
~/.hermes/skills/opensim/opensim-mcp/scripts/start-mcp.sh
Note: The opensim.sh wrapper is fragile — it calls dotnet OpenSim.dll without an absolute path, so it only works when cd'd into bin/. The start script uses the direct form: /usr/bin/dotnet OpenSim.dll -console=rest.
MCP Tools (three only)
| Tool | Use for |
|---|
get(command, args="") | Read-only queries: show, info, stats |
set(command, args="") | Mutations: create, delete, save, load, restart |
run(command) | Any raw console command |
Common Command Categories
Server Info
get("show version")
get("show uptime")
get("show info")
get("show stats")
Regions
get("show regions") / get("show region")
set("change region", "RegionName")
set("create region", "NewRegion Regions/NewRegion.ini")
set("restart") / set("region restart notice", '"Msg" 120 60 30')
Terrain
set("terrain load", "terrain.png")
set("terrain save", "terrain.png")
set("terrain fill", "25")
set("terrain elevate", "5.0")
get("terrain show", "128 128")
Archives (OAR/IAR)
set("save oar", "backup.oar")
set("load oar", "backup.oar")
set("save iar", "First Last /Inventory password archive.iar")
set("load iar", "First Last /Inventory password archive.iar")
Users
get("show users") / get("show users", "full")
set("alert", "Server restarting in 5 minutes")
set("kick user", "First Last Please relog")
set("teleport user", "First Last RegionName")
Objects
get("show object name", '"Object Name"')
set("delete object name", '"Object Name"')
set("backup")
Scripts
set("scripts stop") / set("scripts start")
set("scripts suspend") / set("scripts resume")
get("scripts show")
Land
get("land show")
set("land clear")
Config
get("config show")
get("config get", "Network")
set("config set", "Section Key Value")
Debug/Logging
get("get log level")
set("set log level", "debug")
set("debug http", "in 5")
Shutdown
set("quit") or set("shutdown") - graceful stop
Environment Variables
| Variable | Default | Description |
|---|
OPENSIM_PATH | ~/opensimulator | OpenSim installation path |
OPENSIM_CONSOLE_URL | http://127.0.0.1:9000 | REST console URL |
OPENSIM_CONSOLE_USER | Test | REST console username |
OPENSIM_CONSOLE_PASS | secret | REST console password |
Prerequisites
-
OpenSim must be running with REST console:
cd ~/opensimulator/bin && ./opensim.sh -console=rest
-
REST console must be configured in OpenSim.ini:
[Network]
ConsoleUser = "Test"
ConsolePass = "secret"
console_port = 9000
-
MCP server must start AFTER OpenSim is running and listening on port 9000.
What This MCP CAN Do
| Can Do | How |
|---|
| Query avatar/world state | get("show users", "full") |
| Manage existing objects | get("show object name", ...) / set("delete object name", ...) |
| Terrain, OAR/IAR, config, scripts | Full CRUD via set/get |
| Restart/shutdown regions | set("restart") / set("quit") |
What This MCP CANNOT Do
Object creation via console does NOT work. The load xml2 command appears to succeed (returns to prompt silently) but does NOT add objects to the scene — no error, but show object name <name> returns nothing. This is a confirmed OpenSim 0.9.3.0 bug. The create-prim.sh script will not produce actual objects.
Workaround: use viewer or LSL script — rez a prim manually, add a script with llRezObject() to spawn additional objects from inventory. Console has no working object creation command.
create cylinder → "Invalid command" (expected)
load xml2 → silent success, no object added
create-prim.sh → uses load xml2, does not work
Creating Prims
Console-based prim creation does not work. load xml2 silently fails to add objects. Use the viewer to rez prims, or use an LSL/OSSL script with llRezObject() inside a rezzer prim.
In-world approach (llRezObject):
- Rez a prim and add an LSL script to it
- The script uses
llRezObject() to spawn objects from the prim's inventory at runtime
- See
references/lsl-functions.md → llRezObject for the full signature
XML2 Format Requirements (Pitfalls)
The SceneObjectGroup XML must follow these requirements precisely or OpenSim returns Error parsing object:
- Root element:
<scene> with one <SceneObjectGroup> child
- SceneObjectPart: must have
xmlns:xsi and xmlns:xsd attributes:
<SceneObjectPart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
- UUID nesting: UUID fields use a WRAPPER element:
<UUID><UUID>aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee</UUID></UUID>
NOT <UUID>...</UUID> directly. This applies to all UUID fields (CreatorID, OwnerID, etc.)
- ParentID: for root (unlinked) objects, use a PLAIN INTEGER
0, NOT a UUID element:
<ParentID>0</ParentID>
- Minimum required fields: UUID, LocalId, Name, GroupPosition, Scale, PCode (=9 for prim), Shape (with PathCurve + ProfileShape), Material, CreatorID, OwnerID, CreationDate, ParentID, ParentPartID
- Cylinder specific: PathCurve=16, ProfileShape=Circle (ProfileCurve=1)
- Color: embedded in
<Color> element with R/G/B/A sub-elements — this sets the prim color directly
- Encoding: UTF-8 (no BOM) or ASCII works
Why earlier approaches failed:
<ParentID><UUID>...</UUID></ParentID> → "Error parsing element ParentID" — wrong structure
- Missing
xmlns:xsi → deserialization fails silently
<UUID>uuid-string</UUID> (bare) → wrong, needs <UUID><UUID>...</UUID></UUID>
- Wrong order or missing required elements →
Tried to add null scene object
Getting a working template: save xml2 /tmp/allprims.xml exports all current objects in a correctly-formatted XML2 format. Use this as a template — copy an object's XML subtree, change UUIDs and position fields, load it back with load xml2.
Finding avatar position: parse position from show users output.
Avatar/World Query Workflow
Get full avatar + world info in one session:
SESSION=$(curl -s -X POST http://127.0.0.1:9000/StartSession/ \
-d "USER=Test" -d "PASS=secret" | grep -oP '<SessionID>\K[^<]+')
curl -s -X POST http://127.0.0.1:9000/SessionCommand/ \
-d "ID=$SESSION" -d "COMMAND=show users full"
curl -s -X POST http://127.0.0.1:9000/ReadResponses/$SESSION/
curl -s -X POST http://127.0.0.1:9000/CloseSession/ -d "ID=$SESSION"
LSL Knowledge Base
LSL (Linden Scripting Language) is the in-world scripting language for OpenSimulator.
The KB is built from language_guide.pdf (Linden Lab, 2003) + sl-basics.pdf.
Search the KB
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh "llRezObject"
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh "collision"
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh "timer event"
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh --fn llSetScale
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh --fn llRezObject
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh --event touch_start
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh --event timer
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh --types
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh --ops
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh --categories
~/.hermes/skills/opensim/opensim-mcp/scripts/lsl-search.sh --rebuild
Reference Files
| File | Content |
|---|
references/lsl-functions.md | 204 LSL functions with signatures and descriptions |
references/lsl-events-states.md | All events (touch, collision, timer, sensor, etc.) and state mechanics |
references/commands.md | OpenSim console command reference |
references/opensim-library-assets.md | Built-in library animations, gestures, body parts, clothing — with test script |
references/opensim-mcp-rest-bridge.md | MCP REST bridge debugging, session-based curl fallback |
references/ariadne4j-params.md | Ariadne4j asset editor params: particle systems, RLV, animations, MVP medical equipment, chat channels, attachment points |
LSL Quick Reference
Core types: integer float string key vector <x,y,z> rotation <x,y,z,s> list
Creating objects in-world: No console command exists. Use an LSL script with llRezObject() — put the script in a prim, the script rezzes new objects from inventory. See references/lsl-functions.md → llRezObject.
Common pattern — rez a brown tall cylinder at target position:
default {
state_entry() {
vector target = <129.5, 130.0, 26.0>; // where to rez
llRezObject("CylinderTemplate", target, <0,0,0>, ZERO_ROTATION, 0);
}
on_rez(integer param) {
llSetScale(<0.5, 0.5, 8.0>); // 8m tall
llSetColor(<0.55, 0.27, 0.07>, ALL_SIDES); // brown
}
}
Useful constant quick ref: TRUE=1 FALSE=0 NULL_KEY="" ALL_SIDES=-1 PI=3.14159
Key LSL Concepts
- Everything is event-driven — scripts run in response to events (touch, timer, collision, etc.)
- States — scripts have states,
default is the entry point. Switch with state otherstate;
- Functions are ll-prefixed —
llSetScale(), llSay(), llGetPos(), etc.
- Permissions — many actions require user permission via
llRequestPermissions()
- Channels — chat uses channels. Channel 0 is public. Other channels are for scripts only.
OpenSim-Specific: OSSL (OpenSimulator Scripting Language)
OpenSim extends LSL with additional functions (OSSL). Key extensions:
osSetPenColor(string color) — set drawing color
osGetAgents() — list avatars in region
osNpcCreate/Remove/MoveTo — NPC/obot management
osSetRegionWaterHeight(height) — set water level
osTerrainHeight(x, y) — get terrain height
- See OpenSim wiki for full OSSL docs
Troubleshooting
MCP fails to connect
- The MCP server manages session automatically. If you see auth errors, restart the MCP server.
- If MCP returns 404, OpenSim may have restarted — restart the MCP server.
- Fallback: Use raw curl session commands directly (see Avatar/World Query Workflow section).
LSL Compile Errors — OpenSim LSL ≠ Standard SL LSL
These are NOT viewer or viewer-side issues — OpenSim's YEngine doesn't support some standard LSL patterns:
| Error | Cause | Fix |
|---|
(line, col) Error: undefined constant/function/variable break | break keyword doesn't exist in OpenSim | Exit loop via i = count; or restructure |
(line, col) Error: undefined constant/function/variable llClamp | llClamp() doesn't exist | Manual: if(v<min)v=min; if(v>max)v=max; |
(line, col) Error: undefined constant/function/variable llGetInventoryType(string,integer) | Wrong arg count | Use llGetInventoryType(name) with 1 arg only |
(line, col) Error: undefined name llStringTrim(string) | llStringTrim() doesn't exist in OpenSim YEngine | Write a manual trim helper or use bare string checks |
llDialog: Button label cannot be blank | Empty strings in button list | Remove blank " " separators, use only valid button names |
Loop Exit (no break)
// WRONG - break doesn't exist in OpenSim LSL:
for (i = 0; i < count; i++) {
if (found) break; // COMPILE ERROR
}
// CORRECT:
integer found = -1;
for (i = 0; i < count; i++) {
if (llGetInventoryName(INVENTORY_OBJECT, i) == target) {
found = i;
i = count; // Exit loop
}
}
Manual Clamp (no llClamp) / Manual Trim (no llStringTrim)
// WRONG - llClamp doesn't exist:
float val = llClamp(value, 0.0, 1.0); // COMPILE ERROR
// CORRECT:
float val = value;
if (val < 0.0) val = 0.0;
if (val > 1.0) val = 1.0;
// WRONG - llStringTrim doesn't exist:
string t = llStringTrim(input); // COMPILE ERROR
// CORRECT (if you need trim):
string t = input; // Use bare string comparison, or:
if (llGetSubString(t, 0, 0) == " ") t = llGetSubString(t, 1, -1); // strip leading space
Note: In most cases you can skip trim entirely — if (str != "") works without trimming. Only add manual trim if the user explicitly passes trimmed text to another function that requires it.
llDialog Button List (no blank buttons)
// WRONG - blank strings cause error:
list buttons = ["OK", "", "──────────", "", "Help"]; // ERROR
// CORRECT - no blank entries:
list buttons = ["OK", "Status", "Help"];
Particle System Constants — NOT Supported in OpenSim LSL
OpenSim's YEngine does not support many particle constants that work in standard SL LSL. If you get Error: undefined constant/function/variable PSYS_*, that constant is not available — remove it from the particle list.
Known unsupported constants:
| Constant | Purpose | Fix |
|---|
PSYS_PART_FOUR_SOURCE_DEPTH | Depth flag | Remove; use PSYS_PART_EMISSIVE_MASK alone |
PSYS_BF_SOURCE_ALPHA | Blend source | Remove |
PSYS_BF_ONE_MINUS_SOURCE_ALPHA | Blend dest | Remove |
PSYS_PART_START_GLOW / PSYS_PART_END_GLOW | Glow values | Remove |
PSYS_SRC_BURN_COLOR | Burn color | Remove |
PSYS_SRC_OUTER_TARGET_ALPHA | Outer alpha target | Remove |
PSYS_SRC_INNER_TARGET_ALPHA | Inner alpha target | Remove |
PSYS_SRC_OMEGA | Angular velocity | Remove |
Working particle template (fire preset — ALL numeric constants, no symbolic names):
// Fire effect — NO symbolic PSYS constants in OpenSim 0.9.3.0
llParticleSystem([
PSYS_PART_FLAGS, 256, // EMISSIVE
PSYS_PART_MAX_AGE, 2.0,
PSYS_PART_START_SCALE, <0.5, 0.5, 0.0>,
PSYS_PART_END_SCALE, <0.1, 0.1, 0.0>,
PSYS_PART_START_COLOR, <1.0, 0.5, 0.3>,
PSYS_PART_END_COLOR, <0.9, 0.2, 0.1>,
PSYS_PART_START_ALPHA, 1.0,
PSYS_PART_END_ALPHA, 0.0,
PSYS_SRC_PATTERN, 2, // EXPLODE
PSYS_SRC_MAX_AGE, 0,
PSYS_SRC_TEXTURE, ""
]);
For cone/angle patterns (e.g. fountain, bubble, heart), use:
PSYS_SRC_PATTERN, 4, // ANGLE_CONE (not PSYS_SRC_PATTERN_CONE)
Full working presets (6 types — fire, smoke, fountain, bubble, sparkle, heart):
See opensim-asset-creation/scripts/master-controller.lsl → applyParticlePreset() function for the complete implementation with all 6 presets using only numeric constants.
Known unsupported constants (all symbolic PSYS names fail in OpenSim YEngine):
| Constant | Fix |
|---|
PSYS_SRC_PATTERN_EXPLODE | Use 2 |
PSYS_SRC_PATTERN_CONE | Use 4 |
PSYS_PART_EMISSIVE_MASK | Use 256 |
PSYS_SRC_LIFETIME | Remove — does not exist |
PSYS_SRC_ANGLE_BEGIN / PSYS_SRC_ANGLE_END | Remove — do not exist |
PSYS_PART_FOUR_SOURCE_DEPTH | Remove |
PSYS_BF_SOURCE_ALPHA / PSYS_BF_ONE_MINUS_SOURCE_ALPHA | Remove |
PSYS_PART_START_GLOW / PSYS_PART_END_GLOW | Remove |
PSYS_SRC_BURN_COLOR, PSYS_SRC_OUTER_TARGET_ALPHA, PSYS_SRC_INNER_TARGET_ALPHA, PSYS_SRC_OMEGA | Remove |
See opensim-asset-creation skill for the complete working particle system implementation.
See opensim-asset-creation skill for fixed in-world scripts (master-controller.lsl, etc.) that work with OpenSim's YEngine.
LSL Compile Errors ("undefined constant/function/variable")
- OpenSim LSL differs from standard SL LSL — these common patterns fail:
break in loops → use i = count; to exit
llClamp(x,min,max) → use if(x<min)x=min;if(x>max)x=max;
llGetInventoryType(name, type) → use llGetInventoryType(name) (1 arg only)
- See skill
opensim-asset-creation for the fixed rezzer script.
Session-based curl works when MCP fails
SESSION=$(curl -s -X POST http://127.0.0.1:9000/StartSession/ \
-d "USER=Test" -d "PASS=secret" | grep -oP '<SessionID>\K[^<]+')
curl -s -X POST http://127.0.0.1:9000/SessionCommand/ \
-d "ID=$SESSION" -d "COMMAND=show users full"
curl -s -X POST http://127.0.0.1:9000/ReadResponses/$SESSION/
opensim.sh fails with "cannot execute: required file not found"
- The wrapper has CRLF line endings from Windows/mixed origins. Fix with:
sed -i 's/\r$//' ~/opensimulator/bin/opensim.sh && chmod +x ~/opensimulator/bin/opensim.sh
- The wrapper also uses relative
dotnet OpenSim.dll so it only works from the bin/ directory. Use the direct form instead:
/usr/bin/dotnet OpenSim.dll -console=rest
Port 9000 occupied after crash
- If OpenSim crashed, an orphaned dotnet process may still hold port 9000.
- Find it:
ss -tlnp | grep 9000 (shows PID/command) or lsof -i :9000
- Kill:
kill <PID> then restart OpenSim
- Two dotnet processes can coexist — one with
-console=rest (MCP bridge), one without (region server). Port 9000 is bound by whichever starts first. A second region server fails "Address already in use" even if the first had no console flag.
- Which instance is active? —
tail -n 3 ~/opensimulator/bin/OpenSim.log. If timestamps match current time, it's the live one. Stale entries = that instance crashed and a new dotnet process (visible in ps aux | grep dotnet) took over.
- OpenSim log files: OpenSim writes to several logs in
bin/. Only OpenSim.log (298KB+) is active. opensim.log (22KB), opensim-console.log, opensim_direct.log are stale from previous runs.
create cylinder returns "Invalid command"
- Expected. OpenSim console has no object creation commands. Use viewer to create prims manually, or use an LSL/OSSL script with
llRezObject() inside a rezzer prim.
To modify an existing object's scale via console
set("edit scale", '"ObjectName" <x> <y> <z>') e.g. set("edit scale", '"Tree" 2 2 10')
load xml2 silently fails — object not created
- Symptom: command returns to prompt with no error, but
show object name <name> finds nothing
- Cause: OpenSim 0.9.3.0 deserializes the XML but AddSceneObject() silently drops it (suspected UUID collision or deserialization failure)
- Workaround: use viewer to create prims, or use
llRezObject() in an LSL script inside a rezzer prim
- The XML2 format itself IS correct —
save xml2 produces valid exports — the issue is on deserialization
llRequestURL() / osRequestURL() denied in standalone mode
Known PSYS pattern constant values for OpenSim 0.9.3.0:
| Constant | Value | Notes |
|---|
PSYS_SRC_PATTERN = EXPLODE | 2 | Symbolic name doesn't exist |
PSYS_SRC_PATTERN = ANGLE_CONE | 4 | Symbolic name doesn't exist |
PSYS_PART_FLAGS = EMISSIVE | 256 | Symbolic name doesn't exist |
Use numeric values directly — all symbolic PSYS constant names fail in OpenSim YEngine.