Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"mcpServers": {
"unreal": {
"command": "python",
"args": ["-m", "ue_mcp.mcp_server"],
"env": { "UE_MCP_PROFILE": "full" }
},
"unreal-epic": {
"type": "http",
"url": "http://127.0.0.1:8000/mcp"
}
}
}
10 changes: 10 additions & 0 deletions UnrealEngine_Bridge.uproject
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@
{
"Name": "MLAdapter",
"Enabled": true
},
{
"Name": "ModelContextProtocol",
"Enabled": true,
"Optional": true
},
{
"Name": "AllToolsets",
"Enabled": true,
"Optional": true
}
],
"TargetPlatforms": [
Expand Down
180 changes: 180 additions & 0 deletions docs/EPIC_MCP_MATRIX.md

Large diffs are not rendered by default.

57,797 changes: 57,797 additions & 0 deletions docs/epic_mcp/probe_raw_5.8.0_alltoolsets.json

Large diffs are not rendered by default.

123 changes: 123 additions & 0 deletions docs/epic_mcp/probe_raw_5.8.0_default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"url": "http://127.0.0.1:8000/mcp",
"steps": {
"tools_list": "ok",
"list_toolsets": "ok"
},
"server_info": {
"name": "",
"title": "",
"version": "",
"websiteUrl": null,
"icons": null
},
"protocol_version": "2025-11-25",
"capabilities": {
"experimental": null,
"logging": null,
"prompts": null,
"resources": {
"subscribe": null,
"listChanged": null
},
"tools": {
"listChanged": true
},
"completions": null,
"tasks": null
},
"tools_list": [
{
"name": "list_toolsets",
"title": null,
"description": "List all available toolsets with names and descriptions.",
"inputSchema": {
"type": "object",
"properties": {}
},
"outputSchema": null,
"icons": null,
"annotations": null,
"meta": null,
"execution": null
},
{
"name": "describe_toolset",
"title": null,
"description": "Get detailed information about a toolset including all tool names, descriptions, and input schemas.",
"inputSchema": {
"type": "object",
"properties": {
"toolset_name": {
"type": "string",
"description": "Name of the toolset to describe. Use list_toolsets to see available names."
}
},
"required": [
"toolset_name"
]
},
"outputSchema": null,
"icons": null,
"annotations": null,
"meta": null,
"execution": null
},
{
"name": "call_tool",
"title": null,
"description": "Call a tool by name. Provide toolset_name to call a toolset tool, or omit it to call a top-level MCP tool. Use list_toolsets and describe_toolset to discover available tools and their input schemas.",
"inputSchema": {
"type": "object",
"properties": {
"toolset_name": {
"type": "string",
"description": "Optional. Name of the toolset containing the tool. Omit to call a top-level MCP tool. Use list_toolsets to discover toolset names."
},
"tool_name": {
"type": "string",
"description": "Name of the tool to call, without toolset prefix. Use describe_toolset to discover tool names and input schemas."
},
"arguments": {
"type": "object",
"description": "Arguments to pass to the tool. Must match the tool's input schema. Defaults to an empty object."
}
},
"required": [
"tool_name"
]
},
"outputSchema": null,
"icons": null,
"annotations": null,
"meta": null,
"execution": null
}
],
"list_toolsets_raw": "- ToolsetRegistry.AgentSkillToolset: Provides tools for listing, reading, and creating/updating skills.\n",
"toolset_names_source": "fallback-docs",
"toolset_names": [
"ActorTools",
"SceneTools",
"MaterialInstanceTools",
"ObjectTools"
],
"describe_toolset": {
"ActorTools": {
"arg_key": "toolset_name",
"error": "Toolset 'ActorTools' not found. Available toolsets: ToolsetRegistry.AgentSkillToolset"
},
"SceneTools": {
"arg_key": "toolset_name",
"error": "Toolset 'SceneTools' not found. Available toolsets: ToolsetRegistry.AgentSkillToolset"
},
"MaterialInstanceTools": {
"arg_key": "toolset_name",
"error": "Toolset 'MaterialInstanceTools' not found. Available toolsets: ToolsetRegistry.AgentSkillToolset"
},
"ObjectTools": {
"arg_key": "toolset_name",
"error": "Toolset 'ObjectTools' not found. Available toolsets: ToolsetRegistry.AgentSkillToolset"
}
}
}
1 change: 1 addition & 0 deletions docs/epic_mcp/programmatic_exec_environment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"returnValue":{"instructions":"\nUse the function execute_tool(tool_name, json_input) to call any registered\ntool. ``tool_name`` is the tool name exactly as it appears in your tool list.\nIt returns the result directly as a dict-like object. It raises RuntimeError\non failure - no error checking is needed.\n\nThe script must define a `run()` function that returns a `Dict[str, Any]`.\nUnhandled exceptions will be returned when the tool is executed.\n\nSTOP. Before composing the script: the schemas in your tool list describe\neach tool's INPUTS only, not what it returns. To parse return values in the\nscript, look up the output schema (if available) for each tool the script\nwill call, unless you have already received that tool's return value in\nthis conversation.\n\nIMPORTANT: At the top of the script, define short helper functions that wrap\neach execute_tool call you plan to use. This keeps the rest of the script\nreadable and avoids repeating the verbose execute_tool invocation. For example:\n\n import json\n\n def get_selected_actors():\n return execute_tool(\n \"EditorToolset.EditorAppToolset.GetSelectedActors\",\n \"{}\")[\"returnValue\"]\n\n def set_actor_transform(actor, xform, worldspace=True):\n return execute_tool(\n \"editor_toolset.toolsets.actor.ActorTools.set_actor_transform\",\n json.dumps({\"actor\": actor, \"xform\": xform,\n \"worldspace\": worldspace}))\n\n def run():\n for actor in get_selected_actors():\n set_actor_transform(actor, new_xform)\n return {\"moved\": len(get_selected_actors())}\n\nThe scripting environment allows importing a limited set of safe modules:\nfrozenset({'math', 're', 'json', 'datetime', 'time', 'copy'}). The function execute_tool() is available to call\nregistered tools directly.\n","supported_modules":[{"description":"Standard library JSON encoding and decoding.","name":"json"},{"description":"Standard library mathematical functions.","name":"math"},{"description":"Standard library date and time types.","name":"datetime"},{"description":"Standard library shallow and deep copy operations.","name":"copy"},{"description":"Standard library regular expression operations.","name":"re"},{"description":"Standard library for time.","name":"time"}],"language":"python"}}
77 changes: 77 additions & 0 deletions scripts/probe_epic_mcp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Full-surface probe of Epic's official Unreal MCP server (UE 5.8+).

