Skip to main content
Version: 1.2.0

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):

  1. Validates the configured URL path (an invalid path logs an error and the server does not start).
  2. Acquires an IHttpRouter for the configured port from Unreal's FHttpServerModule.
  3. Binds the four routes described below.
  4. Calls StartAllListeners() and installs a ticker for deferred work (such as tools/list_changed broadcasts).

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.

VerbPathPurpose
POST/mcpPrimary JSON-RPC channel. Carries initialize, tools/list, tools/call, resources/*, ping, and notifications.
GET/mcpOpens a Server-Sent Events (SSE) stream for server-to-client messages.
DELETE/mcpTears down a session.
GET/healthLightweight 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.

MethodSession requiredDescription
pingNoLiveness check; replies with an empty result.
initializeNo (creates one)Negotiates the protocol version and opens a new session.
notifications/initializedYesClient confirms it finished initializing; the session becomes Initialized.
notifications/cancelledYesCancels an in-flight tool call by requestId.
tools/listYesReturns the registered tools (paginated).
tools/callYesInvokes a tool by name with an arguments object.
resources/listYesLists available MCP resources.
resources/readYesReads 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:

  1. initialize — the client sends its supported protocolVersion and capabilities. The server negotiates a version (honouring the client's version if supported, otherwise replying with its own latest), creates a session, and returns its serverInfo (name/title "ClaudusBridge", version read live from the plugin, e.g. "1.2.0"), declared capabilities (tools.listChanged = true, resources), and an instructions block that orients the agent toward the toolset workflow and the editor's bundled Agent Skills. The new session id is returned in the Mcp-Session-Id response header.
  2. notifications/initialized — the client acknowledges; the session transitions to Initialized and is ready for tool calls.
  3. From then on, every request must carry the Mcp-Session-Id header.

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-Id header returns 400 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 fresh initialize.
  • If the request carries an Mcp-Protocol-Version header, 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/json response, framed with Content-Length. This is the path used by initialize, ping, tools/list, and most tool calls.
  • Progress-streaming: if the caller supplies a progressToken in params._meta, the server opens a text/event-stream (SSE) response, pushes notifications/progress events 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:

ToolPurpose
list_toolsetsReturns a compact catalogue of every toolset (name + description).
describe_toolsetReturns the full JSON schema (every tool, input/output schema) for one named toolset.
call_toolDispatches 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. AgentSkillToolsetToolsetRegistry.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).

SettingDefaultDescription
ServerPortNumber3000TCP port the HTTP server listens on.
ServerUrlPath/mcpBase path MCP is served from. Invalid paths are replaced with the default and a warning is logged.
bAutoStartServertrueStart the server automatically during module startup.
bEnableToolSearchtrueTool-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:

  1. Re-discovers the port from the per-editor instance registry (matching your project, newest healthy editor first), gated on the /health probe.
  2. Re-initializes the upstream MCP session transparently (replaying initialize and notifications/initialized).
  3. 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.