Imported from NeuroLift-Technologies/nlt-world-engine (
WorldEngine/Plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/MCP/AGENTS.md). Install upstream withnpx skills add NeuroLift-Technologies/nlt-world-engine --skill MCP. Copyright stays with the author.
NATIVE MCP
Direct plugin MCP implementation for Streamable HTTP/SSE at /mcp. This subtree owns protocol metadata, sessions, dynamic tool visibility, and translation into the bridge subsystem; it does not own editor action implementations.
STRUCTURE
| Area | Responsibility |
|---|---|
DynamicTools/ (7) |
Enabled state, categories, protected tools, legacy list-changed notification |
Execute/ (25) |
Native execute pipeline: request parse, schema validation, receipts |
Gateway/ |
Native gateway mirror of the TS engine: catalog, capability store, describe, search, guidance, folding (McpNativeGatewayFolding: legacy pairs, pins, dispatchBy) |
Generated/ (24) |
ALL GENERATED capability shards (npm run registry:generate). Never hand-edit |
Protocol/ (4) |
JSON-RPC parse/build helpers and MCP tool-result envelopes |
Registry/ (5) |
Canonical-name gate, static definitions, cached schemas |
Routing/ (7) |
Consolidated parent-tool action routing helpers |
Tools/<Category>/ |
(Historical per-tool MCP_REGISTER_TOOL classes were removed) Native MCP tool definitions are now generated into the native registry from the canonical records; the registry reads canonical name/description/category/schema/dispatch metadata |
Transport/ |
Bind/listen, HTTP parsing, sessions, SSE, pending requests, shutdown |
CANONICAL SURFACE
FMcpToolRegistry::Register() accepts exactly these 23 names:
manage_tools, manage_asset, manage_blueprint, control_actor, control_editor, manage_level
build_environment, animation_physics, system_control, manage_sequence, inspect
manage_audio, manage_geometry, manage_effect, manage_gas, manage_character, manage_combat
manage_ai, manage_inventory, manage_interaction, manage_networking, manage_level_structure, manage_pcg
- The native registration is generated from the canonical tool/action records (the TypeScript
consolidated-tool-definitions.tsis the canonical facade over that metadata); the handwritten per-toolMCP_REGISTER_TOOLclasses have been removed.Registry/McpToolRegistry.cppis authoritative for the runtime registry; only canonical names survive, and duplicate names are ignored. - Do not infer the exposed native surface from the number of
McpTool_*.cppfiles — the per-tool C++ files no longer exist.Registry/McpToolRegistry.cppis authoritative. - Adding a canonical registrar entry alone cannot expose a new parent tool. Update the canonical gate deliberately, keep TS/native parity, and justify context growth.
tools/listfilters accepted registry entries by dynamic enabled state;tools/callenforces the same state before dispatch.
TOOL DEFINITIONS
- Tool definitions are metadata only. Build schemas with
McpSchemaBuilder; do not hand-assemble repetitive schema JSON. - Pattern A returns the parent tool name from
GetDispatchAction()and lets the handler read the sub-action. - Pattern B returns an empty dispatch action; transport extracts
GetActionFieldName()from arguments and dispatches that value. - Transport mirrors
actionintosubActionfor handlers that still require the older payload field. Do not spread additional alias normalization. - Keep definition names, action enums, required fields, routing helpers, TS schemas, and handler payload expectations aligned.
manage_toolsis intercepted locally and returns a one-shot response; other tool calls queue throughUMcpAutomationBridgeSubsystemand complete over SSE.
DYNAMIC TOOLS
- Startup enables all accepted tools when
bLoadAllToolsOnStartis true; otherwise it enables thecorecategory. manage_toolsandinspectare protected tools. Thecorecategory cannot be disabled.DynamicToolsowns the internal tool visibility state (enabled tools/categories) consumed byunreal.execute. The publictools/listis permanently a single staticunrealtool, so visibility never changes its shape:OnToolsListChanged()returns early andnotifications/tools/list_changedis suppressed. Preserve locking around tool/category state and cached registry schemas. ThebEnableNativeGatewaysetting and the legacy 23-tool direct listing were removed in the Task 30 cutover.
TRANSPORT LIFECYCLE
POST /mcphandles JSON-RPC;GET /mcpopens the persistent notification SSE stream;DELETE /mcpterminates a session and its streams.initializemust carry an id and returnsMcp-Session-Id. All later requests and notification streams require a valid session header.- Client notifications receive HTTP 202 after validation.
tools/callowns its socket until the streamed result completes. - Return JSON-RPC errors through
McpJsonRpcand tool outcomes through MCPcontent[]plusisError; never leak raw handler JSON as the top-level response. - Do not block socket threads on Unreal work. Shutdown intentionally pumps game-thread tasks while draining active connections and async writes.
SECURITY
- Empty/
localhostlisten hosts normalize to loopback. A disallowed non-loopback host falls back to127.0.0.1. - Fail-closed LAN coupling: the native transport refuses to bind non-loopback unless
bRequireCapabilityTokenis also enabled (SECURITY: refusing to bind native MCP to non-loopbackinTransport/McpNativeTransportLifecycle.cpp). A LAN-exposed surface can never start without auth. - When capability auth is enabled, require
X-MCP-Capability-Tokenbefore method dispatch. - Constant-time token checks:
McpConstantTimeTokenEquals(Private/Foundation/McpSecureTokenCompare.h) compares the token with no data-dependent early exit, so timing never leaks how much of a token matched. - Session-scoped bounded cancellation:
notifications/cancelledcorrelates only to the caller's in-flight request, keyed by the client JSON-RPC id and the owning session id, so one session cannot cancel another. The cancel-marker maps (CancelledInternalRequestIds+CancelledMarkerOrder) are capped byMaxCancelledMarkerswith oldest-first eviction, and a late response for a cancelled request is suppressed (the SSE socket closes without a result). SeeTransport/McpNativeTransportCancellation.cppand the C4 contract test. - Browser Origin/CORS access is allowed only under capability-token protection; preserve origin rejection and preflight behavior.
- Keep request-size limits, session expiry, method/path checks, write serialization, and socket ownership accounting intact.
PROTOCOL VERSION NEGOTIATION (intentional legacy asymmetry)
The native transport supports exactly the three modern MCP versions:
2025-11-25 (latest), 2025-06-18, and 2025-03-26 (see McpSupportedProtocolVersions in Transport/McpNativeTransportPrivate.h). At initialize it echoes the highest mutually supported version, or the latest for an unknown well-formed request; McpDefaultProtocolVersion() (2025-03-26) backs post-initialize requests that omit the MCP-Protocol-Version header.
- The native surface deliberately does NOT implement the later
2026-07-28release-candidate version. That RC is fictional for this codebase and is explicitly excluded fromMcpSupportedProtocolVersions; the contract test asserts it never appears as a listed/implemented version. - Asymmetry with the TS SDK: the TypeScript stdio server negotiates through the MCP SDK's
SUPPORTED_PROTOCOL_VERSIONS, which also accepts two older legacy versions (2024-11-05and2024-10-07). The native/mcptransport is intentionally stricter (modern versions only), so a client pinned to a legacy version will negotiate with the TS surface but not the native surface.
GATEWAY DISCOVERY
The native surface permanently exposes the single unreal tool and mirrors the TypeScript gateway's progressive discovery. describe drills down in three levels and never dumps a full inputSchema:
describe { tool }-> tool summary + paginated/filterable action list.describe { tool, action }-> paginated/filterable parameter catalog (the tool-union, not action-specific).describe { tool, action, param }-> exactly one parameter's full schema.
perActionSchemas is always false: parameters are the union catalog across all actions of the parent tool, and a parameter is passed only when relevant to the selected action. Invalid tool/action/param calls return closest-match suggestions and an executable nextCall payload (guided errors). Native describe is served from the generated parent registry (McpGeneratedParentRegistry*), the same capability records the TS gateway reads.
BARE DESCRIBE (intentional shape asymmetry)
A bare describe {} returns a DIFFERENT drill-down shape per transport, sharing only the scope: "catalog" label:
- Native (
McpNativeGatewayDescribeOverview.cpp): enumerates the 23 canonical parent tools (tool,actionCount,nextCall{operation,tool}), because the native surface has no domain layer to drill through. - TypeScript (
gateway-describe-browse.ts): enumerates capability discovery domains (domain,capabilityCount,familyCount,nextCall{operation,domain}), then families, then capabilities. A client trained on one surface'snextCallchain will not find the same levels on the other. Do not "fix" one side to match the other without a deliberate cross-transport decision; the drill-down depth is a per-transport property.
VALIDATION
npm run test:native-parity
npm run test:params
- Parity verifies canonical TS/native parent tools; the strict parameter audit catches schema and action mismatches.
- Folded families mirror the TS door exactly:
McpNativeGatewayValidation.cppappliesMcpApplyFoldedPinsbefore defaults and schema validation andMcpResolveDispatchActionafter them;FindByParentActionfalls back to any legacy pair, so every former name still resolves. A consent grant may name the capability by its canonical id, an alias, or a foldedtool.actionpair (FMcpCapabilityDemand::ConsentNames).