DraxGUI MCP adapter
Updated 28 October 2026
DraxGUI exposes its project editor to AI clients (Claude Code, Codex, MCP Inspector or any Model Context Protocol client) through an MCP endpoint built into the editor web server. AI clients edit projects through the same services as the browser editor: the same project store and edit ops, the same validation, asset checks, build jobs and pending-edit preparation. DraxGUI does not contain an AI model, does not call any paid API and never receives your AI account credentials.
What an AI client can do: list and read projects, learn the format, apply batched edit transactions, upload images, validate, start and cancel builds, and prepare a pending edit.
What it cannot do: apply or roll back a live deployment (a person runs the returned
/draxgui applyedit <id> command in game or on the console), run shell or console commands,
read or write arbitrary files, fetch URLs, or use the browser login link.
- Endpoint:
POST <editor base URL>/mcp, for examplehttp://127.0.0.1:8765/mcp - Stdio bridge for stdio-only clients:
draxgui-mcp-bridge-<version>.jar, under Downloads in the licence panel
1. Create a token
MCP clients use their own bearer tokens. A token has a name, an expiry, a project scope and a
set of capabilities. Only a SHA-256 verifier is stored (plugins/DraxGUI/mcp-tokens.json,
owner-only file permissions where the file system supports them); the secret is shown once.
In game (with the DraxGUI admin permission) or on the console:
/draxgui mcp token create <name> <projects|all> [--caps <c1,c2,...>] [--ttl <duration>]
/draxgui mcp token list
/draxgui mcp token revoke <name>
projectsis required (comma-separated ids, orall); without--capsthe token is read-only. The command calls theMcpTokensJava API described in §7.
| Option | Meaning | Default |
|---|---|---|
projects |
Comma-separated project ids, or all for every project (including future ones) |
required |
--caps |
Subset of project.read, project.write, asset.write, preview, build, deploy.prepare, script.write |
read-only (project.read); project.read is always added |
--ttl |
Lifetime, 5 minutes to 365 days | 30 days |
Pick the smallest scope that does the job:
| Use | Capabilities |
|---|---|
| Read-only review | project.read |
| Authoring screens/HUDs/popups/graphs | project.read,project.write |
| ... plus images | add asset.write |
| ... plus scripts (Java/JavaScript/Python source text) | add script.write |
| Build and prepare deployments | add build,deploy.prepare |
A pending edit packages the whole server pack (every project), so prepare_deploy only works
for a token scoped to *, or when the token's projects are the only projects on the server.
Token handling
- Put the token in an environment variable (
DRAXGUI_MCP_TOKEN) or a file readable only by you. Never put it in project files,.mcp.jsonliterals, screenshots or chat. - Console issuance is visible in the server console log (DraxGUI does not log it again, but the host may keep console history). Prefer issuing tokens in game when the console log is shared.
- Revoke immediately if a token leaks (
/draxgui mcp token revoke <name>); it stops working on the next request. Expired and revoked tokens are kept in the list for 30 days for audit. - Every write is recorded in
audit.jsonlasmcp:<token name>.
2. Connect a client over HTTP
Use HTTP when the client supports remote/HTTP MCP servers with custom headers. Set the token in
your environment first (PowerShell: $env:DRAXGUI_MCP_TOKEN = Get-Content <token file>; bash:
export DRAXGUI_MCP_TOKEN="$(cat <token file>)").
Claude Code — project file .mcp.json (the file only contains the variable name; Claude
Code expands ${DRAXGUI_MCP_TOKEN} when it loads the server):
{
"mcpServers": {
"draxgui": {
"type": "http",
"url": "http://127.0.0.1:8765/mcp",
"headers": { "Authorization": "Bearer ${DRAXGUI_MCP_TOKEN}" }
}
}
}
Or for your user only: claude mcp add --transport http draxgui http://127.0.0.1:8765/mcp --header "Authorization: Bearer <TOKEN>"
(this stores the literal token in ~/.claude.json; the .mcp.json form above keeps it out of files).
Codex — ~/.codex/config.toml:
[mcp_servers.draxgui]
url = "http://127.0.0.1:8765/mcp"
bearer_token_env_var = "DRAXGUI_MCP_TOKEN"
MCP Inspector (CLI, tested with @modelcontextprotocol/inspector@2.11.0) — config file
draxgui-inspector.json kept outside the repository:
{ "mcpServers": { "draxgui": { "type": "http", "url": "http://127.0.0.1:8765/mcp",
"headers": { "Authorization": "Bearer <TOKEN>" } } } }
npx -y @modelcontextprotocol/inspector@2.11.0 --cli --config draxgui-inspector.json --server draxgui --method tools/list
npx -y @modelcontextprotocol/inspector@2.11.0 --cli --config draxgui-inspector.json --server draxgui \
--method tools/call --tool-name list_projects --tool-args-json "{}"
Python SDK (tested with mcp==2.3.0):
import os, httpx2
from mcp import Client
from mcp.client.streamable_http import streamable_http_client
http = httpx2.AsyncClient(headers={"Authorization": "Bearer " + os.environ["DRAXGUI_MCP_TOKEN"]})
async with Client(streamable_http_client("http://127.0.0.1:8765/mcp", http_client=http)) as client:
print(await client.call_tool("list_projects", {}))
3. Connect a stdio-only client through the bridge
The bridge is a small Java 21 program the AI client launches. It speaks MCP on stdin/stdout and
forwards every message to the DraxGUI endpoint with your token. Download it (draxgui-mcp-bridge-<version>.jar) from the
licence panel: sign in, and it is under Downloads while your licence is active.
MCP Bridge Setup.txt in the BuiltByBit download walks you through the setup.
java -jar draxgui-mcp-bridge.jar --url <http(s)://host:port/mcp> [--token-file <path>]
[--timeout-seconds N] [--allow-insecure-http] [--verbose]
- Token: environment variable
DRAXGUI_MCP_TOKEN, or--token-file. A--tokenargument is refused (command lines are visible to other local users). The token is never printed. --urlmay be the base URL;/mcpis appended when the path is empty.DRAXGUI_MCP_URLworks too.- Plain
http://is only accepted for loopback hosts unless you pass--allow-insecure-http; use HTTPS (a reverse proxy) for remote servers. - stdout carries only protocol messages; diagnostics go to stderr (
--verboseadds method/status lines). - If the server restarts during an initialize-era session, the bridge re-initializes and retries once.
Claude Code .mcp.json:
{
"mcpServers": {
"draxgui": {
"type": "stdio",
"command": "java",
"args": ["-jar", "/opt/draxgui/draxgui-mcp-bridge.jar", "--url", "https://editor.example.com/mcp"],
"env": { "DRAXGUI_MCP_TOKEN": "${DRAXGUI_MCP_TOKEN}" }
}
}
}
Codex ~/.codex/config.toml:
[mcp_servers.draxgui]
command = "java"
args = ["-jar", "/opt/draxgui/draxgui-mcp-bridge.jar", "--url", "https://editor.example.com/mcp"]
env_vars = ["DRAXGUI_MCP_TOKEN"]
On Windows use the full path to java.exe if java is not on PATH, and forward slashes or
escaped backslashes in JSON/TOML paths.
4. Remote access
The editor (and so /mcp) listens on editor.bind-address (loopback by default). For an AI
client on another machine, put the editor behind an HTTPS reverse proxy and set
editor.public-url (see the editor documentation). The MCP endpoint accepts only requests whose
Host header names this server (loopback names with the configured port, the bind address, or
the public URL host), which blocks DNS-rebinding; it rejects any browser Origin other than
the configured editor origin. Do not expose the editor port directly to the internet over plain
HTTP: bearer tokens would travel in clear text.
5. Tools
All tools take a JSON object; schemas are published in tools/list (closed objects with
patterns, ranges and enums). Results are JSON (structuredContent, and the same JSON as text).
Lists are paginated with cursor/limit and return nextCursor when more items exist.
Tools you lack the capability for are hidden from tools/list and refused if called.
| Tool | Capability | Writes | Purpose |
|---|---|---|---|
list_projects |
project.read | Projects in the token's scope with head revision and counts | |
get_schema_info |
project.read | Document format, id patterns, every op with fields and capability, expression syntax, limits, templates, a worked example batch | |
list_element_types |
project.read | Element catalogue (compact, or one full entry with type) |
|
list_node_types |
project.read | Logic node catalogue (filter by category, full entry with type) |
|
get_capabilities |
project.read | Token scope, Minecraft client version registry, shader families, compiler/script runtimes, capability registry entries | |
get_project |
project.read | Document at head or revision; sections for parts; large documents return a summary |
|
get_container |
project.read | One screen/HUD/popup with elements and a parent/child tree | |
preview_container |
preview | PNG of one screen/HUD/popup composed like the Shader-Safe runtime from a fresh compile (the build's own shapes, images, icons; authored state, variable defaults, player "Steve"), returned as an image content item plus {width, height, scale, notes}. Text uses a substitute font stretched to Minecraft's widths, heads/models show placeholders, HUDs use a 480x270 GUI px reference screen, Standard screens show their 2D canvas. One preview at a time, 30 s compile limit, at most 2048 px per side. Not proof of in-game rendering; the standalone dev server answers preview_unavailable. |
|
list_revisions |
project.read | Recent revisions with actor and summary | |
validate_project |
project.read | Diagnostics with JSON pointer and container/element/node/script-line ref | |
apply_ops |
project.write (+ script.write / asset.write for those ops) | yes | One transaction of edit ops against baseRevision with requestId |
create_project |
project.write | yes | New project from a template |
restore_revision |
project.write | yes | Copy an older revision into a new revision |
list_snapshots |
project.read | Named snapshots (newest first): name, source revision, time, actor, note | |
create_snapshot |
project.write | yes | Save a revision (default head) under a name (a-z 0-9 - _, up to 48); at most 50 per project; a taken name is refused. Snapshots survive revision pruning; deleting one is an editor action |
restore_snapshot |
project.write | yes | Copy a snapshot into a new revision against baseRevision |
upload_asset |
asset.write | yes | Base64 image/animation bytes (up to the editor's upload limit, 32 MiB by default) or a TTF/OTF font (<= 8 MiB), same limits and validation as the editor upload; optional license (record it for fonts: DraxGUI ships none and rasterises them into the pack) |
list_assets |
project.read | Asset entries with size, frames, media type, hash | |
search_icons |
project.read | Bundled icon library search (use as props.icon) |
|
start_build |
build | yes | Build an immutable revision (Java versions, optional Bedrock) |
get_build_status |
project.read | State, steps, diagnostics, artifact names/sizes/hashes (never bytes) | |
cancel_build |
build | yes | Cancel a queued/running build |
prepare_deploy |
deploy.prepare | yes | Pending edit for a succeeded build; returns the /draxgui applyedit <id> command for a person to run |
list_deployments |
project.read | Pending/applied edits in scope and the active generation |
Authoring loop
get_schema_infoonce,list_element_types/list_node_typesas needed.get_project(orget_container) and noterevision.apply_opswithbaseRevision= that revision and a newrequestId; batch related edits (up to 500 ops) — the batch is all-or-nothing.- Read the returned
revisionanddiagnostics; fix errors with anotherapply_ops. start_build→ pollget_build_status→prepare_deploy→ give the command to a person.
Retrying with the same requestId returns the original result ("replayed": true) instead of
applying twice. Request ids are private to each token.
Wiring logic graphs. A handler (events.click, a container's events.open, ...) is either a
list of actions or {"graph": "<graph id>", "entry": "<event node id>"}. Event nodes inside a
graph (event.click, event.open, ...) are only entry markers: an event.click node naming an
element runs when that element's events.click routes to it. validate_project reports event
nodes nothing routes to as unbound_event warnings (with the exact events.* value to set) and a
handler naming a missing node as a missing_entry error.
Worked example. The "Daily Rewards" demo was authored through these tools
with the official Python SDK: create, icon search, variables, popup, screen (60 elements), graph,
validate → fix the reported unbound_event bindings → validate clean → build (Java + Bedrock) →
prepare_deploy. A person then ran
/draxgui applyedit <id> and the screen was tested in a 1.21.4 client (claim once, duplicate
clicks refused, next-day branch).
Errors are tool results with isError: true and
{"error": {"code", "message", "details"}}. Common codes:
| Code | Meaning / next step |
|---|---|
invalid_arguments |
Argument failed the schema; details.errors names the field (ops[2].op: must be one of ...) |
missing_capability |
Token lacks details.requiredCapability |
project_forbidden |
Project outside the token's scope |
not_found |
Project, container, element, build or revision missing; message lists what exists |
revision_conflict |
baseRevision is stale; details.currentRevision and details.changesSince show what changed; re-read and retry |
op codes (duplicate_id, bad_parent, bad_patch, ...) |
details.opIndex, details.op, ids and a hint; nothing was changed |
unsupported_media, too_large, bad_base64 |
Upload refused, with the reason |
build_not_finished, deploy_refused, deploy_scope |
Deployment preparation refused, with the reason |
Protocol-level problems (unknown tool, malformed request) are JSON-RPC errors.
6. Protocol notes
- Supported revisions: 2026-07-28 (stateless per-request
_meta,server/discover,MCP-Protocol-Version/Mcp-Method/Mcp-Nameheader validation) and the initialize-based 2025-11-25, 2025-06-18, 2025-03-26 (session viaMcp-Session-Id, ended withDELETE /mcp). The server picks the era from each request; both work on the same endpoint. - Responses are single JSON objects (an SSE frame only when a client does not accept JSON).
There is no GET stream (
405), nosubscriptions/listen, no resumability, no batching. - Only the
toolscapability is offered (no resources, prompts, logging, sampling or elicitation). - Authentication is a pre-provisioned bearer token. OAuth discovery (Protected Resource Metadata)
is not implemented: an OAuth-capable client that receives
401may try to start an OAuth login. Fix the configured token instead. - Limits: 120-request burst and 2 requests/second sustained per token (
429+Retry-After); JSON bodies up to the editor's JSON limit; asset uploads up to the editor's upload limit (one large upload at a time); 30 failed authentications per minute per address. - Legacy sessions are kept in memory (24 h idle); a restart ends them and clients re-initialize.
| HTTP | When |
|---|---|
| 400 | Malformed JSON-RPC, header/body mismatch (-32020), unsupported version (-32022 with data.supported), missing _meta fields (-32602), no legacy session |
| 401 | Missing, invalid, expired or revoked token (WWW-Authenticate: Bearer) |
| 403 | Browser Origin not allowed, or wrong Host |
| 404 | Unknown method in the 2026-07-28 era (-32601), unknown/expired legacy session |
| 413 / 415 / 429 | Body too large / not application/json / rate limited |
7. Java API (plugin integration)
McpTokens tokens = McpTokens.open(dataDir.resolve("mcp-tokens.json"), logger);
McpServer.install(webServer, tokens); // before webServer.start(), after Api.install
McpTokens.Created c = tokens.create(name, senderName, List.of("*"), caps, Duration.ofDays(30));
c.secret(); // show once to the issuer only
tokens.list(); // TokenInfo: id, name, actor, projects, capabilities, times, status
tokens.revoke(name);
All methods do blocking file I/O; call them off the Minecraft server thread. create throws
IllegalArgumentException with an operator-facing message for invalid input.