The MCP Server
At the heart of ClaudusBridge is FClaudusBridgeServer — a lightweight HTTP server that exposes the Unreal Editor as a Model Context Protocol (MCP) endpoint. Any MCP-speaking client — the agent bundled with ClaudusBridge, or an external client such as Claude, Cursor, or Windsurf — connects to it and drives the editor by calling tools.
The server implements the Streamable HTTP transport from the MCP spec, speaks JSON-RPC 2.0, and runs entirely on your machine. By default it binds to 127.0.0.1:3000 and serves MCP at the /mcp path.
http://127.0.0.1:3000/mcp
Lifecycle
The server is started automatically during module startup when bAutoStartServer is enabled (the default). Internally, FClaudusBridgeServer::Start(Port, UrlPath):
- Validates the configured URL path (an invalid path logs an error and the server does not start).
- Acquires an
IHttpRouterfor the configured port from Unreal'sFHttpServerModule. - Binds the four routes described below.
- Calls
StartAllListeners()and installs a ticker for deferred work (such astools/list_changedbroadcasts).
Stop() unbinds every route, removes the ticker, and emits analytics SessionEnd events for any sessions that reached the Initialized state. The server is single-instance: calling Start() while already running logs a warning and restarts cleanly.
Routes
All MCP traffic is served from a single configurable path (default /mcp), differentiated by HTTP verb. A separate /health route is always bound for liveness probing.
| Verb | Path | Purpose |
|---|---|---|
POST | /mcp | Primary JSON-RPC channel. Carries initialize, tools/list, tools/call, resources/*, ping, and notifications. |
GET | /mcp | Opens a Server-Sent Events (SSE) stream for server-to-client messages. |
DELETE | /mcp | Tears down a session. |
GET | /health | Lightweight liveness probe. Returns JSON without an MCP handshake. |
Health probe
GET /health lets tooling confirm the bridge is up without performing a full MCP initialize handshake. It returns:
{ "status": "ok", "server": "ClaudusBridge", "port": 3000, "mcpPath": "/mcp" }
This is the cheapest way for a script or supervisor to verify that a given editor instance is reachable and to learn the port and MCP path it is actually serving on.
Origin validation
To defend against DNS-rebinding attacks (per the MCP security guidance), every POST validates the Origin header. Requests with no Origin (typical of non-browser clients) are allowed; requests whose origin host is anything other than localhost, 127.0.0.1, or [::1] are rejected with 403 Forbidden.
JSON-RPC methods
Requests are JSON-RPC 2.0 objects. The server dispatches on the method field.
| Method | Session required | Description |
|---|---|---|
ping | No | Liveness check; replies with an empty result. |
initialize | No (creates one) | Negotiates the protocol version and opens a new session. |
notifications/initialized | Yes | Client confirms it finished initializing; the session becomes Initialized. |
notifications/cancelled | Yes | Cancels an in-flight tool call by requestId. |
tools/list | Yes | Returns the registered tools (paginated). |
tools/call | Yes | Invokes a tool by name with an arguments object. |
resources/list | Yes | Lists available MCP resources. |
resources/read | Yes | Reads a named resource. |
Any other method name is rejected with JSON-RPC error -32601 (Method not found) before session validation, so a typo is never masked by a session-state error.
The handshake
A client connects in three steps:
initialize— the client sends its supportedprotocolVersionand capabilities. The server negotiates a version (honouring the client's version if supported, otherwise replying with its own latest), creates a session, and returns itsserverInfo(name/title"ClaudusBridge",versionread live from the plugin, e.g."1.2.0"), declared capabilities (tools.listChanged = true,resources), and aninstructionsblock that orients the agent toward the toolset workflow and the editor's bundled Agent Skills. The new session id is returned in theMcp-Session-Idresponse header.notifications/initialized— the client acknowledges; the session transitions to Initialized and is ready for tool calls.- From then on, every request must carry the
Mcp-Session-Idheader.
Session handling
Sessions are tracked server-side and keyed by a GUID. Per the Streamable HTTP spec (2025-06-18 onward):
- A post-initialize request missing the
Mcp-Session-Idheader returns400 Bad Request. - A request naming an unknown session id returns
404 Not Found— the spec-prescribed signal for a client to recover by sending a freshinitialize. - If the request carries an
Mcp-Protocol-Versionheader, it must match the version negotiated for that session, or the request is rejected.
Tool calls and progress streaming
tools/call takes name and an optional arguments object. Tools run asynchronously; their result is always marshalled back onto the game thread before the response is written.
There are two response shapes:
- Unary (default): an ordinary call replies with a single
application/jsonresponse, framed withContent-Length. This is the path used byinitialize,ping,tools/list, and most tool calls. - Progress-streaming: if the caller supplies a
progressTokeninparams._meta, the server opens atext/event-stream(SSE) response, pushesnotifications/progressevents while the tool runs, and delivers the final result as the last SSE write.
In-flight calls can be cancelled with notifications/cancelled (by requestId); the server cancels the tool and silently discards its eventual result.
tools/list_changed
When the registered tool set changes at runtime (for example, after a toolset is loaded on demand), the server schedules a notifications/tools/list_changed broadcast to all initialized sessions with an open SSE stream. The broadcast is deferred to the next tick to avoid re-entering the HTTP connection state machine mid-write.
Tool-search mode vs. eager mode
This is the most important server setting to understand. ClaudusBridge can surface its ~900+ tools to a client in two very different ways, controlled by bEnableToolSearch.
Tool-search mode (default — bEnableToolSearch = true)
tools/list returns just three meta-tools:
| Tool | Purpose |
|---|---|
list_toolsets | Returns a compact catalogue of every toolset (name + description). |
describe_toolset | Returns the full JSON schema (every tool, input/output schema) for one named toolset. |
call_tool | Dispatches an actual tool, given toolset_name, tool_name, and arguments. |
The agent discovers capabilities on demand: it lists toolsets, describes the one it needs, then calls a tool through call_tool. Toolset tools are never registered as native MCP tools, so the client's tool list stays tiny. This keeps the model's context window small and tool selection fast even with hundreds of toolsets available — the recommended mode for almost all clients.
A bare leaf toolset name resolves to its full name (e.g. AgentSkillToolset → ToolsetRegistry.AgentSkillToolset), though clients are encouraged to use the fully qualified name.
Eager mode (bEnableToolSearch = false)
Every toolset tool is registered as an individual native MCP tool at startup, so tools/list returns the full set (hundreds of entries) and the client calls each one directly by name. Use this only with clients that expect a flat, fully enumerated tool list and can handle a large one.
Settings — UClaudusBridgeSettings
Server behaviour is configured under Project Settings → Plugins → ClaudusBridge (stored per-user in EditorPerProjectUserSettings).
| Setting | Default | Description |
|---|---|---|
ServerPortNumber | 3000 | TCP port the HTTP server listens on. |
ServerUrlPath | /mcp | Base path MCP is served from. Invalid paths are replaced with the default and a warning is logged. |
bAutoStartServer | true | Start the server automatically during module startup. |
bEnableToolSearch | true | Tool-search mode (3 meta-tools) vs. eager mode (all tools registered). |
Command-line port override
To run several editor instances side by side (for example, a headless agent and a human editor in the same Multi-User session), give each one its own port on the command line:
UnrealEditor.exe MyProject.uproject -ClaudusBridgePort=3001
-ClaudusBridgePort=N overrides ServerPortNumber for that process only. Confirm which port a given instance ended up on with its /health probe:
curl http://127.0.0.1:3001/health
Connecting a client
Point any MCP client at the server's URL. A minimal initialize over curl looks like:
curl -s http://127.0.0.1:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{},
"clientInfo":{"name":"my-client","version":"1.0"}}}'
The response's Mcp-Session-Id header is the session id you echo back on every subsequent request. After sending notifications/initialized, you can tools/list (returns the three meta-tools in the default mode) and start driving the editor with call_tool.
Surviving editor restarts — the reconnect proxy
The server above lives inside the Unreal Editor process, so its HTTP endpoint disappears whenever the editor restarts — and an agent that rebuilds the plugin restarts the editor constantly. Most MCP clients connect to an HTTP URL once at session start and never re-probe it, so a restart would silently drop the bridge for the rest of the session and force a manual reconnect.
ClaudusBridge ships claudus-mcp-proxy to close that gap: a tiny standalone (no-Unreal) stdio ↔ HTTP MCP proxy. MCP clients spawn a stdio server as a child process and keep it alive across reconnects — so by registering the bridge as a stdio command that forwards to the HTTP endpoint, the connection survives every editor restart automatically.
On startup the plugin writes the project's .mcp.json so the claudusbridge entry launches the proxy over stdio (with --project-dir and --url-path, and no port — the proxy discovers it), instead of a fixed HTTP url:
{
"mcpServers": {
"claudusbridge": {
"command": ".../Resources/Proxy/Win64/claudus-mcp-proxy.exe",
"args": ["--project-dir", ".../YourProject/", "--url-path", "/mcp"]
}
}
}
The proxy forwards every JSON-RPC frame to the editor's POST /mcp. When the editor restarts, it:
- Re-discovers the port from the per-editor instance registry (matching your project, newest healthy editor first), gated on the
/healthprobe. - Re-initializes the upstream MCP session transparently (replaying
initializeandnotifications/initialized). - Re-issues the in-flight request under the fresh session id, with the same JSON-RPC id.
From the client's point of view, a call made during a restart simply takes a little longer and then returns its normal result — no dropped MCP, no manual reconnect. The HTTP server keeps running either way, so other clients (Cursor, Windsurf, a curl script) can still connect to the URL directly. A receive timeout or a connection dropped after the request was received is surfaced as a clean error and is never silently re-issued, so a non-idempotent tool can't be applied twice.
The proxy ships prebuilt under Resources/Proxy/ (rebuildable from Source/Programs/ClaudusMcpProxy/). If it is ever absent, the plugin falls back to writing the plain HTTP entry, so a client still connects — it just won't auto-reconnect across restarts.
For how those tools are organised and how to add your own, see the Toolsets Architecture guide.