modwright
v0.1.10
Published
A game-agnostic MCP server for game modding, with per-game surfaces.
Maintainers
Readme
ModWright
A game-agnostic MCP server for game modding.
ModWright gives an AI assistant the tools a modder uses:
- finding game installs and mod folders, and reading load orders;
- triaging logs, and checking loader and framework versions against the game build;
- looking up the game's own data and a knowledge base of sourced facts;
- scaffolding, validating, building and deploying your mod;
- talking to the running game through an in-game bridge, where the game supports one.
The core is generic. Each game is a surface: one module that declares where things live and what the rules are, so every tool works for every supported game.
Status: 0.x, and the API is not stable yet. How far each game is tested differs a lot; "Supported games" says which surfaces have been used against a real install and which are built from documentation alone.
Why
Every game's modding stack is idiosyncratic, and the idiosyncrasies are exactly what an assistant needs to know:
- Cyberpunk 2077 has six independent mod systems in one install directory. A RED4ext version mismatch after a patch silently disables a whole category of mods, and the evidence is in a log four folders deep.
- Baldur's Gate 3 keeps mods in the user profile, not the install. A
.pakis inert until it appears inmodsettings.lsx, which the game rewrites on launch, dropping the entries it cannot resolve. - Skyrim's load order is a text file, and its real conflicts are record-level inside plugins, not files on disk.
Without that context an assistant guesses. ModWright supplies it as structured data, and returns the game's own caveats alongside every result.
Install
Node.js 20 or newer is required. Register ModWright with your MCP client. For Claude Code on macOS or Linux:
claude mcp add modwright -- npx -y modwright@latestOn Windows, MCP clients cannot start npx directly, so it goes through
cmd /c:
claude mcp add modwright -- cmd /c npx -y modwright@latestFor any other MCP client, add it to the client's configuration. On Windows,
use "command": "cmd" and put "/c", "npx" in front of the arguments.
{
"mcpServers": {
"modwright": { "command": "npx", "args": ["-y", "modwright@latest"] }
}
}- Check it worked:
npx -y modwright@latest --versionprints the version, and asking your assistant to runlist_gameslists the supported games and any installs it found. - Updates:
@latestmakes npx look for a new release each time your client starts the server. Without it npx keeps using the copy it cached the first time. To stay on one version, name it instead, for example[email protected].
Or install it once. A global install puts a modwright command on your
PATH, and the client starts that instead of going through npx:
npm install -g modwright
claude mcp add modwright -- modwrightOn Windows, register it as claude mcp add modwright -- cmd /c modwright.
In another client's configuration, use "command": "modwright" with no
arguments (on Windows, "command": "cmd" with "args": ["/c", "modwright"]).
It starts without a registry check and works offline. It updates only when
you run npm update -g modwright.
First steps
Ask your assistant to run list_games, then check_toolchain for your game.
check_toolchain says what works now and what each feature still needs, for
example: "Ready now: logs, compat. index needs a Jotunn checkout; build works
without tcli." Most features need nothing. ModWright can install some
external tools itself, and any other tool or data folder only has to be
pointed at once.
Which mod is doing this?
With hundreds of mods installed, the usual question is not "is something broken" but "which mod did it". Three tools answer it from different sides, without launching the game:
owner_of: which mod a file comes from. Give it a DLL named in a crash log, a path fromtriage_logs, or a bare file name. It answers from the game's own mod folders, Vortex's deployment records and Mod Organizer 2's profiles (Skyrim), and says how it decided. For example,owner_of game=cyberpunk2077 paths=["TweakXL.dll"]names the Vortex mod that deployed it.who_touches: which mods write the same record or entry: Cyberpunk 2077 TweakDB records and flats, Baldur's Gate 3 stats entries, Stardew Valley Content Patcher assets and entries. With nokeyit lists the keys mods actually disagree on, for examplewho_touches game=cyberpunk2077. These don't count as a disagreement:- identical writes;
- list edits on different items;
- a mod the game does not load (a BG3 pak missing from
modsettings.lsx); - a second copy of the same mod.
Copies of one mod are reported separately, under
duplicateCopies.who_touches key=<record>shows every writer of one key, andmod=<name>shows what one mod writes.It also lists, under
repeatedKeys, any key a Cyberpunk 2077 tweak file repeats within one block. That's invalid YAML. TweakXL reads it without an error and applies every copy in file order, so a value both copies set ends up as the later one.triage_logswithattributionFor: what the frameworks' own logs say happened. For example, TweakXL's log gives the order it read tweak files in, and a later file wins the same flat.
Together they catch the quiet problems, for example a tweak file installed
twice and loaded twice, or a stale copy of a mod that the game binds instead
of the current one. who_touches reads files, not the running game, so
scripts that change things at runtime are not seen. Which of two
disagreeing mods wins depends on load order, which these tools report but
do not decide.
Supported games
| Surface | Game | Tested | Mod systems modeled |
|---|---|---|---|
| cyberpunk2077 | Cyberpunk 2077 | field-tested | legacy archives, REDmod, redscript, TweakXL, ArchiveXL, RED4ext, CET; the TweakDB vanilla index; garment, vehicle, tweak and packaging validators; the CET bridge |
| baldursgate3 | Baldur's Gate 3 | field-tested | .pak mods, loose overrides, Script Extender, the native mod loader; the stats, GUID and handle vanilla index; stats, story, progression and visual-bank validators; the Script Extender bridge |
| skyrimse | Skyrim Special Edition | research only | plugins, BSA archives, SKSE plugins, loose files |
| eldenring | Elden Ring | research only | me3 profiles, Mod Engine 2 overlay and DLLs, UXM loose files; paramdef-typed param exports, the .me3 profile, package-layout and DCX rules |
| valheim | Valheim | research only | BepInEx plugins, patchers, config, MonoMod, the Doorstop loader; Thunderstore packaging, plugin-source and config rules; the vanilla index from Jötunn's dumps |
| stardewvalley | Stardew Valley | research only | SMAPI mods and Content Patcher packs, the load-order override lists, SMAPI's log; the manifest and content.json rules; the wiki schema index, built from the wiki's data pages, which check_toolchain fetches for you |
| subnautica2 | Subnautica 2 | research only | ~mods paks, LogicMods, UE4SS mods, DLC-style plugins; triplet, mount-point, TOC and UE4SS-shape rules |
| nivalisnights | Nivalis Nights | checked on an install | BepInEx 6 IL2CPP plugins, patchers, config, generated interop, MelonLoader mods; the vanilla index (items, recipes, vendors, venues, NPCs, by GUID) built from your install with Cpp2IL and UnityPy |
- Field-tested: used against real installs, real mod projects and the running game, through the in-game bridge, over many field runs.
- Checked on an install: detection, logs, compatibility and the vanilla index were checked against an installed copy of the game. No mod project has been built or deployed through it yet, and it has no bridge.
- Research only: written from the game's modding documentation, its community tools' sources and their published file formats, and tested against fixtures. No install was behind it. Expect gaps, and please report them.
Installs are auto-detected from Steam (every library folder), plus GOG and
Epic paths where they apply. Override with installPath on any tool, or set
MODWRIGHT_<SURFACE_ID>_ROOT.
Tools
| Tool | What it does |
|---|---|
| list_games | Every supported game, its modding model, and whether an install was found. Start here. |
| detect_install | Locate one game, and report every mod root and log source, marking which exist. |
| list_mods | Enumerate installed mods across all roots, with sizes, timestamps and parsed manifests. |
| inspect_mod | Full detail on one mod: files owned, manifest, warnings. |
| find_conflicts | Paths claimed by more than one mod, restricted to roots where that is actually possible. Where Mod Organizer 2 manages the game, it also reports the files one MO2 mod overrides in another. Cyberpunk 2077 .archive files are compared by their indexes, with the winner named. BG3 paks are compared by their file lists, in load order. |
| owner_of | Which installed mod a file comes from, and which copy the game sees when several provide it: for a DLL named in a crash log, a path from triage_logs, or a bare file name. It reads the game's own mod folders, Mod Organizer 2's profiles (Skyrim) and Vortex's deployment records, and says how each answer was decided. |
| who_touches | Which installed mods write a given record or entry, at the level the game merges on: Cyberpunk 2077 TweakDB records and flats, Baldur's Gate 3 stats entries (read out of paks), Stardew Valley Content Patcher assets and entries. With no key it lists the contested ones. A mod the game does not load is listed but never contests, and copies of one mod count once. Static: it does not decide load order, and runtime scripts are not seen. |
| load_order | Read the game's load order and cross-check it against what is installed. For Cyberpunk 2077 the order is derived: archives in ASCII order, or modlist.txt's order when that file exists, then REDmods, each entry saying why it sits there. For BG3 it also checks that every mod's declared dependencies are in the list and load before it. |
| load_order_diff | Snapshot the load order before a launch, then report what the game dropped, added or reordered (BG3 modsettings.lsx). |
| read_log | Tail a mod-related log (redscript, RED4ext, CET, SKSE, BG3SE, BepInEx, SMAPI and more). |
| triage_logs | Read every mod log for the current run and classify lines against known failure signatures. It returns one verdict: which systems loaded, what failed first, and the usual fix. Where a framework's log names who acted (TweakXL's read order, ArchiveXL's merges), it also says which mod did what; attributionFor narrows that to one asset, record or mod. For Cyberpunk 2077 it also reads each RED4ext plugin's own log, one source per plugin. |
| check_compat | Detect the game build, loaders, frameworks and tools, and check them against cited compatibility floors. |
| check_toolchain | Locate the external tools (LSLib divine, WolvenKit CLI, Blender, Cpp2IL and others), report each one's path and version (never guessed), and say what each feature needs. It also remembers a tool or data folder you point it at, installs the tools ModWright can install, and trusts a project to run its own build commands. |
| project_info | Load a modwright.json mod project: sources, build, deploy targets, dependencies and publish targets, with paths resolved and structural warnings. For a BG3 mod it also reports the release state: which copies are installed (your build, the mod.io copy, a loose copy the Toolkit left) and which one the game binds. |
| lookup | Search the knowledge base of game and toolchain facts (each with a status and a source), the validator catalogue, or the game's vanilla index you built locally. |
| build_index | Extract and index the game's own data into a local SQLite file in ModWright's cache. Never redistributed. |
| validate | Run the static validators over a mod project: syntax, reference integrity against the vanilla index, known crash causes and silent failures, packaging hygiene. Each finding names the file, the fix and the facts behind the rule. rule re-runs one rule by id; explained findings can be suppressed with a reason. |
| build | Run a project's build steps: stage sources into a clean tree, run the typed pack/convert/verify steps, and write a manifest of every input and output with content hashes. The cheap validators gate the build, over your sources as well as the staged tree, and a dry run says up front when they would stop it. An exec step can require every run to rewrite its outputs (freshOutputs). |
| deploy | Copy a built mod into the game's mod roots as one write plan. It refuses an artifact the build did not produce or one older than its sources, prunes what a previous build left behind, and defers a file the running game holds locked. A Cyberpunk archive that was only repacked, with the same content, is not copied again. Every overwrite and delete is backed up first. For a BG3 mod published on mod.io, deploying an update sets the mod.io copy aside, and action=restore-published puts it back once the update is out. |
| rollback | List the deploy backups under a project's .modwright/backups/, or restore one, re-checking every file's hash first. A restore also removes the files that deploy added, unless they changed since. action=prune deletes old backup runs, keeping the newest 20 and anything under 14 days old. |
| reload_plan | Classify deployed changes into hot / hot-degraded / save-reload / restart / unknown, with the caveat for each and the single most conservative recommendation. |
| bridge | The in-game bridge (BG3 Script Extender, Cyberpunk 2077 CET): stage the bridge into a project, check its heartbeat with status, arm a probe request and poll for its result, and remove it from the game when you are done. On Cyberpunk 2077, say puts a prompt in a small window drawn over the game, and listen waits for the player's answer from its hotkeys (see "In-game chat" below). |
| run_tests | Run a mod's in-game test plan (<mod>/tests/*.yaml) through the bridge. Probe rows are armed and polled; manual rows become a checklist. Results are written back into the plan. |
| ledger | Track claims and decisions across a fix (<mod>/ledger/). A claim moves authored → checker-clean → built → verified \| contradicted, and nothing is verified without evidence. |
| convert | Run one registered conversion: BG3 lsx↔lsf and xml↔loca through divine, Cyberpunk cr2w↔json through WolvenKit CLI. Each one checks its own round trip. |
| diff | Field-diff two parsed documents (cr2w-json, lsx, loca-xml) with type-aware equality, or diff a file against a cached template. |
| template | Extract one file from your own install into an untracked per-game-version cache, to start a mod file from the game's own. A template is never committed, and validate blocks a build that ships one verbatim. |
| scaffold | Declarative scaffolds (new-project, BG3 spells, passives, statuses, items, quests and class injections; Cyberpunk tweaks, item chains, sector nodes, garments, liveries, player replacers and more; Stardew Content Patcher and SMAPI C# projects), with ids allocated and collision-checked against your project, the vanilla index and the load order. plan walks an authoring sequence for a whole mod type and asks for each asset at the step that needs it. claude-assets writes optional Claude Code skills and agents into your mod repo. |
Every tool that writes is dry-run by default: it describes the plan and writes nothing until you apply it.
How much to trust an answer
Every fact lookup returns carries a status and a source:
| Status | Means |
|---|---|
| verified | Someone observed it in the game; the fact says when. |
| community | A wiki, README, issue or forum post states it, cited by URL, and it has not been re-checked here. |
| inferred | Derived from reading code or from other facts. |
| unverified | Written from documentation or reasoning, and never observed. |
| contradicted | Once believed and later shown wrong. It is kept, pointing at what replaced it, so the mistake is not made again. |
A validator that cannot run is reported as skipped, with the reason,
never as passed. That happens when the vanilla index is not built, a tool is
missing, or the project has nothing of the kind it checks. An in-game test
whose probe is unverified can pass, but its result stays below verified.
Workflow prompts
Seven workflow prompts walk the authoring loop in any MCP client
(prompts/list, prompts/get):
mw-validate,mw-scaffold,mw-verifyandmw-ship;mw-author, which walks a whole mod type's scaffold sequence;- the per-game
mw-new-spell(BG3) andmw-new-item(Cyberpunk).
Each takes an optional path to your mod project. Claude Code lists them as
/modwright:mw-ship and so on.
From a shell
npx modwright tool list # every tool and its parameters
npx modwright tool call list_games
npx modwright tool call check_toolchain game=valheim
npx modwright tool call scaffold --args-file args.json
npx modwright tool prompt mw-ship path=<your mod>
npx modwright --help- Arguments: a
key=valuevalue is parsed as JSON when it parses, and taken as a string otherwise.--json '<object>'and--args-file <file>supply a whole argument object. Use the file form for nested inputs on Windows, where shell quoting of JSON is unreliable. - Output:
--outwrites the result to a file. - Exit code: 1 when the tool reports an error.
In-game chat (Cyberpunk 2077)
The CET bridge draws a small ModWright window over the game, visible with
the CET overlay closed. It shows a prompt, a hold countdown and up to four
choices, and the player answers with hotkeys (GO, ABORT, Choice 1 to 4).
The hotkeys ship unbound; bind them in the CET overlay's Bindings tab.
With the overlay open, a text box sends a typed note. A prompt goes idle a
minute after it expires, and bridge action=say clear=true clears the
window at once.
To type to Claude Code from inside the game, pair the project and run a channel session (channels are a Claude Code research preview):
npx modwright channel init --project <your mod>Then stage and deploy the bridge again so the game holds the pairing key,
register the channel once with
claude mcp add modwright-channel -- npx -y modwright channel --project <your mod>,
and start the session in the mod's folder with
claude --dangerously-load-development-channels server:modwright-channel.
A note typed in the game arrives in that session as your message, and the
reply shows in the window. Only events carrying the pairing key are
forwarded; channel init prints the exact steps for your setup.
Where ModWright keeps its state
Vanilla indexes go in a per-user cache, the same for every session and every project:
| | Indexes | Managed tools and snapshots |
|---|---|---|
| Windows | %LOCALAPPDATA%\ModWright\cache | %LOCALAPPDATA%\ModWright |
| macOS | ~/Library/Caches/ModWright | ~/Library/Application Support/ModWright |
| Linux | $XDG_CACHE_HOME/modwright (~/.cache/modwright) | $XDG_DATA_HOME/modwright |
- Tools you point ModWright at are remembered in a per-user
config.json(%APPDATA%\ModWright,~/.config/modwright, or~/Library/Application Support/ModWright). Record one withcheck_toolchain action=remember tool=<id> toolPath=<path> mode=apply. Lookup order: aMODWRIGHT_TOOL_*variable, then a project'stoolchainoverride, then that config, then ModWright's managed tools folder, then PATH, then common install locations. - Data folders that a feature reads are remembered the same way
(
check_toolchain action=remember data=valheim.jotunn dataPath=<folder> mode=apply). Stardew Valley's wiki pages ModWright fetches itself:check_toolchain action=install data=stardewvalley.wikilists every page first, andmode=applyfetches them (about a minute, one request a second, honouring the wiki's robots.txt), saves them on your machine and remembers the folder. The wiki's text is CC BY-NC-SA, and ModWright never redistributes it. - Some tools ModWright can install for you.
check_toolchain action=install tool=cpp2ilshows exactly what it would fetch (URL, size, sha256, destination);mode=applyinstalls it. Downloads are pinned and checked before anything is written. - Trusted projects are listed in
trusted-projects.json, beside that config. A project'sexecbuild steps andtoolchainoverrides run only after you trust it once:check_toolchain action=trust projectPath=<mod>lists what it would allow, andmode=applyrecords it. Until then an applied build, convert, template extract or index build that would run them is refused.MODWRIGHT_TRUST_ALL=1trusts every project, for CI. - Overrides:
MODWRIGHT_CACHE_DIRmoves the cache;MODWRIGHT_HOMEmoves everything. - Disk space: a vanilla index is built once per game. Baldur's Gate 3's takes about 2 GB with the files it extracts, Cyberpunk 2077's about 500 MB, and the others far less.
- Project state stays in the project's own
.modwright/. Every deploy and every applied writer backs up what it replaces under.modwright/backups/.rollback action=pruneclears old runs (dry run first), anddeploywarns once a project has more than 20. - Shutdown events: if your client closes ModWright while a request is
still running, the event is recorded in
supervisor-incidents.login the managed-tools folder above.
What it deliberately does not do
These are honest limits, not bugs. Each one is a place where guessing would produce confident nonsense:
- Guessing which packed archive wins.
.archive,.pakand.bsamods conflict on resources inside them, so the file comparison skips those roots. For Cyberpunk 2077,find_conflictsreads each.archive's index and names the winner, because the first-loaded archive's copy is the one the game uses. For BG3 it lists the paths two loaded paks both supply, in load order, but names no winner, because no sourced rule says which pak's copy the game uses..bsaarchives are not compared. - Conflicts in isolated roots. Where each mod owns its own subtree (CET,
redscript, REDmod), two mods both containing
init.luais not a conflict, and ModWright will not report it as one. - BG3 load order by pak filename. A pak's filename is arbitrary and rarely
matches its module name, so ModWright never maps the two by name. It reads
the UUID in each pak's
meta.lsxand binds the load order by that. - Skyrim record-level conflicts. Those need xEdit. Directory comparison
under
Data/is not a substitute. - Writing where you did not ask. Writers are dry-run by default, write
only where the project file says, and back up whatever they replace.
deployreports themodsettings.lsxentry a loose BG3 deploy needs, but never edits that file, because a mod manager may own it. - Acting on a running game unasked.
reload_planclassifies what a change needs. A hot reload (Cyberpunk, through Red Hot Tools) or an in-game probe that changes state runs only when the caller grants it, and what it did is confirmed by a read-back, not assumed. - Redistributing game data. Indexes and templates are built from your own install, on your machine, and never shipped.
Security
ModWright runs programs on your machine and writes into your game folders. What it runs, and where it writes:
- Tools it finds. It runs the external tools it locates (LSLib divine,
WolvenKit CLI, Blender and the others
check_toolchainlists). They come from aMODWRIGHT_TOOL_*variable, your per-user config, its own managed installs, PATH or common install locations. Managed installs are pinned and their sha256 is checked before anything is written. - A project's own commands. A mod project's
execbuild steps and itstoolchainoverrides are programs the project's author chose. They run only after you trust that project on this machine:check_toolchain action=trust projectPath=<mod>lists what it would allow, andmode=applyrecords it. Read the project'smodwright.jsonfirst, above all for a mod you cloned from someone else. Trust belongs to the folder, not to the file's content, so a trusted project that later gains a step (after agit pull) is not asked again. - Where it writes. Every writer is dry-run until you apply it, and it
backs up what it replaces. A build writes under the project's
build.outDir, and a deploy writes under the game's mod roots. A project path that is absolute or climbs out with..is refused unless that entry sets"allowOutsideRoot": true. A deploy also refuses to write through a folder link someone made inside a mod folder, which would send files wherever the link points; links the project declares underdeploy.junctionsare allowed.rollbackrestores only under the project and the game's mod roots, unless you passallowOutsideRoots. - The in-game bridges. A verification bridge answers probe requests
written into its request folder while it is deployed and the game runs.
Its Lua eval probes (
bg3.lua.eval,cp2077.lua.eval) run whatever code a request carries, so they are off unless the project opts in with"bridge": { "policy": { "mode": "workbench" } }in itsmodwright.json.bridge stagewrites that switch into the staged bridge, andbridge armrefuses an eval probe for a project without it. Even with eval off, anything that can write the request folder can run the other probes, and some of those change the game session. Deploy a bridge only while you verify, remove it afterwards (bridge action=remove), and never ship it (the release validators block a build that contains it). The bridge READMEs underbridges/describe each one's trust model. - In-game chat. A key press or a typed note from the window is data to
ModWright's tools:
bridge listenreports it as an answer to a prompt, never as an instruction. Only the channel session treats a typed note as your own message, and only for a project you paired withchannel initand a session you started with the channel flag. The pairing key separates the paired bridge from a stray writer of its files. It is not a secret from other software running as you.
What a package scanner reports, and why. Tools such as Socket flag
ModWright for shell access (running the modding tools above, always with an
argument list and no shell), network access (only check_toolchain
action=install with apply: the pinned managed tool installs, and the
Stardew Valley wiki pages its index is built from), environment
variables (the MODWRIGHT_* overrides and the Steam and home folders), file
access, and URL strings (the knowledge base's citations). Their AI review
named the bridges' Lua eval, which is the opt-in above. The production
install is six packages: the MCP SDK's server and core packages, zod, yaml,
smol-toml and sql.js. sql.js is SQLite compiled to WebAssembly, which is what
a scanner's eval and minified flags point at.
To report a security problem, use Report a vulnerability on modwright-community's Security tab, not a public issue.
Reporting a problem
Bug reports, game and feature requests and questions go to
modwright-community:
open an issue,
or ask in Discussions.
Include npx modwright --version, the game, the tool call and what it
returned. Release notes are in that repository's CHANGELOG.
License
MIT; see LICENSE. Files ModWright generates into your own projects are yours, with no attribution required. Data extracted from a game remains its publisher's.
