Skip to main content
Glama
Sauerbraten MCP
by pastukhov

Sauerbraten MCP

A local MCP server for editing levels in a running Sauerbraten (Cube 2) game. It exposes 22 typed tools over stdio and connects to an opt-in native bridge through a private Unix socket. Linux and single-player editing are supported. This is an unofficial extension; it is not part of an official Sauerbraten release.

The Python server lives here. Native engine changes are prepared separately for official Sauerbraten SVN r6891 and included in engine-patches. The repository contains no game assets, personal maps, profiles or credentials. An unmodified game does not provide the required socket protocol.

Build the engine

Use a complete Sauerbraten checkout, including current data/ and packages/:

svn checkout -r 6891 https://svn.code.sf.net/p/sauerbraten/code/ sauerbraten
cd sauerbraten
# Use an absolute path to this repository:
while IFS= read -r name; do
  patch -p1 < "/absolute/sauerbraten-mcp/engine-patches/$name"
done < /absolute/sauerbraten-mcp/engine-patches/series
make -C src -j2 MCP=1 client

Build dependencies: C/C++ compiler, make, SDL2, SDL2_image, SDL2_mixer, zlib, OpenGL and OpenSSL development headers/libcrypto. The engine keeps its bundled ENet. The default build and standalone server/master do not include MCP or its JSON/crypto dependencies. Enabled client objects use a separate src/.mcp/ directory, so switching build modes does not require cleaning.

Related MCP server: dsh-godot-mcp

Install and connect

Install Python >=3.11 and uv, then:

git clone https://github.com/pastukhov/sauerbraten-mcp.git
cd sauerbraten-mcp
uv sync --locked

Map validation also needs Xvfb and xauth. Validators use a separate SDL/GL client; use the same patched executable and asset installation as the live game.

Start the patched game in a terminal, from its full resource checkout:

cd /absolute/sauerbraten
umask 077
sauer_runtime=$(mktemp -d "${XDG_RUNTIME_DIR:-/tmp}/sauer-mcp.XXXXXX")
printf 'Socket: %s/editor.sock\n' "$sauer_runtime"
tools/run-editor.sh \
  --client "$PWD/src/sauer_client" \
  --data-root "$PWD" \
  --profile "$HOME/.local/share/sauerbraten-mcp" \
  --socket "$sauer_runtime/editor.sock"

The launcher writes game output to the dedicated profile's client.log. Create/open a map in the UI and enter edit mode. Configure your MCP host with these stdio command/arguments, replacing paths with absolute paths on your machine:

{
  "command": "uv",
  "args": [
    "run", "--locked", "--project", "/absolute/sauerbraten-mcp", "sauer-mcp",
    "--client", "/absolute/sauerbraten/src/sauer_client",
    "--socket", "/absolute/private-directory/editor.sock"
  ]
}

Use the socket path printed by the launcher. The server attaches to an existing game and does not start or stop it. It can reconnect after restarting. Only one peer is allowed; another receives BUSY. The socket has mode 0600, its parent must have mode 0700 and the peer must have the same UID. The bridge is disabled unless the game is started with -mcpbridge<absolute-path>.

Always pass --client explicitly for a source build. Otherwise the server checks SAUER_MCP_CLIENT, then sauer_client on PATH, then the Debian installation path /usr/lib/cube2/cube2_client. An unpatched or different executable can make validation fail even when the live connection works. MCP stdout carries only the protocol.

Tools and editing rules

Purpose

Tools

Observe

editor_state, region_read, entity_list, texture_catalog, mapmodel_catalog

Geometry

geometry_fill, geometry_face_set, geometry_vertex_set, texture_apply, material_apply

Entities

entity_create, entity_update, entity_delete

History

history_undo, history_redo

View

camera_set, view_capture

Maps and recovery

map_catalog, map_new, map_open, map_save_as, operation_status

Read editor_state first. Each mutation requires a fresh UUID request_id, current map_epoch and expected_revision. Revisions and generations are canonical decimal strings. Use catalog resource references rather than guessing IDs. Schemas are advertised through tools/list and recorded in contracts/v1.json.

If a reply is lost, retain the UUID and exact arguments and query operation_status. An unknown outcome is not permission to repeat an edit. UUID reuse with different arguments is rejected. Native manual edits and MCP edits share undo/redo, with one history step per RPC. Pending native drag/onrelease commands return BUSY.

