Skip to content

feat: add interactive NX GUI bridge as a C# add-in - #7

Open
mikbalarikan wants to merge 2 commits into
DreamEnding:masterfrom
mikbalarikan:feat/interactive-gui-bridge
Open

mikbalarikan wants to merge 2 commits into
DreamEnding:masterfrom
mikbalarikan:feat/interactive-gui-bridge

Conversation

@mikbalarikan

Copy link
Copy Markdown

Summary

docs/architecture.md names a fallback for NX builds that cannot provide a non-blocking GUI scheduler: a minimal C# NX-side bridge. This PR implements it. nx_gui_bridge/ adds an NX Open .NET add-in that serves bridge protocol v1 from an interactive NX session. NX stays usable, and the model updates on screen while MCP tools run.

These stay unchanged:

  • the sidecar and the 16 certified tools;
  • the descriptor, the JSON-RPC envelope and the error codes.

The only Python change is the optional NX_MCP_STATE_DIR variable. Most of the diff is one C# file of 2,348 lines.

Why

  • The bundled runner pumps requests in a blocking journal, so the NX UI freezes. It works in batch only.
  • A Python UI-thread scheduler does not work on NX2206:
    • NX runs Python startup scripts in a sub-interpreter (ID 1), and its UI thread holds the GIL while idle.
    • A ctypes Win32 timer callback deadlocked that thread on its first tick.
    • Every responsiveness probe over 20 s reported the window as hung.

Changes

1. feat: allow NX_MCP_STATE_DIR to relocate the bridge descriptor (src/nx_mcp/bridge.py and tests)

  • When NX_MCP_STATE_DIR is set, default_descriptor_path() returns $NX_MCP_STATE_DIR/bridge.json. The default location is unchanged.
  • An MSIX-packaged MCP client, and every process it starts, sees a private copy of %LOCALAPPDATA%. Its sidecar therefore never finds the bridge.json written by an NX started from Explorer. On the test machine, the Claude desktop app was such a client.
  • The tests pin the variable, so a developer's environment cannot point them at a live bridge.

