I want to build a fighting-game prototype in Unreal without clicking through the editor myself — describe the work, let an agent do it, and find out where that falls apart. Unreal Engine 5.8 ships a first-party MCP plugin from Epic, which makes that a real option for the first time.
Before writing a line of gameplay, one question had to be answered: if the agent does something wrong in my level, can I undo it?
Answering that took a full session. The answer turned out to be less interesting than everything I hit on the way to it.
What “write access to the editor” actually means
Every write-up I could find describes this as one plugin and one toolset. What’s actually on disk in 5.8 is three things:
Experimental/ModelContextProtocol— the server itself. Off by default.Experimental/ToolsetRegistry— the tool registry.Experimental/Toolsets/— 21 separate toolset plugins, plus anAllToolsetsaggregator that turns on every one of them.
Twenty-one. Among them: BlueprintTools (edit Blueprints), LogsToolset (read the editor’s own output log), EditorAppToolset (cvars, selection, viewport, and control of Play-In-Editor), DataTableTools, NiagaraToolsets, GASToolsets, PhysicsToolsets, UMGToolSet, SemanticSearchToolset, and a ProgrammaticToolset that chains other tools together with sandboxed Python.
The combination that changes what this is: Play-In-Editor plus reading the log. An agent that can change something, run it, and read what came out the other end is iterating. An agent that can only spawn actors is a remote control for a mouse. That gap is the entire reason this was worth setting up.
Two naming details cost me time before any of that. The toolset plugin is listed as EditorToolset, one word, so searching the Plugins panel for “Editor Toolset” returns nothing. And both plugins are marked Experimental, which the panel hides until you enable Show Experimental Plugins in its filter. Searching for a plugin that is installed and sitting right there, and getting an empty list, reads exactly like it never shipped.
The server came up healthy with nothing behind it
I enabled the server, restarted, and it started clean. Connections accepted, handshake fine. The only trace of the problem was this, at Display level:
LogToolsetRegistry: Display: Registering Toolset ToolsetRegistry.AgentSkillToolset
LogModelContextProtocol: Tool search enabled: registered 3 meta-tools
(1 toolsets discoverable via list_toolsets)
One toolset — and that one ships with the registry itself. EditorToolset hadn’t come along. So the server was up, accepting connections, and exposing three meta-tools that could discover nothing at all. No warning. No error. Nothing above Display anywhere in the log.
The failure mode is a working connection to an editor the agent cannot touch, and every surface-level signal says success.
What makes it worse is the number that looks like the health check. registered 3 meta-tools comes from bEnableToolSearch, which is on by default: rather than registering every tool up front, the server exposes list_toolsets / describe_toolset / call_tool and lets the model discover the rest on demand. That’s a sensible design for a surface this wide. It also means a completely healthy setup and a completely broken one both print three meta-tools. The real signal is the toolset count sitting next to it in parentheses: 19 is healthy, 1 is dead.
If you take one operational thing from this post, take that line. It’s the only difference between the two states that a human can see.
The plugin switched itself off
I’d enabled EditorToolset through the Plugins panel. The log has the experimental-plugin confirmation dialog answered Yes, timestamped, and the .uproject was written with the plugin in it — I read the file.
Some time later the .uproject no longer contained the entry. I didn’t remove it. Nothing else on the machine had the file open.
I can’t explain the mechanism, and the file timestamps don’t line up cleanly enough with when I last observed the entry present for me to claim one. So: it was enabled, then it wasn’t, and nothing in the log marks the transition. I re-enabled it by editing the .uproject directly, which held. If it happens a second time it becomes a reproducible bug and I’ll have something to report.
I’m including it unresolved on purpose. The setup notes people publish are the ones where everything eventually worked, which is why they’re all so much smoother than the actual experience of doing it.
Read the source, not the blog posts
The settings page for the plugin was not findable by browsing Editor Preferences. I went to the header instead — ModelContextProtocolSettings.h — and the reason is right there: the class is a UDeveloperSettings with config=EditorPerProjectUserSettings. Every value is a plain ini key and the GUI panel is optional. Defaults, from source:
| Key | Default |
|---|---|
ServerPortNumber |
8000 |
ServerUrlPath |
/mcp |
bAutoStartServer |
false |
bEnableToolSearch |
true |
Which means the whole configuration is four lines in a versioned file, and it survives wiping Saved/:
[/Script/ModelContextProtocolEngine.ModelContextProtocolSettings]
bAutoStartServer=True
ServerPortNumber=8000
ServerUrlPath=/mcp
bEnableToolSearch=True
The console commands are similarly undocumented outside the source. ModelContextProtocol.GenerateClientConfig takes ClaudeCode|Cursor|VSCode|Gemini|Codex|All and upserts into an existing config rather than overwriting it, which is worth knowing before you run it against a file you care about. Siblings: .StartServer, .StopServer, .RefreshTools.
One more thing that unblocks people: none of this requires an agent client at all. The server is plain HTTP. initialize, keep the Mcp-Session-Id header, then tools/call — any HTTP client works. I ran the entire session that way, from a shell, because the client wasn’t launched from the directory holding .mcp.json and I didn’t want to debug two things at once.
Tool errors as prompt surface, and the bug in them
Call find_actors with no arguments and the error response contains the complete JSON Schema for the function. The model gets one call wrong and learns the exact signature from the failure. Whoever wrote that understood that a tool error is not a log line — it’s the highest-leverage text in the system, because it arrives at the exact moment the model is wrong and still has a chance to recover.
The same response also showed me a schema bug. find_actors marks name, tag and collision_channels as required. They’re optional filters. Listing every actor in the level means explicitly passing "", "", []. The other filters on the same function — root, actor_type, bounds — carry default: null and are correctly optional.
Optionality didn’t survive schema generation for three fields out of six. And the consequence isn’t a type error, it’s a behavioral one: a model reading required: name will invent a plausible name to fill the field, then report back that it found nothing. The tool worked. The answer was wrong. Nothing anywhere logs a problem.
Half the failures in an agent system look like this — a component behaving exactly as written, producing a confidently wrong result, with no error to grep for.
The undo test
This is the one that decides whether the write tools stay on.
I had the agent spawn a StaticMeshActor named MCP_SmokeCube through add_to_scene_from_class. Actor count went 145 → 146, and find_actors located it by name. Then Ctrl+Z in the editor.
Count back to 145. The actor gone. Nothing else in the level touched.
The plugin participates in Unreal’s transaction system. That’s the result I wanted, and the write tools stay on. If it hadn’t, this post would have been about turning them off.
The thing I actually learned
Here’s what fell out of that test, and it matters more than the result.
A StaticMeshActor with no mesh assigned draws nothing. So that actor existed in my level for several minutes and was never visible on screen. Not hidden — there’s no eye icon toggled off, no layer to unhide. It renders nothing because there’s nothing to render.
And the outliner didn’t help either. The default level carries around 145 actors, mostly LandscapeStreamingProxy entries. One new row in a list of 145 does not announce itself.
So an agent with editor write access can create things you do not see. It didn’t hide anything. It didn’t do anything wrong. The object was simply below the threshold of what a human staring at the screen registers as a change.
I had been sitting there watching, and watching told me nothing. What told me something was the number: 145 before, 146 after, 145 again. Supervision of an agent is a checksum, not a glance. If your only verification is that the screen looks about right, you are not verifying anything — you’re confirming that the agent’s mistakes were large enough to be rude about it.
This is the same shape as the previous two things I’ve written about. An agent’s failures are rarely dramatic. They’re a field the model filled in from its training cutoff, or a filter it invented a value for, or an object it left in your scene that renders as nothing. In every case the system reports success, and the only way to catch it is to have decided in advance what you were going to count.
Where this goes next
The level is still /Temp/Untitled_1, which means it has no file on disk, which means the git baseline I set up so carefully protects the config and the .uproject and nothing else. My safety net that day was Ctrl+Z and closing without saving. That gets fixed before anything else.
Then the real work: a capsule that moves, one attack, a hitbox and a hurtbox — and finding out where the agent falls over once a task stops being one tool call and starts being a thing that has to actually work.
I’m keeping the log as it happens, including the parts that don’t work. Those are the parts I couldn’t find written down anywhere.
Ruiciro Rivera — Senior AI engineer, AI enthusiast, and builder of worlds. By day I build production LLM systems; by night I ship my own products with Claude — and, occasionally, a video game. Find me on LinkedIn and GitHub.
← back