Geometry supports aligned solid/empty fills, face deformation and vertex movement. geometry_face_set sets four absolute depths 0..8 along the selected face axis in grid/8 units; even negative faces use absolute coordinates. The order is (R,C)=00,10,01,11: X faces use Y/Z, Y faces use Z/X, Z faces use X/Y. geometry_vertex_set selects a logical corner with XYZ bits and sets its local XYZ position 0..8. Both affect every selected cell and require existing nonempty leaf cubes exactly matching the grid; fill explicitly first when establishing new geometry. They preserve textures/materials. Neighbors and coincident corners are not moved implicitly. Inverted, nonconvex and zero-volume shapes are rejected before any change. Each successful change is one shared undo step; no-ops do not consume history or clear redo. Textures can target one exact face or all faces. Entities support light, playerstart and mapmodel; attached entities/history are rejected. Reads preserve the native selection and focus. camera_set selects first-person and disables zoom; view_capture requires view_revision and returns PNG MCP ImageContent with state tokens.

Persistence and limits

map_new and map_open refuse to replace an unsaved world. Opening pins and validates the source in a separate process before committing in the game. Native loading is destructive: a failure after reset reports the new state and recovery information; ordinary edits are blocked while recovery_required is set.

map_save_as creates an immutable bundle in the dedicated profile: packages/mcp/<slug>-<request_uuid>/{level.ogz,level.cfg,manifest.json}. It does not overwrite source maps. Exports compare semantic/config fingerprints after reload and depend on this installation's trusted assets/cfg. They are not standalone media packages. Changed or shadowed dependencies block reopening before reset. A stale clean acknowledgement leaves the live map dirty. The persistent export journal records outcomes across transport failures and server restarts; keep it and published bundles for recovery.

Custom shader declarations/variants/alternate/fast/deferred definitions are not supported. Manual custom shader changes disable export/open/new for that process until a game restart; editor_state explains the reason. Standard lazy shaders remain supported. Other unreconstructible or name-dependent cfg may disable export. Native save/load is synchronous and can pause frames. Baking lighting, coop, batch, terrain/blend editing are outside this version.

Mutations allow 4096 cells, 65536 visited nodes, 8 MiB snapshot and 16 MiB inverse preparation. Reads return at most 256 leaves / 4096 nodes per page. Captures are at most 1280x720 with a 10-minute TTL and 64 MiB owned cache. There is no arbitrary CubeScript, shell or file-operation tool.

Tests

uv run --locked pytest -q
# Full integration: explicitly select the patched engine with complete assets.
SAUER_MCP_ENGINE=/absolute/sauerbraten uv run --locked pytest -q
# Native-only suite, from the engine checkout:
make -C /absolute/sauerbraten/src MCP=1 test-mcp

The first command runs isolated Python tests and explicitly skips live/native cases. The second runs the complete suite, including initialized engine cases, stdio/SDK calls, native history, camera/PNG, validated saves/reopen, persistence and recovery. Integration needs Xvfb/xauth and actual assets. Tests use temporary profiles/sockets and never attach to an existing game. To test an installed binary, set both SAUER_MCP_CLIENT and SAUER_MCP_DATA instead; the source fixture comparison is then skipped. Explicit invalid paths are errors rather than silent skips. The native suite needs only Python 3 stdlib, not the MCP SDK.

The Python dependencies are locked in uv.lock; the MCP SDK is pinned to mcp==1.26.0. Extension code uses the zlib license. The engine patch series preserves Sauerbraten's notices and the MIT license of vendored cJSON.

Available Tools

22 tools
camera_setB

Set edit camera coordinates, yaw/pitch and FOV; returns a view_revision for capture.

Inputs: yaw (required), pitch (required), position (required), map_epoch (required), +3 more.

editor_stateA
Read-onlyIdempotent

Read current map/session/revision, camera and editor capabilities. Read before mutations.

Inputs: no named parameters.

entity_createA

Create light/playerstart/mapmodel at explicit coordinates with five attrs; no camera/drop fixups.

Inputs: type (required), attrs (required), position (required), map_epoch (required), +3 more.

entity_deleteC
Destructive

Delete a referenced entity as one undoable step.

Inputs: map_epoch (required), entity_ref (required), request_id (required), expected_revision (required).

entity_listC
Read-onlyIdempotent

List entities and generation-bound references, optionally in world bounds.

Inputs: type, limit, bounds, offset, +2 more.

entity_updateC

Replace position/attrs of a referenced entity. Attached entities are unsupported.

Inputs: attrs (required), position (required), map_epoch (required), entity_ref (required), +3 more.

geometry_face_setB
Destructive

Set four absolute face-axis depths (0..8 in grid/8 units) on each exact nonempty leaf. Face=selection.orient; order (R,C)=00,10,01,11; X uses Y/Z, Y uses Z/X, Z uses X/Y. Atomic and undoable; invalid/zero-volume shapes rejected.

Inputs: depths (required), map_epoch (required), selection (required), request_id (required), +1 more.

geometry_fillC
Destructive

Replace an aligned selection with solid or empty cubes. count is in cells, grid in world units.

