@warpfx/mcp
v0.4.1
Published
MCP server exposing the Warp FiveM dev loop to AI agents
Readme
@warpfx/mcp
MCP server that gives an AI agent direct access to the FiveM dev loop - boot the server, launch the game, drive the UI, and read back what actually happened.
Works on any FiveM server running Warp - see Install.
[!WARNING] Part of Warp, which is pre-1.0. Tool names, arguments and return shapes can change in any release. This is developer tooling for a development machine - see Safety before pointing it at anything you care about.
Install
Register it in your own project - the module you are building, not a checkout of Warp.
For Claude Code that is a .mcp.json at the project root; other MCP-aware editors take
the same shape under their own key.
{
"mcpServers": {
"warp": {
"command": "npx",
"args": ["-y", "@warpfx/mcp"],
"env": {
"WARP_FXSERVER": "C:\\FXServer\\FXServer.exe",
"WARP_SERVER_DATA": "C:\\FXServer\\server-data"
}
}
}
}Two paths, both pointing at the FiveM server you develop against:
| Variable | What it points at |
| ------------------ | ---------------------------------------------------------------------- |
| WARP_FXSERVER | FXServer.exe |
| WARP_SERVER_DATA | the server data folder - the one holding resources/ and server.cfg |
Everything else is derived from those. Warp itself is found at
<WARP_SERVER_DATA>/resources/warp, which is where its config, log and database engine
live. Set WARP_ROOT explicitly if the resource folder is named something else.
Nothing is installed into your project: npx fetches the server on demand and it talks to
your running FiveM server from outside. Your module never depends on it.
Then develop the loop, not just the code:
server_start → your server, with your module
rcon "restart my_module" → reload after an edit, no cold boot
client_logs filter="script error" → what actually broke, client-side
db_query sql="SELECT * FROM my_module_things"Two tools need nothing at all: native_lookup works with no server and no config, and
server_logs reads any FiveM log you point WARP_SERVER_LOG at.
db_queryreaches the whole Warp database, not only your module's tables. On your own development server that is the point; it is also the reason not to point this at a server whose data matters. See Safety.
Tools
| Tool | What it does |
| ------------------------------ | ------------------------------------------------------------------------- |
| server_start / server_stop | Ensure FXServer is up, console captured to the log |
| server_logs | Tail the server console log, optionally filtered by regex |
| client_logs | Tail the game client's log, the same output the F8 console shows |
| rcon | Run a command on the FiveM server console (ensure warp, refresh, …) |
| game_launch / game_close | Ensure a client is in-game, and hand the display back after |
| probe_wait | Block until the game client is loaded and controllable |
| probe_state | Read a connected player's position, health, vehicle and synced tables |
| probe_teleport | Move a connected player to world coordinates |
| game_screenshot | Capture the FiveM window, NUI included |
| game_click | Click a point in the window - drives NUI - and return the resulting frame |
| game_scroll | Turn the mouse wheel, to reach a control below the fold of a NUI list |
| game_type | Type text into a NUI field |
| game_key | Press a named key with modifiers - Tab, Enter, Escape, arrows, f-keys |
| db_query | Run a read-only SELECT against the Warp database |
| db_execute | INSERT/UPDATE/DELETE - destructive, guarded, explicit request only |
| native_lookup | Search the canonical FiveM natives index by name or hash |
A typical session
The point of the loop is that a change gets verified in the running game, not assumed.
Creating a character through the warp-multichar UI, end to end:
server_start → FXServer up, console → logs/server.log
server_logs filter="error" → nothing; boot is clean
game_launch → client connects
probe_wait → blocks until in-game, returns player state
game_screenshot → a "Choose your life" panel
game_click x=0.086 y=0.611 → NEW CHARACTER; returns the frame after
game_type x=0.058 y=0.476 text="Ada" → clicks that field, then types
game_key key=tab → next field
game_type text="Lovelace"
game_click x=0.096 y=0.652 → CREATE CHARACTER
db_query sql="SELECT * FROM multichar_characters" → the row is really there
game_close → display handed back
server_stopEvery input tool returns a fresh screenshot, so each UI step is a single call rather than
act-then-look. Positions are fractions of the window (x=0.14, y=0.56), not pixels -
read a target off a screenshot and divide by that image's width and height, and the
coordinate stays correct however the image was scaled in transit.
Setup
rcon - needs a password in server.cfg:
rcon_password "warp"Override with RCON_HOST / RCON_PORT / RCON_PASSWORD (defaults 127.0.0.1:30120, warp).
server_start - needs WARP_FXSERVER and WARP_SERVER_DATA (see Install).
Refuses to run if unset.
server_logs - no setup if you start the server with server_start, which captures
the console to logs/server.log (truncated per run). Otherwise pipe it yourself:
FXServer.exe +exec server.cfg > logs/server.log 2>&1Or point WARP_SERVER_LOG at an existing log file (e.g. a txAdmin one).
client_logs - no setup. FiveM mirrors the F8 console to a per-session file in
%LOCALAPPDATA%\FiveM\FiveM.app\logs\CitizenFX_log_<timestamp>.log, so client-side
console.log and script errors are readable without the game window being focused. The
newest session file is resolved on every call, because each launch writes a new one.
Override with WARP_CLIENT_LOG (a specific file) or WARP_CLIENT_LOG_DIR (the folder).
The engine writes a lot of its own noise into this log, so filter it:
client_logs filter="script error" → client-side failures only
client_logs filter="Warp|my_resource" → follow one module
server_logsandclient_logssee different halves of the same session. A feature that looks fine server-side can still be failing in the client script, and onlyclient_logsshows that.
game_launch - no setup, and no path to FiveM.exe required. It hands
fivem://connect/<address> to explorer.exe rather than running the executable, because
the game refuses to start when the process launching it has a console attached
("This application should be launched directly from the shell or a web browser").
Going through the shell holds whether or not the caller has one. explorer.exe reports no
useful exit code, so probe_wait is what actually confirms the launch worked - a timeout
there with no FiveM process means the fivem:// handler is not registered.
game_screenshot - Windows only, no setup. GDI window capture, so run FiveM
windowed or borderless: exclusive fullscreen returns a black image.
game_click / game_scroll / game_type / game_key - Windows only, no setup.
All four are synthetic OS input (same input.ps1 as the screenshot, which needs the window
on top for the same reason), so the FiveM window is brought to the foreground first, and NUI
only receives them while the resource holds SetNuiFocus(true, true). If nothing changed,
the panel likely was not focused, or the point missed the control.
Keyboard input goes through .NET SendKeys, which is keyboard-layout aware - a literal
"John" types the same on QWERTZ as on QWERTY. It cannot reach a window running elevated
when the MCP server is not.
probe_* - needs the warp-probe resource ensured on
the server, and a connected game client. Reached over HTTP on the same host/port as RCON;
override with WARP_PROBE_URL.
db_query / db_execute - no setup. Both shell out to the engine Warp downloads into
<warp>/.spacetimedb/bin/<version>/, using that folder as the CLI root, so they speak to
the same data directory and identity the server uses - not to any CLI on PATH. Host, port
and database name come from the Warp resource's warp.config.json (defaults
127.0.0.1:3000, warp-db). The database only runs while the server does, so
server_start first.
Tables are prefixed with their module name (multichar_characters, economy_accounts);
core tables are unprefixed (player).
native_lookup - no setup. Downloads natives.json + natives_cfx.json from
runtime.fivem.net on first use into .cache/ (gitignored). Delete that folder to refresh.
Attaching to what is already running
Both server_start and game_launch detect and reuse rather than duplicate, so an
agent can call them first thing without caring whether the developer already has a server
or a client open:
server_startaskshttp://<address>/info.json. Anything answering - started here, by hand, or by txAdmin - is reused. Reload code withrcon restart <resource>instead of a cold boot. (A server started outside this tool has no captured console; pointWARP_SERVER_LOGat its log to keepserver_logsuseful.)game_launchaskswarp-probefor player state, which is a stronger signal than the process existing: it means connected and resources live. A client already in-game is reused and its state returned. If FiveM is running but not in-game on that address, it says so rather than spawning a second instance - FiveM allows only one.
The teardown tools are deliberately asymmetric: server_stop and game_close only stop
what this tool started. A server or client that was already running belongs to the
developer and needs an explicit force: true to kill. Losing a session mid-work is worse
than a slower loop.
Both track "did I start this" in memory, so it resets if the MCP server reconnects - after that, stopping needs
force.
Safety
Two of these tools can do real damage, and both are development-only.
warp-probe is unauthenticated player control. Anything that can reach the server's
HTTP port can read and move any connected player. Never ensure warp-probe on a public
server.
db_execute writes straight to the database, bypassing module validation and any
server-side logic. The sync engine still pushes the change to connected clients, because
the server is subscribed to the table - so a bad write is visible immediately and is not
undoable.
Reads are a separate tool from writes so the two can be permissioned apart: allow
db_query freely, keep db_execute behind a prompt. db_execute refuses unless all of:
- the statement is a single INSERT, UPDATE or DELETE - no stacked statements, no DDL,
confirm: trueis passed,- a DELETE or UPDATE carries a WHERE clause - a missing one is how a one-row fix becomes a
wiped table.
WHERE trueis the escape hatch if the whole table really is the target.
The tool description tells the agent to treat db_execute as explicit-request-only - never
as cleanup, never to tidy up test data it created, never to make a test pass. The guards
above are the backstop for when that instruction is ignored.
Run standalone
npx @warpfx/mcp # anywhere
bun packages/mcp/src/index.ts # in this repo, no build stepSpeaks MCP over stdio - stdout is the protocol channel, so it prints nothing on start.
Tests
bun packages/mcp/test.tsDrives the server over real stdio JSON-RPC. Covers protocol wiring, tool registration, native matching, log tailing, and every failure path that must stay a clean error rather than a hang - unreachable server, unreachable probe, missing window, unconfigured paths.
The db_execute guards are tested exhaustively and all reject before the CLI is ever
spawned, so the suite never touches a real database. That is deliberate: the day one of
those stops rejecting, an agent can quietly destroy data.
