Skip to content

feat(mcp): add get_network_log + UI automation tools#9437

Draft
eordano wants to merge 73 commits into
devfrom
feat/mcp-cdpu-parity
Draft

feat(mcp): add get_network_log + UI automation tools#9437
eordano wants to merge 73 commits into
devfrom
feat/mcp-cdpu-parity

Conversation

@eordano

@eordano eordano commented Jul 20, 2026

Copy link
Copy Markdown
Member

Bring capabilities to debug the client, not just scenes, to the embedded MCP server.

Network (get_network_log):

  • McpNetworkLogBuffer: thread-safe ring buffer (512) of finished requests with monotonic seq numbers, mirroring SceneLogBuffer (pure, unit-tested).
  • McpNetworkAnalyticsHandler: an IWebRequestAnalyticsHandler taps the exact seam the Chrome DevTools Network domain uses (ChromeDevToolHandler), so coverage is identical: everything through the shared WebRequestController. Records {url, method, status, mimeType, sizeBytes, durationMs, failed, reason} on finish/exception. Wired in WebRequestsContainer, exposed as NetworkLogBuffer, passed to McpServerPlugin.
  • get_network_log tool: limit/sinceSeq/failedOnly/status filters.

Client UI automation (list_ui_elements / get_ui_state / click_ui):

  • UiAutomation walks both UI systems: uGUI Selectables via Selectable.allSelectablesArray, and UI-Toolkit focusable elements under each UIDocument.rootVisualElement. Real clicks: pointer down/up/click (uGUI) or navigation-submit (UI-Toolkit), the way AltTester drives live UI.
  • UiElementPath: pure path build/match rules (unit-tested).

Tests (folded into DCL.EditMode.Tests): McpNetworkLogBufferShould, UiElementPathShould. UI/engine walk + click are integration-only.

Docs: new sections in docs/mcp-automation.md (tool catalog + coverage + implementation map).

pravusjif and others added 30 commits July 5, 2026 03:46
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eat/explorer-mcp-server

Resolved conflict in DynamicWorldContainer.cs: dropped the unused
'using CommunicationData.URLHelpers;' brought by the remote side.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	Explorer/Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs
popuz and others added 27 commits July 17, 2026 14:26
…n't drift

  The path rename /mcp → /unity-explorer-mcp reached the HttpListener prefix
  but not the two "listening on …" announce strings (McpHttpServer and
  McpServerPlugin) nor the docs, so the client logged and copy-pasted a dead
  address. Introduce McpHttpServer.EndpointUrl, derived from one ENDPOINT_PATH
  constant, and route the listener prefix and both announces through it — the
  logged address is now computed from the same string it actually binds. Update
  every external copy that can't reference the constant (docs/mcp-automation.md,
  docs/app-arguments.md, the mcp-scene-iteration skill incl. the functional
  screenshot.sh) and anchor them with a sync comment on the constant.
# Conflicts:
#	Explorer/Assets/DCL/PerformanceAndDiagnostics/Diagnostics/ReportsHandling/ReportsHandlingSettingsProduction.asset
…stion

  The Content-Length guard in HandlePostAsync was bypassed by
  Transfer-Encoding: chunked requests (ContentLength64 == -1), letting
  ReadToEndAsync read an unbounded body into memory. Read into a fixed
  MAX_BODY_BYTES buffer and reject oversized bodies with 413.
…s teardown

  - McpHttpServer: cap POST body read into a fixed buffer so the 1MB limit
    can't be bypassed via Transfer-Encoding: chunked (ContentLength64 == -1)
  - McpServerPlugin: store DebugMenuConsoleLogEntryBus as a field and
    unsubscribe logBuffer.Append in Dispose()
  - McpHttpServer: cap POST body read via ReadBlockAsync so chunked requests
    can't bypass the Content-Length limit and exhaust memory
  - McpHttpServer: capture the listener into a local in the accept loop to
    avoid an NRE race with a concurrent Dispose()
  - McpHttpServer: document the deliberate stateless single-session design
    (unconditional, unvalidated Mcp-Session-Id)
  - McpToolsRegistry: hand out a DeepClone of the tools/list payload so
    concurrent dispatches don't race on the shared JObject
  - McpJsonRpcDispatcher: return -32600 (Invalid Request) for well-formed
    JSON that isn't an object, keeping -32700 for unparseable input
  - McpJsonRpcDispatcher: log a warning when the client's initialize
    protocolVersion differs from the server's
  - McpServerPlugin: store the log entry bus in a field and unsubscribe in
    Dispose()