2. feat: add interactive NX GUI bridge as a C# add-in (nx_gui_bridge/ and docs)

  • A background .NET thread owns the loopback listener with the same limits as nx_mcp.bridge:

    • 127.0.0.1 on a random port;
    • a 256-bit token;
    • 1 MiB per message and a 5 s receive deadline.

    Requests are handled one at a time.

  • Each call runs on NX's UI thread through a hidden WinForms control. It starts only after UF_UI_lock_ug_access(UF_UI_FROM_CUSTOM) returns UF_UI_LOCK_SET.

    • While a dialog or command owns NX, the call retries every 250 ms.
    • After 60 s it fails with NX_MAIN_THREAD_UNAVAILABLE (not_started, retryable).
  • The executor is a C# port of NXOpenExecutor for the 16 certified commands. It keeps:

    Legacy and experimental commands are not available.

  • Configuration comes from gui-bridge.json next to the DLL. If that file is missing, the bridge reads %LOCALAPPDATA%\nx-mcp\gui-bridge.json, and environment variables override both.

    • workspace is required.
    • state_dir and stop_file are optional.
  • Two ways to load it:

    • Run File > Execute > NX Open (Ctrl+U). Running it again stops the bridge.
    • Put it in the startup folder of a custom directory.
  • The bridge logs each method, its outcome and its duration. The token is never logged.

  • build.ps1 compiles with the in-box .NET Framework csc.exe (C# 5), so no SDK is needed. NX loads an unsigned add-in only with a dotnet_author license; otherwise, sign the DLL with SignDotNet.exe.

  • Docs:

    • The new docs/gui-bridge.md covers the build, configuration, limits and the validation record.
    • The README capability row is updated, and a short README section is added.
    • docs/architecture.md points to the add-in.

Validation

Local checks (Windows 11, Python 3.12.13):

  • pytest -q -p no:cacheprovider -m "not real_nx": 299 passed, 14 skipped. Branch coverage is 82.95%.
  • ruff check, ruff format --check and mypy src/nx_mcp are clean.
  • CI does not build the C# add-in.

Real NX was NX2206 build 2206.9101 only, on 2026-09-18. The add-in was built with the in-box .NET Framework 4.8 csc.exe, without an SDK. The runs used a branch that combined this PR and #6. Its tree is identical to the result of merging both PRs.

Check Result
Load from a custom directory's startup folder NX called Startup() on the UI thread.
python -m nx_mcp.real_smoke --iterations 1 Passed in 29 s.
pytest -m real_nx tests/test_real_nx.py (20 iterations plus negative cases) Passed in 297 s. Each of the 20 STEP files had one solid, 6 faces and a 20 × 10 × 12.5 mm extent.
Protocol parity For 21 malformed and edge-case requests, the codes and execution_state matched the Python server.
Stop file The descriptor was removed. Later calls returned NX_BRIDGE_UNAVAILABLE (not_started).
NX started from Explorer, sidecar inside an MSIX-packaged client, shared state_dir The sidecar found the bridge at once. A 1-iteration smoke passed in 19 s.
A live MCP session in that interactive NX An L-bracket (6 sketch lines, 30 mm extrude) was modeled, saved and exported to STEP in 46 s. The STEP had 1 solid, 8 faces and a 60 × 40 × 30 mm extent.

UI responsiveness was probed every 250 ms during the whole acceptance run: 1,491 probes over 570 s.

  • Windows never reported the window as hung.
  • The median probe latency was 0.26 ms.
  • NX was busy only while a single command ran. The longest busy stretch was 4.5 s, and 28 probes timed out at 2 s, all during commands.

Not yet exercised

  • deferral while a user dialog is open;
  • the Ctrl+U start/stop toggle;
  • running alongside other in-process NX plugins;
  • NX 2506.

Notes for reviewers

🤖 Generated with Claude Code

mikbalarikan and others added 2 commits September 18, 2026 21:32
MSIX-packaged MCP clients, such as Store-installed desktop apps, and every
process they start see a private copy of %LOCALAPPDATA%. A sidecar they
launch therefore never finds the bridge.json written by an NX started from
Explorer. NX_MCP_STATE_DIR selects a shared directory outside AppData; the
default location is unchanged. Tests pin the variable so a developer's
environment cannot redirect them to a live bridge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bundled Python runner pumps requests in a blocking journal, so the NX
UI freezes. A Python UI-thread scheduler is not possible on NX2206: NX runs
Python startup scripts in a sub-interpreter whose UI thread holds the GIL,
and a ctypes Win32 timer callback deadlocked that thread.

Following the fallback in docs/architecture.md, nx_gui_bridge/ adds an NX
Open .NET add-in that serves bridge protocol v1 unchanged. A background
thread owns the loopback socket; each call runs on the NX UI thread through
a hidden WinForms control, only while UF_UI_lock_ug_access succeeds. It
ports the 16 certified commands, reads gui-bridge.json beside the DLL and
honors state_dir / NX_MCP_STATE_DIR. build.ps1 uses the in-box csc.exe.

Validated on NX2206 only (see docs/gui-bridge.md): 20-iteration acceptance
plus negative cases, 21 protocol parity checks against the Python server,
no hung-window probes during the run, and a live session in an NX started
from Explorer with an MSIX-packaged MCP client.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: d94ed04d-7f6b-4de2-8318-28bbcdff38ab

📥 Commits

Reviewing files that changed from the base of the PR and between c5d12ef and 710bbb9.

📒 Files selected for processing (8)
  • README.md
  • docs/architecture.md
  • docs/gui-bridge.md
  • nx_gui_bridge/NxMcpGuiBridge.cs
  • nx_gui_bridge/build.ps1
  • src/nx_mcp/bridge.py
  • tests/test_bridge.py
  • tests/test_mcp_v2.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

1 participant