Skip to content

Using the Gateway

ChiR24 edited this page Sep 30, 2026 · 6 revisions

Use it · Using the Gateway: search, describe, execute

The server exposes exactly one MCP tool, unreal. Everything the editor can do sits behind it, reached through four operations:

🔎 search
Find capabilities from a few plain words.

📄 describe
Read one capability's exact contract, or browse the catalog.

▶️ execute
Run one capability with validated parameters.

⚙️ configure
Enable or disable groups of internal tools. Never touches the editor.

Your assistant drives all of this by itself; the tool description and the server's instructions tell it the order. This page is for understanding what it does, writing better prompts, debugging a failed call, or building your own client.

Note

The replies on this page are real, trimmed for length, taken from the native HTTP route. The stdio route carries the same information with a few differences in field names and browsing; they are listed in Differences between the routes.

On this page · Workflow · search · describe · execute · configure · Errors · Old direct calls · Route differences · Tips

The workflow

sequenceDiagram
    autonumber
    participant M as AI model
    participant G as unreal tool
    participant E as Unreal Editor
    M->>G: search · "spawn actor"
    G-->>M: matching rows, each with a nextCall
    M->>G: describe · tool + action from the row
    G-->>M: the exact contract
    M->>G: execute · tool + action + params
    G->>E: validated request
    E-->>G: result
    G-->>M: data + receipt
Loading
  1. search with 2-4 words naming a verb and an object.
  2. describe the row that fits, by sending that row's nextCall unchanged.
  3. execute with the same tool and action, plus params that use only the parameter names describe listed.

Tip

Search rows and errors carry a nextCall, a ready-to-send request for the next step. A model that follows nextCall rarely gets lost. Nothing is built from guessed names.

search

{ "operation": "search", "query": "spawn actor", "limit": 2 }
Reply
{
  "success": true,
  "operation": "search",
  "query": "spawn actor",
  "catalogRevision": "fbe48b864b6054b5",
  "total": 95,
  "hasMore": true,
  "nextCursor": "2",
  "results": [
    {
      "capability": "control_actor.spawn",
      "parent": "control_actor",
      "domain": "actor",
      "family": "spawn",
      "effect": "write",
      "available": true,
      "summary": "Spawn an actor from a class or mesh path, or from a Blueprint, or many actors in one batch.",
      "score": 452,
      "matchReasons": ["id-exact", "id", "family", "domain", "topic", "summary", "parent"],
      "nextCall": { "operation": "describe", "tool": "control_actor", "action": "spawn" }
    },
    {
      "capability": "control_actor.delete",
      "parent": "control_actor",
      "effect": "destructive",
      "summary": "Delete actors by name, or every actor carrying a tag.",
      "nextCall": { "operation": "describe", "tool": "control_actor", "action": "delete" }
    }
  ]
}

Each row names the capability, the internal tool it belongs to (parent), whether it reads, writes or destroys (effect), and a one-line summary.

Field Use
query 2-4 plain words: spawn actor, add variable blueprint, save level, set light intensity. Full sentences and filler words rank worse.
domain · family · tool Narrow to one area, for example "domain": "widget" or "tool": "manage_sequence"
effect read, write or destructive. "effect": "read" is handy for "look, don't touch".
limit · offset · cursor Paging. limit defaults to 12 and caps at 25; pass nextCursor back as cursor to continue.
maxBytes Byte ceiling for the reply (512 to 262,144). Rows drop from the end until it fits, and truncated says so.

describe

Send the row's nextCall:

{ "operation": "describe", "tool": "control_actor", "action": "spawn" }
Reply (trimmed)
{
  "success": true,
  "operation": "describe",
  "scope": "capability",
  "capability": "control_actor.spawn",
  "tool": "control_actor",
  "action": "spawn",
  "summary": "Spawn an actor from a class or mesh path, or from a Blueprint, or many actors in one batch.",
  "parameters": [
    { "name": "classPath", "type": "string", "required": false, "description": "Unreal class path (e.g. /Script/Engine.PointLight) for the actor to spawn.", "variants": ["class"] },
    { "name": "blueprintPath", "type": "string", "required": false, "description": "Canonical /Game Blueprint asset path to spawn from.", "variants": ["blueprint"] },
    { "name": "actors", "type": "array", "required": false, "description": "Actors to spawn, 1-500. ...", "variants": ["batch"] }
  ],
  "inputSchema": { "...": "JSON Schema of exactly these parameters" },
  "outputSchema": { "...": "what a successful reply contains" },
  "examples": [ { "title": "...", "input": { "...": "..." }, "output": { "...": "..." } } ],
  "policy": { "requiredScope": "write", "consent": "none", "dataAccess": "project-write" },
  "availability": { "requiredPlugins": [], "editorStates": ["edit"] },
  "whenToUse": ["..."],
  "whenNotToUse": ["..."]
}

The reply is the whole contract for that one capability and nothing else: parameters, input and output schemas, a worked example, policy, availability, and when to use it or not.

