Prefab MCP
Hosts an MCP server inside a Hytale server so agents such as Claude Code, Github Copilot, or Codex can author prefabs against that server's own live block palette.
Five tools are available: search the palette, inspect a block, run a build script, render the result, and publish it once it is finished.
| MCP tool | Description |
|---|---|
search_blocks |
Real names from the running server, mods included, with each block's size in cells |
get_block_information |
Everything about a block: its face texture (or inventory icon for model-drawn blocks), size and footprint at each rotation, supported yaw/pitch, and model extent |
build_prefab |
Runs your JavaScript, writes <name>.prefab.json to a private staging area |
render_prefab |
PNG from any camera angle, so the agent can see what it built |
publish_prefab |
Copies a staged prefab into prefabs/prefab-mcp/, where players can find it |
Install
Start the server and look for:
[PrefabMcp|P] Palette snapshot: <your-total> blocks and fluids
[PrefabMcp|P] MCP server listening on http://127.0.0.1:[mcp-port]/mcp
Run /prefab-mcp in game and it opens a page with the configuration, and your personal token when
one is required, in fields you can select and copy. On a server that is local only, which
is the default, that looks like:
MCP server listening on port <mcp-port>
Add this to your MCP client configuration on this machine. This server is local only, so it
accepts connections from this machine alone; to use it from other devices, set "localOnly": false
in mcp.json and restart.
{
"mcpServers": {
"hytale-prefab": {
"type": "http",
"url": "http://127.0.0.1:[mcp-port]/mcp"
}
}
}
Or point an agent on the same machine at it directly:
claude mcp add --transport http hytale-prefab http://127.0.0.1:[mcp-port]/mcp
Local only
By default the listener binds to the loopback address (127.0.0.1), the address a machine uses to
talk to itself. Traffic to it never leaves the machine, so only an agent running on the same machine
as the Hytale server can connect, and /prefab-mcp always gives 127.0.0.1 as the host.
To use it from other devices, set localOnly to false in mcp.json and restart. The listener then
binds to 0.0.0.0, every network address the machine has, and accepts connections from any device
that can reach the port. /prefab-mcp then prints a <server-host> placeholder instead, because
where the agent runs relative to the server, and whether a reverse proxy or DNS name sits in front, is
not something the server can see, so it asks rather than guesses:
Add this to your MCP client configuration. Replace <server-host> with this server's address, or
127.0.0.1 if the agent runs on this machine.
{
"mcpServers": {
"hytale-prefab": {
"type": "http",
"url": "http://[server-host]:[mcp-port]/mcp",
"headers": {
"Authorization": "Bearer 657827e9-8e16-4614-b533-4cd444373a46.HvjnLeoLyTwzJ-8kMPhP69Vze-7MXHZz"
}
}
}
}
Before turning it off, it is recommended to enable requirePersonalToken, as anyone with access to the port will be able to generate prefabs, regardless of their permissions on the server.
Authentication
Off by default. A new server sets requirePersonalToken to false, so it works the moment it boots:
a new server is also local only, so reaching it already requires being on the machine.
{ "requirePersonalToken": true }
Which mode is active is stated at boot:
[PrefabMcp|P] Personal tokens NOT required: anyone who can reach the endpoint may use every tool.
Set "requirePersonalToken": true in mcp.json to require one.
/prefab-mcp prints whichever configuration that server actually accepts: with the headers block
when a token is needed, without it when one is not. While tokens are off, tokenSecret sits unused in mcp.json,
but is still generated and kept, so switching over later does not change anyone's token.
When requirePersonalToken is true, every MCP request must carry the personal token of a player who
holds the permission below:
Authorization: Bearer <player-uuid>.<signature>
The two halves are checked separately. The token says which player; the permission system says
whether they may. So revoking someone's permission cuts off their MCP access immediately, with no
need to rotate the secret or reissue anyone else's token. Tokens are shown only by /prefab-mcp,
only to the player who ran it, and are never logged.
To revoke everyone at once, change tokenSecret in mcp.json and restart; every issued token
stops verifying. To revoke one player, take away their permission.
Where prefabs are created
build_prefab writes to a staging area in the plugin's data directory,
mods/willchil_PrefabMcp/staging/. That is outside prefabs/, so the agent's unfinished iterations
never show up in game. render_prefab reads from staging first. Only publish_prefab writes to
prefabs/prefab-mcp/, so agent-made prefabs never mix with the ones an operator or the in-game editor
made.
The staging area is deleted when the server shuts down. Anything the agent did not publish is lost.
Both locations are split by caller in the same way:
| Caller | Staging | Published |
|---|---|---|
| Anonymous, on a server not requiring tokens | staging/ |
prefabs/prefab-mcp/ |
| A player who was online at the time | staging/<username>/ |
prefabs/prefab-mcp/<username>/ |
| A player who was offline at the time | staging/offline-players/ |
prefabs/prefab-mcp/offline-players/ |
build_prefab and publish_prefab each take overwrite, which replaces an existing prefab of the
same name in staging or in the published folder respectively.
Permission
/prefab-mcp and every MCP request require willchil.prefabmcp.command. The node is logged at
startup:
[PrefabMcp|P] Registered /prefab-mcp (permission: willchil.prefabmcp.command)
Configuration
On first load the plugin writes mods/willchil_PrefabMcp/mcp.json:
{
"port": 7520,
"tokenSecret": "rK-FDBd0xT66CRThXuR30VQDwpj6D3-iSFveyJLinJw",
"requirePersonalToken": false
}
tokenSecret is randomly generated on first load, and is the salt every personal token derives
from.
The port it starts with is the game's own bind port plus 2000, so several servers on one machine do not collide.
-Dhytale.mcp.port=<n> overrides the file for one run without writing back to it.
If the chosen port is already taken the plugin logs a warning and stays off rather than quietly binding somewhere else.