Usage: python scripts/probe_epic_mcp.py <out.json> [server_url]

Requires a running editor with the ModelContextProtocol plugin serving
(console: `ModelContextProtocol.StartServer`); enable the AllToolsets plugin
to expose the full shipped surface. list_toolsets returns lines of
`- <ToolsetName>: <description>`; describe_toolset takes {toolset_name} and
returns that toolset's complete JSON schema. The dump is the authoritative
input for docs/EPIC_MCP_MATRIX.md — rerun per engine version and diff.
"""
import asyncio
import json
import sys

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

URL = sys.argv[2] if len(sys.argv) > 2 else "http://127.0.0.1:8000/mcp"


def content_text(res) -> str:
return "\n".join(c.text for c in (getattr(res, "content", []) or []) if getattr(c, "text", None))


async def main(out_path: str) -> None:
out: dict = {"url": URL}
async with streamablehttp_client(URL) as (read, write, _):
async with ClientSession(read, write) as session:
init = await session.initialize()
out["protocol_version"] = init.protocolVersion
tools = await session.list_tools()
out["tools_list"] = [t.model_dump() for t in tools.tools]

ts = await session.call_tool("list_toolsets", {})
raw = content_text(ts)
out["list_toolsets_raw"] = raw

names = []
for line in raw.splitlines():
line = line.strip()
if line.startswith("- ") and ":" in line:
names.append(line[2:].split(":", 1)[0].strip())
out["toolset_names"] = names
print(f"{len(names)} toolsets discovered")

out["toolsets"] = {}
tool_total = 0
for name in names:
try:
d = await session.call_tool("describe_toolset", {"toolset_name": name})
text = content_text(d)
if getattr(d, "isError", False):
out["toolsets"][name] = {"error": text}
continue
try:
schema = json.loads(text)
out["toolsets"][name] = schema
n = len(schema.get("tools", []))
tool_total += n
print(f" {name}: {n} tools")
except (json.JSONDecodeError, ValueError):
out["toolsets"][name] = {"raw": text}
print(f" {name}: (non-JSON payload, {len(text)} chars)")
except Exception as e:
out["toolsets"][name] = {"exception": str(e)}
print(f" {name}: EXCEPTION {e}")

out["tool_total"] = tool_total
print("TOTAL concrete tools:", tool_total)

with open(out_path, "w", encoding="utf-8") as fh:
json.dump(out, fh, indent=2, default=str)


if __name__ == "__main__":
asyncio.run(main(sys.argv[1] if len(sys.argv) > 1 else "probe_out_full.json"))
Loading