Repository navigation
UEVR MCP Discovery Process
letmein edited this page Jun 18, 2026
·
1 revision
Documented from live session on Outer Worlds 2 (Indiana-Win64-Shipping), June 2026. Applies to any UE4/UE5 game running under UEVR with uevr_mcp.dll loaded.
UEVR MCP (uevr_mcp.dll) is a UEVR plugin that runs an embedded HTTP server inside
the game process. It exposes a REST API for:
- Executing Lua code in the game thread
- Exploring live UObjects by address, class, field, and method
- Calling Blueprint functions with arguments
- Reading properties, arrays, and singletons
The plugin source lives at:
C:\Users\xaero\.gemini\antigravity-ide\scratch\uevr-mcp-1.1.0\
| Component | Port | Purpose |
|---|---|---|
uevr_mcp.dll |
8899 | The HTTP server inside the game process — this is what you talk to |
UevrMcpServer (dotnet) |
54320 | An MCP-over-stdio bridge for AI tool calls — NOT a general REST API |
Critical: Always use port 8899 directly for REST calls. Port 54320 is the dotnet stdio bridge and cannot be called with raw HTTP.
- UEVR injected into the game
-
uevr_mcp.dllpresent in the game's UEVR plugins folder:%APPDATA%\UnrealVRMod\<GameName>\plugins\uevr_mcp.dll - Game loaded to a playable state (not at the main menu)
- Verify server is running:
Invoke-RestMethod -Uri "http://localhost:8899/api/status"
POST http://localhost:8899/api/lua/exec
Content-Type: application/json
Body: { "code": "<lua code here>" }
$lua = @'
local pawn = uevr.api.get_local_pawn(0)
return pawn:get_full_name()
'@
$body = @{ code = $lua } | ConvertTo-Json -Compress
Invoke-RestMethod -Uri "http://localhost:8899/api/lua/exec" -Method POST -Body $body -ContentType "application/json"{ "result": "...", "execTime_ms": 1.23, "output": {}, "success": true }On error: { "error": "..." }
- The sandbox is separate from the game's own Lua mod state
-
uevr.apiis a table (not a userdata), accessed with dot notation:uevr.api.get_local_pawn(0)notuevr.api:get_local_pawn(0) -
get_fname()returns a string directly (not an object with:to_string()) -
pairs()does NOT work on UObject userdata — use REST explorer routes instead -
call_function(name)works for zero-parameter UE functions only
-- Get local player pawn
local pawn = uevr.api.get_local_pawn(0)
-- Find a UObject by full name (slow — scans all objects)
local obj = uevr.api.find_uobject("BlueprintGeneratedClass /Game/Path/To/Class.Class_C")
-- Returns nil if not found. Takes ~400ms for a full scan.
-- Get full name of any object
obj:get_full_name() -- "ClassName /Full/Path.ObjectName"
obj:get_fname() -- "ObjectName" (string directly, NOT userdata)
-- Get address as number
obj:get_address() -- returns integer
-- Get class and walk hierarchy
local cls = obj:get_class()
while cls ~= nil do
print(cls:get_fname())
cls = cls:get_super()
end
-- Get objects of a class (UObjectHook)
local pawnClass = pawn:get_class()
local instances = uevr.uobject_hook.get_objects_by_class(pawnClass, false)
-- NOTE: class must be a UClass userdata from get_class(), not from find_uobject()
-- Read typed properties
local boolVal = obj:get_bool_property("SomeFlag") -- returns bool
-- Object properties require the explorer REST route, not Lua get_property in this SDK version
-- Call zero-parameter functions
obj:call_function("SomeFunctionName") -- no return value capture in Lua
-- List available Lua methods on any UObject
local mt = getmetatable(obj)
for k, v in pairs(mt) do print(k, type(v)) end
-- Useful methods found: get_full_name, get_address, get_fname, get_class,
-- get_bool_property, get_property_data, call_function, class_castAll routes are GET unless noted. Base: http://localhost:8899
GET /api/explorer/objects_by_class?className=<fullClassName>
-
className= full UObject name e.g.BlueprintGeneratedClass /Game/.../Weapon_Base.Weapon_Base_C - Response:
{ "className": "...", "count": 50, "results": [ { "address": "0x...", "className": "...", "fullName": "..." } ] }
GET /api/explorer/type?typeName=<fullClassName>
- Returns
{ "name": "...", "super": "...", "fields": [...], "methods": [...], "address": "0x..." } -
fields= array of{ "name": "FieldName", "type": "NameProperty" } -
methods= array of{ "name": "FuncName", "owner": "ClassName", "params": [...], "returnType": "..." } - The
addresshere is the CDO (Class Default Object), NOT a live instance
GET /api/explorer/summary?address=0x<address>
- Returns
{ "fullName": "...", "className": "...", "fields": { "FieldName": "TypeInfo -> Value" } } - Field values shown as strings, e.g.
"ObjectProperty -> SkeletalMeshComponent @ 0x..." - Very useful for navigating object graphs
GET /api/explorer/field?address=0x<address>&fieldName=<name>
- Returns
{ "field": "...", "type": "ObjectProperty", "value": { "address": "0x...", ... } }
GET /api/explorer/method?address=0x<address>&methodName=<name>
- Only works if the function has no input parameters
- Returns
{ "result": <value> }— vectors returned as{ "x": ..., "y": ..., "z": ... } - Error if function has params:
"Method 'X' has parameters — use invoke_function instead"
POST /api/explorer/method
Body: { "address": "0x...", "methodName": "FuncName", "args": { "ParamName": value } }
-
argscan be an object (keyed by param name) or an array (positional) - Example calling GetSocketLocation:
{
"address": "0x1E3E1F4BBC0",
"methodName": "GetSocketLocation",
"args": { "InSocketName": "MuzzleFlashSocket" }
}- Returns
{ "result": { "x": ..., "y": ..., "z": ... }, "outParams": {} }
GET /api/explorer/array?address=0x<address>&fieldName=<name>&limit=20
- Returns elements with their addresses and class names
- For object arrays, elements have
{ "address": "0x...", "className": "...", "index": 0 }
GET /api/explorer/search?query=<substring>&limit=10
- Scans all 580k+ objects — slow but useful
- Returns
{ "count": N, "results": [...], "total_scanned": 583306 }
GET /api/player
- Returns
{ "pawn": { "address": "0x...", "class": "..." }, "controller": { ... } } - Use this as the entry point instead of Lua for getting pawn address
Invoke-RestMethod -Uri "http://localhost:8899/api/status"$player = Invoke-RestMethod -Uri "http://localhost:8899/api/player"
$pawnAddr = $player.pawn.address # e.g. "0x1E76ECA3340"
$pawnClass = $player.pawn.class # e.g. "IndianaPlayerCharacter_BP_C"$summary = Invoke-RestMethod -Uri "http://localhost:8899/api/explorer/summary?address=$pawnAddr"
# Filter for what you need:
$summary.fields.PSObject.Properties | Where-Object { $_.Name -match "Weapon|Equip|Inventory" } |
ForEach-Object { "$($_.Name): $($_.Value)" }- Each
ObjectProperty -> SomeClass @ 0xADDRin the summary gives you the next address to inspect - Follow the chain:
pawn → PlayerEquipmentComponent → EquippedWeapon → GetFPVMeshComponent()
# Get all methods on a class (use URL-encoded full name)
$typeName = [System.Uri]::EscapeDataString("BlueprintGeneratedClass /Game/Blueprints/WEAP/Weapon_Base.Weapon_Base_C")
$result = Invoke-RestMethod -Uri "http://localhost:8899/api/explorer/type?typeName=$typeName"
$result.methods | ForEach-Object { $_.name }$className = [System.Uri]::EscapeDataString("BlueprintGeneratedClass /Game/Blueprints/WEAP/Weapon_Base.Weapon_Base_C")
$result = Invoke-RestMethod -Uri "http://localhost:8899/api/explorer/objects_by_class?className=$className"
Write-Host "$($result.count) instances"
$result.results | Select-Object -First 5 | ForEach-Object { $_.fullName }# Zero-param getter:
$r = Invoke-RestMethod -Uri "http://localhost:8899/api/explorer/method?address=0xADDR&methodName=GetShotOrigin"
$r.result # { x, y, z }
# With params:
$body = @{ address = "0xADDR"; methodName = "GetSocketLocation"; args = @{ InSocketName = "MuzzleFlashSocket" } } | ConvertTo-Json -Compress
$r = Invoke-RestMethod -Uri "http://localhost:8899/api/explorer/method" -Method POST -Body $body -ContentType "application/json"# Get the sockets array from a Skeleton asset
$r = Invoke-RestMethod -Uri "http://localhost:8899/api/explorer/array?address=0xSKELADDR&fieldName=Sockets&limit=20"
foreach ($socket in $r.elements) {
$s = Invoke-RestMethod -Uri "http://localhost:8899/api/explorer/summary?address=$($socket.address)"
$name = $s.fields.PSObject.Properties | Where-Object { $_.Name -eq "SocketName" }
Write-Host "$($socket.address): $($name.Value)"
}/api/player → pawn.address (IndianaPlayerCharacter_BP_C)
→ summary → PlayerEquipmentComponent (ObjectProperty → PlayerEquipmentComponent @ 0x...)
→ summary → EquippedWeapon (ObjectProperty → Weapon_PI_Standard_Unique_C @ 0x...)
→ method GET GetFPVMeshComponent → SkeletalMeshComponent @ 0x...
→ summary → SkeletalMesh (ObjectProperty → SkeletalMesh @ 0x...)
→ summary → Skeleton (ObjectProperty → Skeleton @ 0x...)
→ array Sockets → [ SkeletalMeshSocket objects ]
→ summary each socket → SocketName, BoneName
| Object | Full UObject Name |
|---|---|
| Weapon base class | BlueprintGeneratedClass /Game/Blueprints/WEAP/Weapon_Base.Weapon_Base_C |
| FireMode base | BlueprintGeneratedClass /Game/Blueprints/WEAP/WeaponModes/FireMode_Base.FireMode_Base_C |
| Player character | IndianaPlayerCharacter_BP_C |
| Function | Owner | Params | Returns |
|---|---|---|---|
GetShotOrigin |
RangedMode | none | StructProperty (FVector) |
GetAimingDirection |
RangedMode | bIsForShotSpawnTransform (bool) | StructProperty |
GetMuzzleLocation |
RangedMode | multiple | StructProperty |
GetShootSocketName |
RangedMode | mesh component | NameProperty |
GetLastShotEndLocation |
FireMode | none | StructProperty |
| Socket Name | Bone | Purpose |
|---|---|---|
MuzzleFlashSocket |
barrel_JNT |
Primary muzzle — use this for trace fix |
MuzzleFLashSocketSilencer |
barrel_JNT |
Silencer variant (note: game typo in name) |
Barrel_Socket |
h_weapon |
Weapon barrel attach point |
VFX_CasingEject |
h_weapon |
Shell eject VFX |
PickupSocket |
h_weapon |
Pickup interaction |
Mod_Socket |
h_weapon |
Weapon mod attachment |
Magazine_Socket |
Magazine_JNT |
Magazine mesh |
WeaponAttachSocket = "WeaponPoint" — socket on the player arm mesh where the weapon attaches
| Problem | Cause | Fix |
|---|---|---|
find_uobject returns nil |
Wrong name format | Must include full path: "BlueprintGeneratedClass /Game/..."
|
get_objects_by_class errors |
Passing UObject* not UClass* | Use pawn:get_class() result, not find_uobject() result |
get_fname():to_string() errors |
get_fname() already returns a string |
Just use obj:get_fname() directly |
/api/lua/exec errors with escaping |
PowerShell quote escaping | Use PowerShell here-strings @' ... '@
|
uevr.api:method() nil error |
Wrong colon vs dot | Use uevr.api.method() (dot), not colon |
call_function with params crashes |
call_function only handles zero-param | Use POST /api/explorer/method with args
|
| 400ms find_uobject calls | Full object table scan | Cache results; call once not per-frame |
| Sockets array empty on SkeletalMesh | Sockets live on Skeleton asset | Navigate to SkeletalMesh → Skeleton → Sockets
|
pairs() on UObject fails |
sol container restriction | Use REST explorer routes to enumerate |
GET /api/status
GET /api/game_info
GET /api/player
GET /api/camera
GET /api/vr/status
GET /api/vr/poses
GET /api/vr/settings
POST /api/vr/settings
POST /api/vr/recenter
POST /api/vr/config/save
POST /api/vr/config/reload
GET /api/lua/state
POST /api/lua/exec ← primary tool
POST /api/lua/reset
GET /api/console/cvars
GET /api/explorer/search
GET /api/explorer/classes
GET /api/explorer/type
GET /api/explorer/object
GET /api/explorer/summary
GET /api/explorer/field
GET /api/explorer/method
GET /api/explorer/objects_by_class
POST /api/explorer/field
POST /api/explorer/method ← for functions with params
POST /api/explorer/chain
GET /api/explorer/singletons
GET /api/explorer/singleton
GET /api/explorer/array
GET /api/explorer/memory
GET /api/explorer/typed
POST /api/explorer/batch
GET /api/discovery/subclasses
GET /api/discovery/names
GET /api/discovery/delegates
GET /api/discovery/vtable
POST /api/discovery/pattern_scan
GET /api/discovery/all_children
POST /api/hook/add
GET /api/hook/list
GET /api/hook/log
POST /api/hook/clear
GET /api/animation/state
POST /api/animation/play_montage
POST /api/animation/stop_montage
POST /api/animation/set_variable
GET /api/animation/montages