variants shows up when a capability is a family: one capability whose selector parameter picks the variant. Here spawnKind is class (the default), blueprint or batch, and actors is only read by the batch variant. More in 🧰 Tools Reference.

Browsing instead of searching

describe with fewer selectors walks the catalog:

Request Returns
{ "operation": "describe" } The 23 internal tools, each with its action count and a nextCall
{ "operation": "describe", "tool": "control_actor" } That tool's action names, its domains and families, and a drillDown call
{ "operation": "describe", "tool": "control_actor", "action": "spawn" } One exact contract
{ "operation": "describe", "tool": "control_actor", "action": "spawn", "param": "actors" } One parameter's full schema, plus the consent grant when the capability needs one
{ "operation": "describe", "tool": "manage_asset", "action": "create_noise_texture" } The contract an old name resolves to: the texture.create_texture family. Executing the old name fills in kind: "noise" for you.

The stdio route also browses by domain and family; see Differences between the routes.

execute

{
  "operation": "execute",
  "tool": "control_actor",
  "action": "spawn",
  "params": {
    "classPath": "/Script/Engine.PointLight",
    "actorName": "KeyLight",
    "location": [0, 0, 300]
  }
}

The gateway checks four things before anything reaches the editor:

Rule Detail
✅ Only declared parameters An unknown name is refused with UNDECLARED_PARAMETER and the list of allowed names. Casing matters.
✅ Types as described Use the shapes describe lists. control_actor.spawn takes location as [x, y, z], for example.
✅ No control fields in params action, subAction, operation and consent are envelope fields, not parameters.
✅ Name the target by tool + action Exactly as the search row and describe gave them. The stdio route also accepts the capability id as "capability": "control_actor.spawn"; sending both is fine only when they agree, otherwise the call gets FORM_CONFLICT.

What comes back

