tm-control-mcp
GitHub

Openplanet plugin for Trackmania Latest release

Drive Trackmania from a script.

tm-control-mcp opens a localhost JSON socket into the game. AI agents and Python scripts create maps, drive the main menu, read race state, capture screenshots and check that what they did actually happened.

Socket
127.0.0.1:30006
Wire
one JSON line in, one out
Auth
none — localhost only

Example session

7 stops

  1. START status

    Is the socket up? The reply names the loaded plugin, its version and id.

    python3 tools/call.py status
    {"route":"status"}
    {"ok":true,"id":null,"route":"status","error":"","data":{"alive":true,"port":30006,…}}
  2. CP1 EditNewMap

    Open a new Stadium map in the editor (default vista). If leaving a dirty map pops the unsaved-changes dialog, it warns instead of guessing.

    python3 tools/call.py EditNewMap '{"environment":"Stadium"}'
    {"route":"call","tool":"EditNewMap","input":{"environment":"Stadium"}}
    {"ok":true,"id":null,"route":"call","error":"","data":{"tool":"EditNewMap","result":{"success":true,"output":{…}}}}
  3. CP2 WaitUntil

    Stop guessing whether the editor is ready. Loading screens pause the clock; a miss returns timedOut, not a crash.

    python3 tools/call.py WaitUntil '{"condition":"editorReady","timeoutMs":20000}'
    {"route":"call","tool":"WaitUntil","input":{"condition":"editorReady","timeoutMs":20000}}
    {"ok":true,"id":null,"route":"call","error":"","data":{"tool":"WaitUntil","result":{"success":true,"output":{…}}}}
  4. CP3 PlaceBlock

    Place a grid block. Mutating tools return mapPre and mapPost, so the client sees exactly what changed.

    python3 tools/call.py PlaceBlock '{"blockName":"RoadTechStraight","x":24,"y":12,"z":24}'
    {"route":"call","tool":"PlaceBlock","input":{"blockName":"RoadTechStraight","x":24,"y":12,"z":24}}
    {"ok":true,"id":null,"route":"call","error":"","data":{"tool":"PlaceBlock","result":{"success":true,"output":{"mapPre":{…},"mapPost":{…}}}}}
  5. CP4 TakeScreenshot

    Native viewport capture with overlay rendering switched off for the shot. Waits for the file and returns its game-side path and size.

    python3 tools/call.py TakeScreenshot '{"hideOverlay":true}'
    {"route":"call","tool":"TakeScreenshot","input":{"hideOverlay":true}}
    {"ok":true,"id":null,"route":"call","error":"","data":{"tool":"TakeScreenshot","result":{"success":true,"output":{"fullName":…,"sizeBytes":…}}}}
  6. CP5 SaveMapAs

    Save under the user Maps folder. If a dialog was up, the reply carries a warning rather than pretending it saved.

    python3 tools/call.py SaveMapAs '{"name":"agent-demo"}'
    {"route":"call","tool":"SaveMapAs","input":{"name":"agent-demo"}}
    {"ok":true,"id":null,"route":"call","error":"","data":{"tool":"SaveMapAs","result":{"success":true,"output":{…}}}}
  7. FINISH BackToMainMenu

    Unwind the editor back to the main menu. Stops early on the unsaved-changes dialog instead of spinning.

    python3 tools/call.py BackToMainMenu
    {"route":"call","tool":"BackToMainMenu","input":{}}
    {"ok":true,"id":null,"route":"call","error":"","data":{"tool":"BackToMainMenu","result":{"success":true,"output":{…}}}}

01 What it controls

The editor, the menu and the race — as named tools.

Every tool is a name plus a JSON input. Ask the live plugin for the full catalogue with {"route":"tools"}; DEV builds add diagnostics that release .op packages leave out.

Map editor

Open, create and save maps, read blocks and items, move the cursor and camera, place grid blocks, undo and redo, bake shadows, and capture the viewport.

  • OpenMapInEditor
  • GetMapInfo
  • SaveMapAs
  • PlaceBlock
  • GetBlocks
  • ControlCamera
  • ComputeShadows
  • TakeScreenshot

Main menu

Push menu routes, inspect UI layers and click real controls. Clicks go through CControlBase::OnAction, the same dispatch as a mouse click — never the crash-prone TriggerPageAction.

  • SetMenuPage
  • ClickMenuButton
  • CreateMapViaMenu
  • EditNewMap
  • PlayMap
  • BackToMainMenu

Race & play

Read race data, players and server info; inspect vehicle state; place and aim the in-play free camera exactly on a target.

  • GetRaceData
  • GetPlayers
  • GetServerInfo
  • GetVehicleState
  • ControlPlayCamera

Built for agents

  • Readiness. GetReadiness and WaitUntil answer “can I act yet?”; call.py --until-ready wraps them.
  • Structured errors. Failures can carry code, retryable, requiredMode and hint.
  • Verification. AssertPlacement checks block and item deltas after a change.
  • In-plugin guides. ListGuides / GetGuide teach menu routes, screenshots, cleanup and more.

Openplanet itself

  • Plugins & settings. List, load, reload and configure plugins; it refuses to disable or unload itself.
  • ManiaScript. RunManialinkScript injects through MLHook into menu, playground or editor, with an optional result channel.
  • Reflection & files. GetTypeInfo dumps live classes; ControlFids browses and extracts game files.
  • Overlay. ControlOverlay hides Openplanet's overlay before camera or pixel work.

Via the Editor++ pack

  • Free placement. Freeblocks and flying items at exact positions and rotations.
  • Named macroblocks. Compose in memory, preflight, place with offset and pivot, save as durable JSON.
  • Tags & cleanup. Tag what an agent placed, then remove only that — not the whole map.
See tool packs
Trackmania map editor viewport: yellow pillar items placed on a dark ground, with the editor's green toolbar along the bottom and the yellow Modes button and block folders on the left.
Real capture The map editor as returned by TakeScreenshot. From the repository's docs/images.

02 Protocol

One TCP connection, one JSON line each way.

It is a plain localhost TCP socket, not an HTTP server. Send one newline-terminated JSON object; read one JSON reply; the plugin closes the connection.

Routes
RequestDoes
{"route":"status"}Liveness, host, port, plugin version
{"route":"tools"}Every registered tool with its JSON schema
{"route":"call","tool":"GetMode","input":{}}Run one tool
{"route":"GetMode","input":{}}Shorthand: tool names work as routes

Replies

ok success

{"ok":true,"id":null,"route":"call","error":"","data":{"tool":"GetMode","result":{"success":true,"output":{"mode":"MapEditor"}}}}

err a tool that failed: see data.result

{"ok":true,"id":null,"route":"call","error":"","data":{"tool":"PlaceBlock","result":{"success":false,"error":"…","code":"NOT_IN_EDITOR","retryable":true,"requiredMode":"Editor","hint":"…"}}}

Python, stdlib only

import json, socket

def call(tool, payload=None, port=30006):
    req = {"route": "call", "tool": tool, "input": payload or {}}
    with socket.create_connection(("127.0.0.1", port)) as s:
        s.sendall((json.dumps(req) + "\n").encode())
        s.shutdown(socket.SHUT_WR)
        reply = b""
        while chunk := s.recv(65536):
            reply += chunk
    return json.loads(reply)

print(call("GetMode"))

Or use the bundled client, which adds process checks, readiness waits, schema validation (--strict) and screenshot path detection: python3 tools/call.py --pretty GetMode.

03 Install

Five steps to a live socket.

  1. Install Openplanet

    Get Openplanet for Trackmania. Native Windows and Proton/Wine installs both work.

  2. Add the dependencies

    Required: MLHook and MLFeed: Race Data (MLFeedRaceData). Optional: VehicleState and Camera. Editor++ is only needed by the E++ tool pack.

  3. Install the plugin

    Download tm-control-mcp-<version>.op from the latest release into Openplanet's Plugins folder and enable it.

  4. Get the client

    The command-line client tools/call.py lives in the repository and needs Python 3 (standard library only). It checks for the game on Linux/Proton and native Windows. Clone it:

    Shell, get the client

    git clone https://github.com/clankercode/tm-control-mcp.git
    cd tm-control-mcp

    Building from source instead of step 3? In the clone, ./build.sh dev stages the plugin into ~/OpenplanetNext/Plugins by default (override with PLUGINS_DIR).

  5. Check the socket

    With Trackmania running and the plugin enabled, from the clone:

    Shell, verify

    python3 tools/call.py status
    python3 tools/call.py GetMode

    On Windows, use py -3 in place of python3 if that is how your Python installation launches.

Host, port and the listener toggle live under Openplanet Settings → TM Control MCP → Server and apply without a reload. Keep the host at 127.0.0.1.

04 Tool packs

Add tools without touching the bridge.

A tool pack is a separate Openplanet plugin that depends on tm-control-mcp and registers its tools at runtime as packId.FuncName. The bridge never imports a pack. ListToolPacks shows what is loaded.

Known packs
PackAdds
Editor++Pack id: tm-mcp-pack-epp Editor++ placement as tools: freeblocks and flying items, named macroblocks with skins, provenance tags and tag-based cleanup, inventory and edit-mode control, batch place and delete.
VisualPack id: Visual Point the camera and shoot clean screenshot series: preset and orbit framing, exact camera restore, host-side crops, grids and contact sheets.
Ghosts++Pack id: GhostsPP Ghosts++ diagnostics: a watch loop, spectating, clip snapshots and loaded-ghost state.
MLHookPack id: MLHook MLHook's exports as tools: Manialink injection and messaging, custom events, and buffered event capture.
Map TogetherPack id: MapTogether Drive Map Together rooms: create, invite and join, chat, admin actions, and desync testing.

Writing one? Start with docs/tool-packs.md and the fixture pack in tools/fixtures/. The small monospace label beside each name is its pack id, the prefix its tools use.

05 Updates

One text file says what's latest.

The bridge and each pack can publish a tiny latest-version.txt next to their site. The bridge checks those files politely in the background and reports the result through ListToolPacks. It never downloads or installs plugins.

For pack authors

AngelScript, pack Main()

auto b = TmMcp::ToolPackBuilder("MyPack");
b.SetLatestReleaseUrl("https://YOUR-ACCOUNT.github.io/YOUR-PACK/latest-version.txt");
  • The file holds one semantic version and nothing else, such as 0.2.0. A leading v is accepted.
  • Direct public HTTPS URL: no redirects, queries or ports.
  • Ship it in the same GitHub Pages deployment as your site, or one will replace the other.
  • The installed version always comes from your info.toml.

Full contract and a Pages workflow template: docs/pack-updates.md. Authoring basics: docs/tool-packs.md.

What clients read

installedVersion
From the pack plugin's own manifest.
latestVersion
Last valid version file result; empty if unknown.
updateAvailable
True only for a known newer version.
updateStatus
Read this first: false above can also mean unknown, disabled or erroring.

This site's own file

clankercode.github.io/tm-control-mcp/latest-version.txt

not loaded

Users can switch checks off under Openplanet Settings → TM Control MCP → Updates.

Localhost only. No auth.

Any process on your machine can call tools while the plugin is loaded: edit or clear maps, drive menus, inject ManiaScript, change plugins and settings. Treat it like a debugger attached to the game. Never bind it to a LAN or public address. Read SECURITY.md