Inputs: mode (required), map_epoch (required), selection (required), request_id (required), +2 more.

geometry_vertex_setB
Destructive

Set one logical corner (XYZ bits) to absolute local XYZ coordinates 0..8 in grid/8 units on each exact nonempty leaf. Neighbors/coincident corners are not moved implicitly. Atomic and undoable; invalid shapes rejected.

Inputs: corner (required), position (required), map_epoch (required), selection (required), +2 more.

history_redoC

Redo the top shared manual/MCP history step; read state afterwards.

Inputs: map_epoch (required), request_id (required), expected_revision (required).

history_undoB

Undo the top shared manual/MCP history step; read state afterwards.

Inputs: map_epoch (required), request_id (required), expected_revision (required).

map_catalogB
Read-onlyIdempotent

List trusted local maps. Use opaque map_id values with map_open.

Inputs: limit, offset, search.

mapmodel_catalogB
Read-onlyIdempotent

List mapmodel registry references for entity_create/update.

Inputs: limit, offset, map_epoch, generation.

map_newB
Destructive

Create a native map (bottom half solid). Scale 10..16; refuses unsaved changes.

Inputs: scale (required), map_epoch (required), request_id (required), expected_revision (required).

map_openB
Destructive

Validate and open a catalog map or saved artifact; refuses an unsaved map.

Inputs: map_epoch (required), request_id (required), expected_revision (required), map_id, +1 more.

map_save_asC

Validate reload and publish an immutable map snapshot; returns revision_saved and conditional clean_ack.

Inputs: slug (required), map_epoch (required), request_id (required), expected_revision (required).

material_applyC

Apply a built-in volume/clip material to an aligned selection.

Inputs: material (required), map_epoch (required), selection (required), request_id (required), +1 more.

operation_statusA
Read-onlyIdempotent

Look up a mutation UUID; unknown means do not blindly repeat it.

Inputs: request_id (required).

region_readC
Read-onlyIdempotent

Read octree leaves in half-open world bounds, without subdividing geometry.

Inputs: bounds (required), cursor.

texture_applyC

Set exactly one orientation (0..5) or all six faces of an aligned selection.

Inputs: faces (required), map_epoch (required), selection (required), request_id (required), +2 more.

texture_catalogB
Read-onlyIdempotent

List texture references. Preserve map_epoch and vslot_generation when applying.

Inputs: limit, offset, map_epoch, generation.

view_captureC
Read-onlyIdempotent

Capture the requested map/view revision as PNG image content.

Inputs: map_epoch (required), view_revision (required), expected_revision (required).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 22 tools

    Disambiguation4/5

    Each tool targets a distinct resource+action: entities (create/update/delete/list), geometry (fill/face_set/vertex_set), textures (catalog/apply), maps (new/open/save_as/catalog), and history (undo/redo). Minor possible confusion between material_apply vs texture_apply and between the three *_catalog tools, but descriptions clearly scope them to different domains. The three geometry mutation tools are clearly separated by the type of shape manipulation.

    Naming Consistency4/5

    Dominant pattern is noun_verb (entity_create, geometry_fill, texture_apply, map_open), applied consistently across domains. A few outliers like editor_state, operation_status, and map_save_as are noun_noun or verb-phrase forms rather than the strict verb at end, but still readable and predictable.

    Tool Count4/5

    22 tools is above the ideal 3-15 band but justified by a rich 3D map editor spanning entities, geometry, textures, maps, camera, and history. Each domain contributes a small, purposeful cluster rather than redundant tools, so the count feels earned though slightly heavy.

    Completeness4/5

    Covers a strong lifecycle: map new/open/save/catalog, entity CRUD+list, geometry mutation, texture catalog/apply, material apply, camera, history undo/redo, operation_status, and region_read for readback. Minor gaps exist (no material catalog listing, no explicit entity get-by-ref beyond entity_list), but agents can work around these.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables full control of the Godot engine from any MCP client through three layers: headless project, scene, script, settings and export operations, live editor edits that remain undoable, and inspection of a running game with input injection, frame-stepped digests and screenshots. Each layer also exposes an arbitrary-GDScript escape hatch so unanticipated tasks can still be performed.
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables any MCP client to drive the Fortnite map editor through typed, safety-validated tools — placing and tagging props, renaming assets with Verse-reference pre-flight, authoring prefabs, exporting textures for visual identification, and searching the project's Verse API digests. An in-editor listener executes the operations on the editor's main thread over HTTP.
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Connects an MCP client to a running Serious Editor 2017 / Serious Sam Fusion instance through a local Lua bridge, enabling package and entry inspection, log reading, editor diagnostics, and previewed/undoable entity property edits on a captured selection.
      14
      MIT