tmodloader-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| TMODLOADER_DIR | No | tModLoader install directory; defaults to Steam's layout. | |
| TMODLOADER_MOD_NAME | No | The mod's internal name, used for artifact filenames. Derived from TMODLOADER_MOD_SOURCE; set only if the folder name differs from the mod name. | |
| TMODLOADER_SAVE_DIR | Yes | Required. Where the mod writes artifacts (tModLoader save directory). | |
| TMODLOADER_TASKKILL | No | Path to Windows taskkill.exe (used to kill sessions). | |
| TMODLOADER_TASKLIST | No | Path to Windows tasklist.exe (used to list sessions). | |
| TMODLOADER_WORLD_WIN | No | Default world, as Windows spells it. Used by 'launch' when no world is passed; no default. | |
| TMODLOADER_MOD_SOURCE | Yes | Required. Mod source directory (WSL path). | |
| TMODLOADER_POWERSHELL | No | Path to Windows PowerShell (used to run build commands). | |
| TMODLOADER_MOD_SOURCE_WIN | No | Mod source as Windows sees it, for building. Derived from TMODLOADER_MOD_SOURCE; set only if source lives outside /mnt/<drive>. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| build_modA | Compile the configured mod source into a .tmod. Args: timeout: Seconds to wait for the compile. A large mod on a slow machine can outlast the default, and a build that runs out of time says so rather than reporting a compile failure with no errors in it. tModLoader REFUSES to build while the game is open, and says so with an error that otherwise reads like a compile failure. That case is reported as itself, with the fix, rather than as a broken build — which is the difference between closing the game and hunting a syntax error that is not there. Success is read from the output, not the exit code, which is not reliable here. |
| launchA | Start tModLoader and wait until it can actually answer. Args:
mode: "server_client" — a server plus one joined client. It is the only
mode there is, and the only way to observe what a CLIENT sees, which
is where most sync bugs live. The other two are refused because the
engine cannot satisfy them: "singleplayer" has no headless entry
point, and "server" alone never ticks, so the mod never polls and
never answers.
port: Server port. Change it only if something else holds the default.
player: Character name for the client. Must already exist — Waits for a heartbeat that is BOTH recent and reporting a live world. Those fail differently — a stale-but-ready heartbeat means the process died, a fresh-but-not-ready one means it is still loading — and checking only existence conflates them. |
| joinA | Bring another character into the session that is already running. Args:
player: Character name. Must already exist — The protocol has supported several clients since answers became per-player; the LIFECYCLE supported one, so the arrangement that work exists to make safe could only be reached by spawning a game by hand. This is that, with the waiting done properly. It waits for THIS client, not for a process. A new pid says something
started — not that a character loaded, that the join was accepted, or that
a world is under it. And it watches only that player's own tokened
heartbeat: the unsuffixed Address the new client by name — |
| triggerA | Ask the running game to do something, and return what it said. Args:
command: One of the dev commands THIS mod serves — call
|
| commandsA | What this side's mod says it serves, read from the mod itself. The list is published by the responder when it loads, not assembled here. This harness used to carry its own copy of one mod's twelve commands and its own belief about which read an argument — facts that belonged to running C# and drifted the moment either side changed alone.
A list that exists but cannot be read is an ERROR rather than Args: server: Ask the dedicated server rather than the client. Each side publishes its own list, and they are not always the same. |
| diagA | Ask one side of the session what it currently sees, parsed. Args: server: Read the dedicated server's view instead of the client's. target: Address a specific client by name. timeout: Seconds for the WHOLE call — the reply and then the dump it promises, out of one budget rather than one each. A large world takes longer. Returns counters as integers and the mod's absence markers as null, so a reading of 0 — a real measurement — cannot be confused with "no data". Asking both sides at the same moment is the only way to answer "the client reports no NPC", which one side alone cannot.
|
| wait_untilA | Poll Args:
field: A TOP-LEVEL diag field, exactly as Use this instead of sleeping and taking a diag. A guessed sleep is wrong in both directions, and the short one is dangerous: the check reads the state BEFORE the thing happened, which looks exactly like the feature being broken. THE COMPARISON IS TYPED. IT REFUSES WHAT CAN NEVER COME TRUE rather than waiting it out. An unknown field names the fields that do exist; ordering a composite string says what the value actually is. Both used to be spellable and would have reported a timeout - blaming a game that was answering perfectly. Not matching is an ANSWER, not an error: it returns |
| shotA | Capture a region of the game's own back buffer and return the PNG path. Args: region: topleft, topright, bottomleft, bottomright, or full. REQUIRED and deliberately without a default — the frame holds the player's character name, world name and any chat, so a request says which corner it wants. target: Address a specific client. timeout: Seconds for the WHOLE call — the reply and then the PNG, out of one budget rather than one each. This reads what the game rendered, not the screen, so no other window can appear in it — by construction rather than by luck. It also sees things the in-game capture camera cannot: dust and the interface layer. The path comes back only once the bytes behind it are a WHOLE PNG. A file exists from the moment it is created rather than the moment it is finished, so waiting on the name alone would hand back half a picture as readily as a whole one — and anything else that landed on that name as readily as either. |
| capturesA | Every capture in the save directory, newest last. Names, not paths — a path handed out is a path that can come back changed,
and |
| read_captureA | Return one capture's PNG as image content.
Args:
name: A capture filename from |
| prune_capturesA | Delete all but the newest Captures accumulated forever. Args:
keep: How many of the newest captures to keep. REQUIRED and
deliberately without a default, the same way Only files matching THIS mod's capture pattern are touched, and each is
re-checked to resolve to a direct child of the save directory — the same
containment Newest is decided by MTIME, not by the index in the filename: the index is this harness's counter and the timestamp is the disk's account of what happened. |
| statusA | Whether a session is running, and what it is. The only read-only way to ask. Without it an agent that lost track had to
provoke an error to find out — Reports what this server BELIEVES it started. It does not re-query the
process table, so it cannot tell you a game was closed from outside; |
| logsA | Tail one of tModLoader's logs, optionally filtered. Args:
name: Which log — see Useful when a launch fails: the reason is usually in a log and not in
anything the trigger protocol can reach, because the game never got far
enough to poll. Which is also why |
| log_filesA | Which logs exist right now, and how many earlier runs are archived. Read off disk rather than listed as a constant: which logs exist depends on
what was run, and a server-only session writes no |
| inventoryA | The worlds, characters and mods on this machine.
The mods answer something else. THOSE TWO ARE NOT ONE FACT. A mod can be enabled and have no |
| save_snapshotA | Copy this world and its characters aside, so a run can be undone. WHAT THIS IS FOR. The mutating verbs write to a real install and none of
that FAILS — it accumulates. Enemy NPCs do not survive a reload, so Take one before a run that mutates, and REFUSES WHILE THE GAME IS RUNNING, naming the pids. A running tModLoader owns these files and writes them out on its own schedule, so a copy taken now is mid-write. Stop the session first. Copies the configured world's |
| save_restoreA | Overwrite the world and characters with a snapshot. This DESTROYS what is on disk now, so it saves that first: the state being
overwritten is copied to Refuses while the game is running, and refuses a label that does not exist by listing the ones that do. |
| save_snapshotsA | Every snapshot on this machine, newest first, with its age in seconds. A snapshot whose manifest cannot be read is omitted rather than listed as
empty, because |
| heartbeatA | Why the game is not answering, for both sides at once.
Reads OFF DISK and needs no session, deliberately: a failed Both sides are returned together because "the client is silent and the server is fine" is a different diagnosis from both being silent, and asking one at a time cannot see the difference. |
| log_sinceA | Only what a log has gained since you last looked. Args:
name: A log filename from NOT A LIVE TAIL, and it cannot be one. Tools here are synchronous and a
game session is process-global state, so a
|
| api_searchA | Find a type, field, property or method in the INSTALLED tModLoader. Args:
query: Part of a name, or a type. ANSWERS THE QUESTION YOU HAVE BEFORE YOU WRITE ANYTHING. A compile tells you
exactly whether the call you already wrote is right; it cannot tell you what
is there. READ FROM THE ASSEMBLY'S OWN METADATA, so it cannot drift from the version installed — which is the failure mode of every wiki page and every model's recollection of an API. It carries no prose, because it is not documentation: it is the public surface, with signatures. The index is built once per tModLoader version and cached against the DLL it came from, so a game update invalidates it by construction rather than by anybody remembering to. The first call after an update pays a few seconds. Needs a .NET SDK, because the indexer is a small C# tool — it reads metadata without loading or running the game assembly. Without one this refuses and says so, rather than answering from a stale or absent index. |
| log_watchA | Block until a log line matches, instead of polling Args:
name: A log filename from THE OFFSET IS THE MECHANISM. Each poll resumes where the last stopped, so a line is matched exactly once — never missed in the gap between two polls, and never re-reported on the next. A watch that re-read the file from the top would match a line written before the wait began and call it news, which is how "wait for the crash" passes on the crash from the PREVIOUS run. Not matching is an ANSWER, not an error: it returns
|
| restartA | Stop, rebuild, and start again with the session's own settings. Args: build: Compile the mod between stopping and starting. On by default, because picking up a code change is the reason this loop exists. timeout: Seconds to wait for readiness on the relaunch. build_timeout: Seconds to allow the compile. THE ORDER IS THE POINT. tModLoader REFUSES to build while the game is open and reports it with an error that reads like a compile failure, so stop-then-build-then-launch is not a preference — building first sends you hunting a syntax error that is not there. Three separate calls let a caller get that order wrong; this one cannot. The mode, port, player and WORLD come from the running session rather than
from arguments or defaults. That last one is why Needs a running session, because a session is where those settings live.
With nothing running there is nothing to reuse — call |
| stopA | Kill only the processes this session started, and confirm they are gone. Args:
settle: Seconds a killed process may take to leave the process table
before it counts as a survivor. Surgical on purpose: a developer usually has their own game open, and a teardown that killed every tModLoader it could find would take it with them.
|
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
| diagnose_silence | The four silences, with this install's current readings. |
| start_a_session | The worlds and characters that actually exist here. |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 24 tools
Each tool has a clearly distinct purpose, even those sharing a domain like logging (log_files lists, logs tails, log_since increments, log_watch waits). The descriptions are exceptionally detailed, leaving no ambiguity about which tool to select.
Naming is inconsistent: some tools use verb_noun with underscores (log_watch, read_capture, prune_captures), others are single verbs (launch, join, stop), and some are plain nouns (captures, commands, status). There is no uniform convention.
24 tools is on the high side but justifiable for a complex development workflow covering building, launching, monitoring, capturing, logging, and snapshot management. It borders the heavy range but each tool serves a distinct function.
The tool surface covers the full mod development lifecycle: build, launch, observe, capture, restore, and log analysis. Minor gaps exist (e.g., no direct world editing or mod config tools) but core workflows are well-supported.