gmod-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@gmod-mcpdebug the Lua error on server start"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
gmod-mcp
A local-first MCP server for AI-assisted Garry's Mod addon development. It plugs into
Claude Code the way claude-in-chrome does: agents discover the MCP tools and iterate on
their own — lint → boot → observe runtime → patch → validate. No web app, no UI.
Two halves:
gmod-mcp— the MCP server, TypeScript/Node, stdio transport.gmod_mcp_bridge(addon/gmod_mcp_bridge) — a GLua addon exposing server state over a file transport inside GMod's DATA sandbox. The daemon and srcds share a filesystem, so no network is involved. This is not a stylistic choice: GMod'sHTTP()was measured not to reach a localhost daemon from a dedicated server.
What it buys you
You talk about the addon in plain language while the model sees the actual game state — "why does this net message never fire?", "fix this error", "show me that menu". The agent lints, boots the server, reads structured Lua errors, patches, reloads and re-validates, in a loop.
Related MCP server: Minecraft Dev MCP
Install
pnpm install && pnpm build
# Register the server with Claude Code (project scope, committable):
node dist/index.js install
# -> writes <repoRoot>/.mcp.jsonCLI alternative:
claude mcp add gmod-mcp -e GMOD_MCP_REPO=<repoRoot> -- node <abs>/dist/index.js
The daemon finds the repo root by walking up from the cwd looking for tools/lint.sh and
CLAUDE.md, or via GMOD_MCP_REPO, or via .gmod-mcp/config.json.
The bridge addon must be mounted by your dedicated server. Symlink it rather than copying, so
a SteamCMD validate can never overwrite it:
ln -s /path/to/gmod-mcp/addon/gmod_mcp_bridge \
/path/to/srcds/garrysmod/addons/gmod_mcp_bridgeTransport
Server realm. The daemon writes srcds/garrysmod/data/gmod_mcp/cmd/<id>.json atomically;
the addon reads it, runs it, writes res/<id>.json and deletes the command. Events (Lua
errors, bridge_up) arrive as evt/<n>.json. The daemon polls res/ and evt/. No port, no
token, no handshake.
One daemon per transport directory, enforced by daemon.lock (PID inside, stale locks
reclaimed). The protocol consumes res/, so a second daemon reading the same directory
deletes results the first one is waiting for: the command really ran, the result really was
written, and the caller still times out. That is what happens the moment a second Claude Code
session is opened on the same repo — measured 2026-07-25, and it cost forty minutes because
the symptom accuses the game. Every bridge tool timed out while srcds was healthy and the
addon mounted; reconnecting the client and restarting the server changed nothing, since the
interfering state lived in a third process. Diagnose it with:
ps -eo pid,etime,args | grep gmod-mcp/dist/index.js # more than one line is the bugA daemon that cannot take the lock keeps its MCP tools but touches nothing: no scanner, no
commands written, and every bridge call refuses with the owner's PID. health reports the
same under bridge.transport. A res/ file matching no in-flight command is now left alone
for a grace period rather than deleted on sight — blind cleanup is what turned coexistence
into an outage — and the count of such files is reported, because on a single-daemon setup it
should be zero.
Client realm. The daemon writes a cl command down the same file channel; the server addon
routes it to the client over a net message; the client runs it and sends the result back, in
chunks reassembled server-side into res/. No HTTP, and the client can be on any machine as
long as it is connected to the server. The target is the first player, or args.player
(SteamID).
Chunks are small and paced one per frame, drained one result at a time, and capped at 48 chunks per result. All three came from the same failure, met twice.
An early version pushed 60 KB chunks in a single frame, which overflowed the client-to-server
reliable channel (send reliable stream overflow) and timed the client out. That failure is
persistent and silent — once the channel is swamped, no net message from that client gets
through, so every client tool times out and the relay looks broken when it has merely been
flooded.
A relayed command the client never answers fails server-side after 20 s, under the
daemon's 30 s round trip, and says so: which tool, how many chunks arrived before the transfer
was discarded, and that srcds itself is fine. Half-received chunks are dropped with it — a
partial reassembly is not a result. read_runtime carries the relay's state (waiting,
oldest_seconds, oldest_tool, partial_transfers), because a stuck channel and an idle one
used to read identically from the daemon.
Per-frame pacing alone was not enough. Measured on a real player: a full-screen capture at quality 80 came to 424 KB and 62 chunks and dropped them from the server, while a full-screen q70 capture (100 KB, 15 chunks) had gone through fine minutes earlier. Two things were wrong. Each result had its own timer, so several in flight summed on the same channel and the pacing stopped meaning anything — a caller retrying a command it believes was lost has two captures in flight, and the second is what tips it over. And nothing bounded a single result. Now one drain serialises every result, and anything over 48 chunks is refused with its size and a suggestion instead of sent. The default half-scale capture is about six chunks.
Iteration loop
edit → lint → (boot) → observe → patch → reload → validate → repeat
The daemon shells out to the host project's tools/lint.sh, start-server.sh and
server-log.sh, parsing file:line: and exit codes. It also encodes the traps that cost real
debugging time: the boot boundary inside an accumulating console.log, waiting for
InitPostEntity before reading cvars, the queueing latency of game.ConsoleCommand, and
NUL-safe log reads.
Tool catalogue
Local (daemon) — health, lint, start_server, stop_server, sync_config,
read_logs, package, patch_file, restore_patch, reload_file, reload_addon,
validate, run_iteration.
Server, reading — batch, read_runtime, read_players, read_entities,
inspect_entity, read_hooks, read_convars, read_net_messages, read_timers,
run_console_command, send_debug, run_test, run_lua (guarded, optional extension).
Server, acting (all guarded) — spawn_entity, world_edit, set_player_state,
force_hook.
Client (via bridge) — read_view, client_input, read_panels, inspect_panel,
capture_screen, read_console, read_client_convars.
health also asks the addon which handlers it registered and reports any the daemon
declares but the game does not have. That gap used to surface only as an "unknown handler"
after a full round trip, or — when a whole include was missing — not at all. It reports the
transport state alongside: whether this daemon owns the directory and who holds it otherwise,
what is in flight, how long since the addon last answered, and how many res/ files matched
nothing of ours. Start every "nothing responds" investigation there.
capture_screen returns a real image content block. Returned as text, a base64 JPEG is
billed to the model token by token and still cannot be looked at, so the "see" half of an
act/see loop silently does nothing while every test passes.
Acting
The reading tools diagnose; the acting ones set up what is worth diagnosing. Server-side,
spawn_entity places something, world_edit moves, freezes, heals, arms or removes it,
set_player_state sets money, job, salary or RP name, and force_hook runs a gamemode
hook directly.
Money goes through the r-capitalism ledger when it is loaded. That ledger holds an audited
invariant — sum(balances) == issued - burned — and a raw addMoney would move a balance
without an entry, leaving the drift permanently off zero. A debugging tool must not corrupt
what is being debugged. Amounts are integer cents, as the rest of that server economy is.
force_hook coerces tagged arguments, because JSON cannot carry a game object:
{"__ent": 3}, {"__ply": "STEAM_0:1:2"}, {"__vec": [x,y,z]}, {"__ang": [p,y,r]}.
Client-side, client_input drives the connected GMod client — movement, aim, key holds,
Derma clicks, typing, chat — and read_view reports eye position, aim trace, cursor,
hovered panel and keyboard focus. read_view is the cheap half of an act-then-look loop:
one chunk, no image, and it answers "am I aimed at the door" without a screenshot.
client_input is bounded in Lua rather than guarded behind a confirmation. It drives a
real person's machine, and a prompt clicked two hundred times is not a safety property:
holds expire, durations are seconds and clamped to five, a 30s deadline resets everything,
and gmod_mcp_release in the client console returns control without involving the daemon.
Batching
A bridge round trip costs about 0.4s: the addon polls at 0.25s and the daemon scans at
0.15s. Any sequence that acts and then looks pays that per gesture. batch carries up to
32 server steps in one command instead:
{ "steps": [{"tool": "read_runtime"},
{"tool": "read_timers", "args": {"names": ["gmod_mcp_bridge_poll"]}}],
"settleMs": 100 }Measured against a live DarkRP server: three steps in 0.19s, versus roughly 0.75s as three separate calls.
A failing step is data, not a transport error — each step reports its own ok/data/
error, stopOnError marks the rest skipped and records abortedAt, so the caller sees
the whole shape of the batch. settleMs pauses between steps, which is what makes
act-then-look honest: without it a step observes the frame before the previous one landed.
Guarded tools are checked on both sides. batch is a single unguarded definition, so
without the check a run_lua step would bypass the confirmation its own gate demands; the
daemon resolves each step against the registry and the Lua runner repeats the check.
Any argument may instead be {"__step": 1, "get": "index"}, read from an earlier step's
result. Without it, spawning something and then acting on it costs two round trips — the
caller cannot know the EntIndex until the spawn has answered, which is the round trip
batching exists to remove.
Steps share a server tick unless settleMs is set, and some engine effects only land at
end of frame: Entity:Remove() is deferred, so a step reading the entity back still finds
it valid and the agent concludes the removal failed. Set settleMs (100 is usually enough)
whenever a step must observe an earlier one.
Client-realm steps are refused explicitly rather than silently dropped — a batch runs
inside the server addon, and cl tools are relayed over net. So an act-then-look loop on
the client is currently two round trips, not one.
All three realms have been exercised against a live DarkRP server: the server tools on
rp_nycity_day at tick 33, and the client tools against a connected GMod client —
read_panels returning a live VGUI tree and capture_screen returning a complete 1920x1080
JPEG. batch was proven the same way, including its failure paths: a step raising returns
the real Lua error with file:line, the rest come back skipped, and a run_lua step is
refused unconfirmed and executes confirmed.
Security
Guarded tools (
run_lua) requireconfirm: trueor membership intoolAllowlist; otherwise they are refused without executing. A guarded tool used as abatchstep needs the batch itself confirmed, and both the daemon and the Lua runner enforce that independently. Every call, result, patch and executed Lua line is appended to<repoRoot>/.gmod-mcp/logs/audit.jsonl.patch_fileis locked to the repo root and refuses paths outside it.No network listener. The server transport is files inside DATA; the MCP layer is stdio. The trust boundary is the local filesystem.
run_lua— arbitrary Lua execution — lives in the optionaloptional/gmod_mcp_runluaextension, isolated becauseglua-auditforbids dynamic execution and the main bridge stays lint-clean. Development only, never on a production server.
Project config — <repoRoot>/.gmod-mcp/config.json
Every key is optional (see config.example.json):
{
"repoRoot": ".",
"addons": ["gmod_mcp_bridge"],
"clientWaitMs": 30000,
"toolAllowlist": [],
"plugins": []
}clientWaitMs is how long a client-realm call keeps retrying while the client is absent.
That realm needs a human connected, and humans crash, alt-tab and reconnect; retrying lets an
agent resume when they come back instead of failing the moment they drop. Set it to 0 to fail
fast. Server-realm calls ignore it — srcds does not come and go mid-session, so a failure there
is a real one.
Plugins
Extend the tool set with ESM modules listed in plugins. Each module exports tools:
// my_plugin.mjs
export const tools = [
{ name: "my_tool", description: "…", realm: "local", inputSchema: {}, handler: () => ({ ok: true }) },
];A failing plugin is reported on stderr without blocking startup.
Design notes
docs/2026-07-25-autonomie-client-design.md— what the client side needs before an agent can drive it without a human: name a panel instead of guessing a pixel, assert text instead of reading a JPEG, and type into a field at all. Each gap listed there blocked a real session.
Development
pnpm test # vitest
pnpm typecheck
pnpm buildLinting the GLua addon needs the host project's tools/lint.sh (four passes) plus a local
copy of GLua API definitions — see addon/gmod_mcp_bridge/.luarc.example.json.
License
MIT. See LICENSE.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityAmaintenanceIntelligent MCP server for AI-assisted Godot 4 development with spatial intelligence, code analysis, and 35 tools53MIT
- Alicense-qualityAmaintenanceA Model Context Protocol server that gives AI assistants native access to Minecraft mod development tools — decompile, remap, search, and analyze Minecraft source code directly from your AI workflow.22631MIT
- Alicense-qualityCmaintenanceLocal MCP server that gives AI agents 44 engine tools to build, run, and debug real 2D and 3D games through conversation.MIT
- Flicense-qualityAmaintenanceA local MCP server plus a bundled Godot editor addon that lets an AI agent create, inspect, run, debug, and export real Godot 4.6 games through tools.2
Related MCP Connectors
An MCP server that gives your AI access to the source code and docs of all public github repos
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
MCP server for AI dialogue using various LLM models via AceDataCloud
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ProjectSocietyStudio/gmod-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server