…hed payload

  The implicit JObject conversion on McpToolsRegistry hid a DeepClone behind
  `Result(id, tools)` in the dispatcher — a surprising conversion with a
  per-request allocation. Serialize the tools/list payload once in Build() and
  hand it out via an explicit ToolsListPayload() that wraps the immutable JSON
  string in a fresh JRaw, so concurrent dispatches share it without cloning or
  re-parenting a JToken tree.

  - Drop `implicit operator JObject` and the per-request DeepClone
  - Store the sealed payload as a serialized string, built once in Build()
  - Update the dispatcher call site to tools.ToolsListPayload()
  - Re-parse the raw payload in registry tests via a Payload() helper
  Replace repeated routable.Value.X access with a single tuple
  deconstruction and a property-pattern guard in DispatchAsync.
  Centralize Formatting.Indented serialization in McpToolResult so tools
  can't drift the structured payload from its text mirror or forget the
  formatting. Convert the eight callsites and drop their now-unused
  Newtonsoft.Json import.
  The builder produces both input and output JSON Schemas (OutputSchema,
  VectorSchema), so the "Input" name misrepresented it. Pure rename of the
  type and its file/test.
  The three structured tools declare OutputSchema and build the payload by
  hand, so a field added to one and forgotten in the other would silently
  misdescribe the result. Add McpSchemaAssert.KeysMatch, a recursive
  schema-vs-payload key check, and wire it into the Get*/List* tool tests
  (populating a camera entity and a scene facade so nested objects are
  covered too).
… Build

  Dedup nullable type-token logic in McpJsonSchema via a private
  TypeToken(type, nullable) helper, dropping the (JToken)type cast in
  Property and sharing it with Object. Add symmetric OutputSchema
  validation in McpToolsRegistry.Build so a malformed output schema
  fails fast at registration instead of reaching the client.
  Change the extension to take HttpStatusCode instead of int, casting
  once inside the method. Drops the (int) cast and statusCode: label
  from all seven call sites and makes the status intent type-safe.
  Cap each tools/call at TOOL_CALL_TIMEOUT with a CancellationTokenSource
  linked to the server-lifetime token. A hung tool now returns a timeout
  error result instead of holding its HttpListenerContext open until
  shutdown. Distinguish shutdown cancellation (rethrow) from timeout
  (error result) via a when (ct.IsCancellationRequested) filter.
  Tools declared "switch to the main thread yourself" and 15 of 16 opened
  with an identical `await UniTask.SwitchToMainThread(ct)`. Hop once in
  McpJsonRpcDispatcher.CallToolAsync instead and flip the IMcpTool contract
  to "invoked on the main thread". Readers with no other await become
  non-async (UniTask.FromResult); tools that offload to the thread pool
  keep their return-trip hops.
Bring the embedded MCP server to parity with chrome-devtool-protocol-unity
by adding the two capability sets CDP had that MCP lacked.

Network (get_network_log):
- McpNetworkLogBuffer: thread-safe ring buffer (512) of finished requests with
  monotonic seq numbers, mirroring SceneLogBuffer (pure, unit-tested).
- McpNetworkAnalyticsHandler: an IWebRequestAnalyticsHandler taps the exact seam
  the Chrome DevTools Network domain uses (ChromeDevToolHandler), so coverage is
  identical: everything through the shared WebRequestController. Records
  {url, method, status, mimeType, sizeBytes, durationMs, failed, reason} on
  finish/exception. Wired in WebRequestsContainer, exposed as NetworkLogBuffer,
  passed to McpServerPlugin.
- get_network_log tool: limit/sinceSeq/failedOnly/status filters.

Client UI automation (list_ui_elements / get_ui_state / click_ui):
- UiAutomation walks both UI systems: uGUI Selectables via
  Selectable.allSelectablesArray, and UI-Toolkit focusable elements under each
  UIDocument.rootVisualElement. Real clicks: pointer down/up/click (uGUI) or
  navigation-submit (UI-Toolkit), the way AltTester drives live UI.
- UiElementPath: pure path build/match rules (unit-tested).

Tests (folded into DCL.EditMode.Tests): McpNetworkLogBufferShould,
UiElementPathShould. UI/engine walk + click are integration-only.

Docs: new sections in docs/mcp-automation.md (tool catalog + coverage +
implementation map).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

badge

Build skipped — no changes detected under Explorer/.

Base automatically changed from feat/explorer-mcp-server to dev July 24, 2026 10:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants