ワンクリックで
ue5-network-viz
3D network topology visualization and interactive digital twin in Unreal Engine 5.8 via the built-in UE5 MCP server.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
3D network topology visualization and interactive digital twin in Unreal Engine 5.8 via the built-in UE5 MCP server.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Federate your NetClaw with other NetClaw operators over the BGP mesh — exchange capability inventories and ask your claw what a peer can do. (US1; remote invocation and chat land in later phases.)
Catalyst Center device inventory and site management - list/filter devices by hostname, IP, platform, family, role, reachability; view site hierarchy; get interface details per device; device reachability monitoring; cross-reference with pyATS. Use when listing network devices, checking device reachability, auditing software versions, viewing site hierarchy, or finding a device by serial number.
Catalyst Center troubleshooting workflows - device unreachable investigation, client connectivity issues, interface down analysis, site-wide outage triage, wireless roaming problems, integration with pyATS for CLI-level diagnostics. Use when a device is unreachable, a user reports connectivity problems, an interface is down, a site has an outage, or wireless clients have roaming issues.
Human-in-the-loop escalation via HumanRail — route low-confidence agent decisions, pre-destructive operation approvals, and ambiguous incident tickets to real human engineers. Human answers are verified and returned as structured output. Workers are paid via Lightning Network. Use when the agent is uncertain, when a destructive change needs explicit human sign-off beyond a ServiceNow CR, or when an ambiguous ticket requires human triage before automated handling.
Generate and manage Visio network diagrams on SharePoint via Microsoft Graph API - create topology diagrams from CDP/LLDP discovery, update existing diagrams, export to PDF. Use when creating Visio topology diagrams, uploading network diagrams to SharePoint, or generating physical/logical topology views from discovery data
Reconcile NetBox source of truth against live network state - detect IP drift, missing interfaces, undocumented links, cable mismatches, VLAN mismatches, and ticket discrepancies in ServiceNow. Use when validating NetBox accuracy, checking for config drift, auditing network documentation, or reconciling source of truth after changes
| name | ue5-network-viz |
| description | 3D network topology visualization and interactive digital twin in Unreal Engine 5.8 via the built-in UE5 MCP server. |
| license | Apache-2.0 |
| compatibility | Network: local HTTP/JSON-RPC to the UE5 MCP server only (UE5_MCP_URL, default localhost); no external endpoints. |
| metadata | {"openclaw":{"requires":{"bins":["python3"],"env":["UE5_MCP_URL"],"network":true}}} |
Version: 0.2.0 Feature: 044-ue5-mcp-network-viz (foundation), 045-ue5-digital-twin (interactive digital twin) Status: Active
The UE5 Network Visualization skill enables 3D network topology visualization in Unreal Engine 5.8 using the built-in MCP server. Network engineers can request topology renderings via natural language, and NetClaw transforms network data into an immersive 3D scene with color-coded devices, animated links, and real-time health updates.
045-ue5-digital-twin extends this from a static snapshot into an interactive digital twin / network looking-glass: interface-level actors, universal labeling, a color legend, live traffic/health/trap-driven state, real ping/traceroute animation, on-demand config panels, PagerDuty incident correlation, historical playback, hierarchical zoom, and a floating metrics HUD — all conversational, no in-engine UI. See the "Digital Twin Capabilities (045)" section below for full command reference.
Unreal Engine 5.8+ with MCP plugin enabled
ModelContextProtocol.StartServerNetClaw Environment
Network Data Source (at least one)
"Render my network in UE5"
"Visualize the topology in Unreal Engine"
"Show me the network in 3D"
"Create a UE5 scene from my pyATS topology"
"Refresh the network visualization"
"Update the UE5 scene with current topology"
"Re-render only the changed devices"
"Mark router-1 as critical"
"Show core-sw-01 as warning state"
"Set the link between router-1 and switch-1 as down"
"Highlight unhealthy devices"
"Focus on router-1"
"Show me the core routers"
"Fly through the network"
"Give me an overview of the entire topology"
"Switch to top-down view"
"What is device router-1?"
"List all rendered devices"
"Show me device details for core-sw-01"
"Clear the UE5 scene"
"Reset the visualization"
Everything below extends a topology already built by the commands above into an interactive digital twin — all conversational, no in-engine UI. Every capability refuses (and tells you why) rather than silently attempting or ignoring a request that names a device, interface, or link not present in the currently built scene.
Every render/refresh automatically gets: a separate small actor for each up/up interface (attached near its parent device, with links connecting to interfaces rather than device centers when interface data is available), a compact "N down: ..." summary per device instead of one actor per down interface, a label on every device/interface/link, and a legend actor showing the live device-type color mapping. Nothing extra to ask for — "render my topology" and "refresh the topology with colours" both include all of this.
"Render my topology"
"Refresh the topology with colours"
"Show traffic on r1's links"
"Refresh the traffic visualization"
Colors links/interfaces on a green→yellow→red gradient from utilization data you or NetClaw has already retrieved (via gnmi-mcp/pyATS). Links/interfaces with no utilization data keep their normal appearance — never an error.
"Refresh device health"
"Start live mode"
"Is live mode running?"
"Stop live mode"
On-demand health refresh recolors device/link actors from real gnmi-mcp/pyATS status. Live mode runs continuous background polling until explicitly stopped; one unreachable device never blocks polling the rest.
A real linkDown trap latches a sticky alert (hot pink) on the affected interface that persists across unrelated refreshes — cleared only by a matching linkUp trap or a confirmed healthy poll, never by time alone. Traps for a device/interface not in the current topology are ignored, not errored.
"Ping r1 from sw1"
"Traceroute from pc1 to r1"
Animates a real ping/traceroute result (green = success, red = failure/ timeout) along the path between actors. A device not in the current topology is reported, not attempted.
"Show me r1's running config"
"Show metrics for sw1"
Renders a readable text panel near the device's actor with real content (config or CPU/memory/uptime). Asking again replaces the panel with fresh data — it never stacks duplicates or shows a stale cached value.
"Is there an open incident for r1?"
"Check incidents for the r1-sw1 link"
Matches a device (or either endpoint of a link) against open PagerDuty incidents by hostname substring in the title/description/service name. A match applies a distinct alarm color (hot pink); no match is clearly reported, never a silent no-op.
"Replay the last 30 minutes"
"Replay from 2pm to 2:30pm at double speed"
Replays recorded health/traffic/trap changes against the live scene in original order, compressed by default (2x) with an adjustable speed. An empty window is reported explicitly.
"Zoom into rack-1"
"Zoom out to the full site"
Groups devices by real NetBox/Infrahub rack/site placement first, with a manual grouping fallback for devices neither source has placement for. Zooming only toggles actor visibility and reframes the camera — it never rebuilds the topology, so nothing is lost or duplicated when you zoom back out.
| Device Type | Shape | Color | Hex |
|---|---|---|---|
| Router | Cube | Blue | #3366CC |
| Switch | Cube | Green | #33B34D |
| Firewall | Cube | Red | #CC3333 |
| Access Point | Sphere | Yellow | #E6CC33 |
| Load Balancer | Cube | Purple | #9933CC |
| Endpoint | Sphere | Orange | #E68019 |
| Unknown | Cube | White | #FFFFFF |
| Status | Color | Effect |
|---|---|---|
| Healthy | Device type color | Normal |
| Warning | Orange | Pulsing glow |
| Critical | Bright Red | Bright emissive |
| Unreachable | Dark Red | Flashing |
| Unknown | Gray | Dimmed |
| Status | Color | Visual |
|---|---|---|
| Healthy | Green | Solid |
| Degraded | Yellow | Animated |
| Down | Red | Broken/dashed |
| Unknown | Gray | Faded |
{
"devices": [
{
"hostname": "core-rtr-01",
"device_type": "router",
"ip_addresses": ["10.0.0.1"],
"model": "Cisco ISR4451",
"vendor": "Cisco",
"status": "healthy",
"utilization": 45.2
}
],
"links": [
{
"source_device": "core-rtr-01",
"target_device": "dist-sw-01",
"source_interface": "GigabitEthernet0/0",
"target_interface": "GigabitEthernet1/0/1",
"status": "healthy",
"bandwidth": 1000,
"utilization": 12.5
}
],
"source": "pyATS"
}
If device_type is not specified, it's inferred from hostname:
rtr, router, cr, er → Routersw, switch, ds, as → Switchfw, firewall, asa, ftd → Firewallap, wap, wireless → Access Pointlb, f5, bigip → Load Balancersrv, server, host → EndpointMcp-Session-Id header from initializationlist_toolsets, describe_toolset, call_toolexecute_tool(tool_name, arguments) - arguments must be JSON strings, not dictsname, tag, and collision_channels parameters (all required)["key"] (or .get("key") with no default) — .get("key", default) raises _StrictDict.get() does not support a default value. This ONLY applies to dicts/objects returned by calling into UE5 from a running script (e.g. get_component_by_class(), get_all_level_actors()). It does NOT apply to plain dicts you build yourself or to JSON already parsed client-side over the MCP wire — ue5_mcp_client.py and the per-actor functions in actors.py are all client-side and safe. It bites specifically inside code passed to execute_tool_script (see "Batch Build Pattern" below).staticMesh (lowercase 's') and pass the loaded asset object, not just path string/Game/ path (not /Temp/) to avoid crashes — see save_level_as(), which now does this programmatically instead of requiring a manual File > Save Current Level As.TextRenderActor also spawns an editor-only billboard/sprite child component (renders as a black "Tt" box in editor viewport screenshots, separate from the real text). Hide it: for comp in actor.get_components_by_class(unreal.BillboardComponent): comp.set_visibility(False).world_size (24, later tried 95) is unreadable once a topology has more than a handful of devices. Scale it to the scene's bounding-box diagonal instead — roughly diagonal / 20, floored around 100. See _compute_batch_specs() in actors.py.~/.openclaw/workspace/ue5-topo/, never /tmp — /tmp is wiped every time WSL restarts, which happens on every host crash. Losing an in-progress topology capture and generated build script to a crash cost real rework time during the 2026-07-01 incident. persist_build_artifacts() in actors.py does this automatically for every batch build.Use this for any initial/full topology build. Spawning one actor at a time (spawn_device_actor/spawn_link_actor, one MCP round trip each) costs roughly 4 round trips per actor — for a 10-device/12-link topology that's ~90 round trips at 15-20s apiece, several minutes total, and every round trip is a chance for a host crash to leave the build half-finished. This is exactly what happened repeatedly during the 2026-07-01 live incident.
Instead, generate one Python script and run the entire build inside UE5 in a single execute_tool_script call:
from workspace.skills.ue5_network_viz import render_topology_fast
async with UE5MCPClient() as client:
# One MCP round trip builds every device, link, and label at once.
# Falls back automatically to the slower per-actor path if the batch
# build raises for any reason — this can never be worse than before.
result = await render_topology_fast(client, topology)
Under the hood (actors.render_topology_batch / actors.build_batch_scene_script):
_StrictDict bug.execute_tool_script's input schema is {"script": "<python source>"} (script is a required string) — confirmed live against a running UE5 8.0 MCP server on 2026-07-02.result variable to a JSON summary as its best-effort return value, but the client-side code does not trust it — it re-queries the scene via find_netclaw_actors() afterward to confirm what actually got spawned, the same way the live agent verified state by hand ("Verification confirms the scene: 10 devices, 12 links in their folders"). Re-verify this convention next time UE5 is stable for an extended session.save_level_as(client, package_path="/Game/NetClaw/NetClawTopo") calls unreal.EditorLoadingAndSavingUtils.save_map(...) via execute_tool_script, solving the "/Temp/ level can't be saved" blocker programmatically instead of requiring the user to do it by hand.capture_scene_screenshot(client, filename, width, height) calls unreal.AutomationLibrary.take_high_res_screenshot(...) via execute_tool_script, avoiding the built-in CaptureViewport MCP tool entirely — that tool's schema requires every field, including a full nested annotations struct, with no defaults honored despite being marked optional.render_topology_incremental() (health-state updates on an already-built scene) intentionally still uses the per-actor path — updating one or two actors doesn't benefit from rebuilding the whole scene in one script.
Not every UE5 MCP build behaves the same way. Two build-specific issues surfaced during a real CML+pyATS→UE5 rebuild, both now handled:
tool_name must be the SHORT method name, not the fully-qualified <toolset>.method string. This build silently returned "Unknown tool" for every call because the skill passed the fully-qualified name in tool_name on top of the already-separate toolset_name — a "double-qualified" call this build's dispatcher didn't recognize. UE5MCPClient.call_tool() now strips the toolset prefix from tool_name automatically before sending, so every existing TOOL_* constant (kept fully-qualified for readability) keeps working unchanged. It also now detects "Unknown tool"/"Unknown toolset"/"tool not found" in the response text and reports success=False instead of silently reporting success — this exact gap is what caused a false positive ("10 devices rendered" when nothing had actually spawned).execute_tool_script's Python sandbox can forbid import unreal. On this build, only stdlib modules (math, json, re, time, etc.) are importable inside a script — meaning the entire batch-build pattern above is structurally unusable on this build, full stop. This fails immediately when the script runs, as a normal (non-exception) success=False result, not a Python exception — render_topology_fast() was fixed to check result.success in addition to catching exceptions, since only catching exceptions meant the automatic fallback to the per-actor path never actually triggered for this failure mode.Lesson: always verify a build's actual capabilities (tool-name convention, execute_tool_script import allowlist) with a cheap smoke test before trusting a full build to succeed — do not trust a script's self-reported summary or a blanket success=True over re-querying the scene for what's really there.
text/event-stream and keep the connection open as a keep-alive stream even after sending the actual JSON-RPC result. UE5MCPClient used to call httpx's plain .post() and read response.text, which makes httpx wait for the full response body — i.e. for the connection to close — before returning. On a keep-alive build that hangs until the timeout fires even though the real answer was available instantly. This is very likely responsible for several "UE5 seems frozen" symptoms hit throughout this whole investigation. Fixed by switching _send_request() and initialize_session() to httpx's streaming API (client.stream(...) + aiter_lines()), reading the SSE stream line-by-line and stopping as soon as a complete JSON-RPC object arrives, instead of waiting for the connection to close. Verified live 2026-07-03: session init and list_toolsets() dropped from routinely timing out at 30-60s to completing in under half a second.set_actor_transform does not reliably preserve omitted fields, despite its own docs claiming it does. Setting only scale on an existing actor was observed to silently reset its location to (0,0,0) on a live build — this is what caused an entire 22-actor scene to visually "stack in the middle" after an otherwise-successful build. Always pass the full transform (location + rotation + scale) together in a single call; never assume a partial update leaves the rest alone, regardless of what the tool's own documentation says./Temp/Untitled_N level and later persisted to a real /Game/ level get a NEW refPath under the new level path — old refPaths captured before the save (e.g. in a persisted JSON artifact) will not resolve. Re-resolve actors via find_actors(tag="netclaw") and match by their stable index/name suffix rather than trusting a previously-cached refPath across a save/reload._compute_batch_specs()/the centering fix in layout.py's ForceDirectedLayout.run() for the reference implementation) — do this for BOTH device positions and link midpoints/endpoints, derived from the already-centered device positions rather than recomputed from raw coordinates.execute_tool_script sandbox restriction from gotcha #2 above initially appeared to block ALL of 045's new capabilities — interface actors, labels, the legend, and live coloring were all originally implemented via execute_tool_script + native unreal.* calls. On a build where this sandbox forbids import unreal (confirmed live: "Import of 'unreal' is not permitted. Allowed modules: {json, time, datetime, re, copy, math}"), none of that could run. This has since been resolved for interfaces, labels, the legend, and all status/traffic/trap/incident/ping coloring — see items 5-8 below for the non-scripted replacement path. Config/metrics panels (panels.py) and hierarchical zoom (hierarchy.py's zoom_to/zoom_out_to_site) still use execute_tool_script and remain blocked on this build; they're unit-tested and will activate automatically on a build without this restriction.success=False. render_topology()'s per-actor path wrapped the entire function (device spawn, link spawn, AND the optional lighting step) in one try/except. When setup_default_lighting() raised (itself a separate pre-existing bug — scene.py's lighting functions used the wrong call_tool() keyword arguments), the shared except clause overwrote an already-correct success=True, devices_rendered=4, links_rendered=3 down to success=False, making a fully successful 4-device render report as a total failure. Fixed by isolating the lighting step in its own try/except that records a non-fatal error instead of failing the whole render — never let an optional, cosmetic step's failure invalidate already-completed substantive work.find_actors's returned actor references carry only refPath (the engine's auto-generated internal object name, e.g. StaticMeshActor_34), never the display Label (e.g. NC_core-rtr-01) that set_actor_label/generate_device_actor_name set. Any code that re-queries find_actors afterward and tries to classify or count actors by matching a "name"/"label" field against a generated NC_... string (as opposed to trusting a script's own self-reported spawn tally, or the calling function's own per-actor spawn_result.success count) will not find what it's looking for. render_topology()'s per-actor path is unaffected — it counts from each spawn call's own immediate success response, never from a post-hoc find_actors re-query.spawn_device_actor/spawn_link_actor set the actor's real Label to a value that never matches generate_device_actor_name()'s "NC_..." string. The spawn call's requested "name" is not reliably honored by the engine (it may fall back to an auto-generated name like StaticMeshActor_34), and the actor's displayed Label ends up being the bare hostname, not the NC_-prefixed one. Any later operation that reconstructs an actor reference from generate_device_actor_name(hostname) — coloring, destroying, focusing — silently targets nothing. Fixed by capturing the REAL reference returned by the spawn call itself and storing it in scene.py's new device_refs/link_refs/interface_refs dicts (register_device_actor(..., ref_path=actor.ref_path) etc.); every later operation looks up this stored ref instead of reconstructing one from the name.ObjectTools.set_properties on mesh/material properties (staticMesh, overrideMaterials) silently no-ops when targeting the actor instead of its StaticMeshComponent. spawn_device_actor's original mesh-assignment call passed the actor reference; get_properties afterward confirmed staticMesh stayed "None". Fixed by resolving the component first via ActorTools.get_components (actors.get_mesh_component()) and targeting that. This was silently breaking mesh assignment since 044 shipped — devices were invisible StaticMeshActors with no mesh at all, not a scale or color problem.editor_toolset.toolsets.material_instance.MaterialInstanceTools is a real, confirmed-available toolset — not script-based, unaffected by the execute_tool_script sandbox. MaterialInstanceTools.create(folder_path, asset_name, parent) + set_vector_parameter(instance, "Color", {r,g,b,a}), applied to a mesh component's overrideMaterials array via ObjectTools.set_properties, is the confirmed-working, non-scripted way to recolor an actor. Parent it to /Engine/BasicShapes/BasicShapeMaterial — the same material this skill's own primitive meshes already use — which exposes exactly a Color vector parameter and a Roughness scalar. See actors.apply_color_to_actor_ref()/_get_or_create_color_material().EditorToolset.EditorAppToolset has real, non-scripted camera and screenshot tools (SetCameraTransform, GetCameraTransform, FocusOnActors, CaptureViewport) that were undiscovered through all of 044 and most of 045 — camera.py's functions and capture_scene_screenshot() were built on execute_tool_script because this toolset simply hadn't been enumerated yet. CaptureViewport returns a base64 PNG directly in its response. Its schema has the same "marked optional but actually required" quirk documented elsewhere in this file — captureTransform and every field of annotations (including classFilter) must be passed explicitly even though the schema marks them optional; pass gridSpacing/gridExtent/gridHeight/maxLabelDistance/maxLabels all 0 with any valid classFilter to suppress the grid/label overlay entirely./Engine/BasicShapes/Cube.Cube, Sphere.Sphere, Cylinder.Cylinder) are already 100cm (1m) per side/diameter at scale=1.0 — not 1cm as this codebase assumed since 044. DEFAULT_DEVICE_SCALE was {100,100,100}, intended as "1 meter cubes" per its own comment, but actually produced 100-meter cubes (confirmed live via ActorTools.get_actor_bounds: exactly 10000×10000×10000cm). This single bug explains nearly every visual-scale confusion this feature produced: devices completely overlapping despite a seemingly-generous layout spacing, screenshots showing one giant dark silhouette filling the frame, and every position offset elsewhere in this codebase (label height above a device, interface ring radius, camera framing distance) being tuned for human-scale objects and rendered meaningless against 100m devices. Fixed by using scale=1.0 for devices (INTERFACE_SCALE similarly corrected from 20 to 0.2, link cylinder thickness from 10 to 0.1) — always verify a "scale" constant against get_actor_bounds() on a live build rather than trusting a comment's stated intent.TextRenderComponent only renders its text correctly from one side of its plane — at the default rotation=[0,0,0], labels read mirrored/backwards from camera.py's default overview camera position. spawn_label_actor() now defaults to rotation=[0, 225, 0] (yaw toward -X,-Y, matching where calculate_overview_position() places the default camera) — a heuristic default, not a true camera-facing billboard, but it makes labels readable from NetClaw's own default viewpoint instead of facing away from it.CRITICAL: To avoid null mesh errors when saving:
# 1. Load the mesh asset FIRST
loaded_mesh = await load_asset(mesh_path) # Returns asset object
# 2. Spawn empty StaticMeshActor
actor = await add_to_scene_from_class(
actor_type={"refPath": "/Script/Engine.StaticMeshActor"},
name="MyActor",
xform={...}
)
# 3. Assign mesh using set_properties with lowercase 'staticMesh'
await set_properties(
instance=actor["returnValue"],
values='{"staticMesh": <loaded_mesh_object>}' # NOT just path string!
)
# 4. Set label and tags
await set_label(actor=actor["returnValue"], label="Device Name")
await add_tag(actor=actor["returnValue"], tag="netclaw")
To avoid /Temp/ path crashes when saving:
/Game/NetClaw/ via AssetTools.create_folder/Game/NetClaw/NetClawTopo/Game/ pathWhy /Temp/ crashes: UE5 creates temporary levels in /Temp/Untitled_* which cannot be properly saved. Always save to a /Game/ path first.
| Shorthand | Full Path |
|---|---|
| scene | editor_toolset.toolsets.scene.SceneTools |
| actor | editor_toolset.toolsets.actor.ActorTools |
| material | editor_toolset.toolsets.material_instance.MaterialInstanceTools |
| primitive | editor_toolset.toolsets.primitive.PrimitiveTools |
| programmatic | editor_toolset.toolsets.programmatic.ProgrammaticToolset |
| Operation | Full Tool Name |
|---|---|
| Spawn from asset | editor_toolset.toolsets.scene.SceneTools.add_to_scene_from_asset |
| Spawn from class | editor_toolset.toolsets.scene.SceneTools.add_to_scene_from_class |
| Remove actor | editor_toolset.toolsets.scene.SceneTools.remove_from_scene |
| Find actors | editor_toolset.toolsets.scene.SceneTools.find_actors |
| Set transform | editor_toolset.toolsets.actor.ActorTools.set_actor_transform |
| Run arbitrary script inside UE5 | editor_toolset.toolsets.programmatic.ProgrammaticToolset.execute_tool_script — schema {"script": str}, script required. Used by the batch build pattern above. |
Every submodule (actors.py, renderer.py, scene.py, camera.py, telemetry.py) previously used bare absolute imports (from actors import ...) while __init__.py used package-relative imports (from .actors import ...). That combination meant the documented from workspace.skills.ue5_network_viz import ... entry point in this file's own Quick Start (below) had never actually worked — it failed immediately with ModuleNotFoundError. This is why the 2026-07-01 incident's live agent had to hand-write everything from scratch in ad hoc scripts instead of using this skill's own API. Every cross-module import now uses a try: from .x import ... except ImportError: from x import ... shim, so both the documented package import and the sys.path-style loading a live agent does when writing ad hoc scripts work correctly. Verified 2026-07-02 by importing the package both ways.
ASSET_PATHS = {
"cube": "/Engine/BasicShapes/Cube.Cube",
"sphere": "/Engine/BasicShapes/Sphere.Sphere",
"cylinder": "/Engine/BasicShapes/Cylinder.Cylinder",
"cone": "/Engine/BasicShapes/Cone.Cone",
}
{
"location": {"x": 0, "y": 0, "z": 0},
"rotation": {"pitch": 0, "yaw": 0, "roll": 0},
"scale": {"x": 50, "y": 50, "z": 50}
}
{"refPath": "/Game/Maps/Untitled.Untitled:PersistentLevel.ActorName"}
# UE5 MCP Server URL (default: localhost:8000)
UE5_MCP_URL=http://127.0.0.1:8000/mcp
In config/openclaw.json:
{
"mcpServers": {
"unreal-mcp": {
"url": "${UE5_MCP_URL:-http://127.0.0.1:8000/mcp}"
}
}
}
from workspace.skills.ue5_network_viz import (
UE5MCPClient,
safe_render_topology,
)
# Define or fetch topology
topology = {
"devices": [
{"hostname": "core-rtr-01", "device_type": "router"},
{"hostname": "dist-sw-01", "device_type": "switch"},
{"hostname": "dist-sw-02", "device_type": "switch"},
{"hostname": "fw-01", "device_type": "firewall"},
],
"links": [
{"source_device": "core-rtr-01", "target_device": "dist-sw-01"},
{"source_device": "core-rtr-01", "target_device": "dist-sw-02"},
{"source_device": "dist-sw-01", "target_device": "fw-01"},
{"source_device": "dist-sw-02", "target_device": "fw-01"},
],
}
# Render with error handling
result = await safe_render_topology(topology)
print(f"Rendered {result.devices_rendered} devices, {result.links_rendered} links")
from workspace.skills.ue5_network_viz import (
UE5MCPClient,
set_device_critical,
set_link_down,
)
async with UE5MCPClient() as client:
# Mark device as critical (red glow)
await set_device_critical(client, "core-rtr-01")
# Mark link as down (red)
await set_link_down(client, "core-rtr-01", "dist-sw-01")
from workspace.skills.ue5_network_viz import (
UE5MCPClient,
flythrough_orbit,
focus_on_device,
)
async with UE5MCPClient() as client:
# Focus on specific device
await focus_on_device(client, "core-rtr-01")
# Orbital fly-through around topology
await flythrough_orbit(client, duration=30.0)
from workspace.skills.ue5_network_viz import (
UE5MCPClient,
render_topology_incremental,
parse_topology_dict,
)
async with UE5MCPClient() as client:
# Initial render
await render_topology_from_dict(client, initial_topology)
# Later: incremental update (preserves camera)
new_topology = parse_topology_dict(updated_data)
await render_topology_incremental(client, new_topology)
"Get topology from pyATS and render in UE5"
"Visualize the testbed in Unreal Engine"
"Render SuzieQ topology in UE5"
"Show me the SuzieQ network in 3D"
"Render my GNS3 lab in UE5"
"Visualize the CML topology in Unreal Engine"
When Telemetry Receivers MCP is active, device/link colors update automatically based on:
| Metric | Target | Notes |
|---|---|---|
| Render 100 devices | <60 seconds | With materials and lighting |
| Status color update | <30 seconds | Real-time telemetry |
| Camera transitions | Smooth 60fps | Native UE5 interpolation |
| Max devices | 200 | Performance limit |
| Max links | 500 | Performance limit |
ModelContextProtocol.StartServer in UE5 consoleModelContextProtocol.RefreshTools in UE5 consoleNormal behavior - UE5 MCP first request may timeout. Retry.