A successful reply has success: true, the payload in data (checked against the capability's output schema), and a receipt:

Receipt field Meaning
status success or error
capabilityId The capability that actually ran
changes What the call changed: assets, actors, levels
handles Typed references to what it created or touched, for follow-up calls
warnings Things that worked but deserve a look
timingMs How long the editor took
validation Whether the reply passed the capability's output schema
liveRevisions Selection, level, asset-registry and package counters after the call
catalogRevision · capabilityRevision · schemaRevision Which contract version validated the call
correlationId · requestId For matching the call to log lines
error On failure: kind, code, message, and pointer for validation errors

Replies report what actually happened. A partial result is a failure (DELETE_PARTIAL, PARTIAL_FAILURE) that lists what did and didn't happen, rather than a quiet success.

Consent

62 capabilities require a consent grant: all 29 destructive ones, plus 33 writes such as duplicating an asset. describe returns the exact grant, here from { "operation": "describe", "tool": "control_actor", "action": "delete", "param": "actorName" }:

{
  "consentGrant": {
    "capability": "control_actor.delete",
    "acknowledge": "explicit",
    "nonce": "62A12661-4F6A-1C97-339E-69921EE9E2A7"
  }
}

Send it back, unchanged, as a top-level consent field next to params:

{
  "operation": "execute",
  "tool": "control_actor",
  "action": "delete",
  "params": { "actorName": "OldLight" },
  "consent": { "capability": "control_actor.delete", "acknowledge": "explicit", "nonce": "62A12661-4F6A-1C97-339E-69921EE9E2A7" }
}
  • Without a grant, the call is refused with CONSENT_REQUIRED.
  • The nonce makes a grant single-use: sending it a second time is refused with CONSENT_REUSED, so run describe again for the next call. The stdio route issues grants without a nonce.
  • A grant is never inferred from earlier calls or from running on localhost, and the plugin checks it itself whatever the client sends. More in 🔐 Security.

Execution options

Optional controls go in a top-level options object, never inside params:

Option Effect
idempotencyKey 1-128 characters. Repeating an execute with the same key and parameters returns the first receipt instead of running again, so retries are safe.
expectedCatalogRevision Refuse with STALE_STATE if the catalog changed since you read it
expectedRevisions { selection, level, assetRegistry, package } counters from a receipt's liveRevisions or the ue://state/revisions resource. Refuse with STALE_STATE if any moved.
timeoutMs Deadline for this call, 1 to 600,000 ms

Anything else is refused with UNSUPPORTED_OPTION. Without timeoutMs, each capability gets a budget from its declared cost. ⚙️ Configuration › Timeouts has the details, including the 5-minute ceiling per call.

configure

configure runs one manage_tools action. It changes which internal tools execute may use. The visible tool list never changes: it is always just unreal.

{ "operation": "configure", "action": "disable_category", "params": { "category": "gameplay" } }
Action Params
get_status · list_tools · list_categories none
enable_tools · disable_tools tools: array of internal tool names
enable_category · disable_category category: core, world, gameplay, utility or all
reset none. Re-enables everything.

All 23 tools start enabled. inspect and manage_tools can't be disabled, so the core category can't be switched off as a whole. A disabled tool's capabilities still show up in search, marked unavailable, and execute refuses them with TOOL_DISABLED. On the native route, turning off Load All Tools on Start starts with only the core tools enabled.

When a call fails

Every failure carries errorCode, message and an executable nextCall; validation errors add pointer, naming the offending field. A misspelled parameter on a read-only call, for example:

{ "operation": "execute", "tool": "control_actor", "action": "list", "params": { "colour": "red" } }
{
  "success": false,
  "errorCode": "UNDECLARED_PARAMETER",
  "message": "Undeclared parameter 'colour' (allowed: className, filter, folder, limit, near, offset, propertyNames, radius, summary, tag)",
  "pointer": "/colour",
  "nextCall": { "operation": "describe", "tool": "control_actor", "action": "list" }
}
Code Meaning What to do
🔤 UNKNOWN_TOOL · UNKNOWN_ACTION · UNKNOWN_CAPABILITY A name was guessed search again
🧾 UNDECLARED_PARAMETER · MISSING_REQUIRED_PARAMETER · MISSING_REQUIRED_ONEOF · INVALID_PARAMETER_VALUE · INVALID_PARAMETER_TYPE Parameters don't match the contract Read pointer and the message, or describe again
✋ CONSENT_REQUIRED · CONSENT_REUSED The capability needs a (fresh) consent grant describe for a grant, send it once
🔌 NOT_CONNECTED The editor isn't reachable Start the editor; see 🩺 Troubleshooting
🚫 TOOL_DISABLED Switched off with configure configure › enable_tools or reset
🔐 SCOPE_NOT_GRANTED · PATH_NOT_PERMITTED · PROJECT_NOT_PERMITTED · COMMAND_BLOCKED · QUOTA_EXCEEDED Refused by the security policy See 🔐 Security
⏳ STALE_STATE A revision pin in options no longer matches Re-read, then retry
📏 RESULT_TOO_LARGE The reply would exceed 100,000 characters (6,000,000 for images) Narrow the request with filters or limits
⚙️ UNSUPPORTED_OPTION Unknown key in options Use only the four options above
🪟 EDITOR_BLOCKED The editor's game thread stalled for over 15 s, usually a modal dialog Dismiss the dialog in the editor
🎮 Handler codes: ASSET_NOT_FOUND, MESH_NOT_FOUND, DELETE_PARTIAL, … The editor refused, or only partly did, the work Read message; it names the cause

Old direct tool calls

Before 0.6, clients called manage_asset, control_actor and the rest directly. Such a call is no longer executed, on either route. It returns a receipt whose nextCall is the same request through the gateway:

{
  "success": false,
  "errorCode": "DIRECT_TOOL_CALL_REMOVED",
  "tool": "manage_asset",
  "message": "Direct tool calls are removed. Call the 'unreal' gateway with operation 'execute' to run 'manage_asset.list'.",
  "nextCall": { "operation": "execute", "tool": "manage_asset", "action": "list", "params": { "path": "/Game" } }
}

More in ⬆️ Upgrading.

Differences between the routes

Both routes serve the same catalog and enforce the same rules; they differ in a few shapes. Following nextCall works on both.

🌐 Native HTTP 🧩 stdio
Name a capability tool + action tool + action, or capability (the id, e.g. control_actor.spawn)
describe with no selector Lists the 23 internal tools Lists the 40 domains; domain and family then browse one level at a time
describe reply The contract The contract, plus a nextCall for execute
Search row fields parent, available, score, matchReasons parentTool, availability, policy, runnable, reasons
Consent grant Includes a single-use nonce capability + acknowledge
Validation errors pointer pointer, plus suggestions and allowedParameters

Getting good results

Tip
🎯 Ask for outcomes, not tool calls. "Add a red point light above the door and make it flicker" works better than naming capabilities; the assistant searches for each step itself.
📁 Give real paths. Assets live under /Game/... (not /Content/...), and classes look like /Script/Engine.PointLight.
🔍 Ask it to check its work. A screenshot (control_editor.screenshot), a property read (inspect), or control_actor.audit_placement for actors sunk into the floor or floating catch mistakes that a successful reply won't.
📦 Batch big jobs. Spawning hundreds of actors, or building a whole Blueprint event graph (manage_blueprint.build_graph), is one call rather than hundreds.
↩️ Undo exists. control_editor.undo and redo step through the editor's transaction history, like Ctrl+Z.
🧭 If the model keeps inventing names, tell it once: "search first, then describe, then execute, and copy each nextCall." The server's instructions say the same, but not every client passes them on.

🏠 Home

Get started
🚀 Quick Start
📦 Installation
🔌 Connecting Clients

Use it
🧭 Using the Gateway
🧰 Tools Reference
📚 Resources and Prompts

Set it up
⚙️ Configuration
🔐 Security

Help
🩺 Troubleshooting
💬 FAQ
⬆️ Upgrading from 0.5.x

Contribute
🛠️ Development


Covers the 0.6 line · Releases · Discussions

Clone this wiki locally