Skip to content
Conduit

mcp server

nxopen-mcp

MCP server giving AI coding agents accurate Siemens NXOpen .NET API knowledge via hybrid retrieval (BGE-M3 + sqlite-vec), built from your own licensed NX installation

mingfeng6684/nxopen-mcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "nxopen-mcp": {
      "command": "uvx",
      "args": [
        "nxopen-mcp"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 17, 2026

nxopen-mcp

English | 繁體中文

MCP server that gives AI coding agents (Claude Code, Codex, Cursor) accurate knowledge of the Siemens NXOpen .NET API — eliminating hallucinated API calls via hybrid retrieval over your own NX installation's official documentation.

Why

LLMs hallucinate NXOpen APIs: it's a niche domain (Siemens NX CAM/CAD automation) with sparse public training data, so models confidently invent classes, methods, and parameters that don't exist. This server grounds agents in the real docs instead of guesses:

  • Semantic search (BGE-M3 dense + sparse embeddings) so natural-language queries in English or 中文 find the right API even without exact names.
  • Exact-name channel: a literal CamelCase name in your query (e.g. CavityMillingBuilder) is looked up directly and pinned to the top (types first) — never left to approximate matching.
  • RRF fusion available to combine channels — though evaluation made dense + exact the default (see Evaluation).

Everything runs locally and offline against an index built from your own licensed NX installation — no Siemens files are ever bundled with this repo or sent anywhere.

Quick start

Requires Python 3.11+.

# 1. Install from PyPI (or run without installing: uvx nxopen-mcp)
pip install "nxopen-mcp[embed,reflect]"

# 2. Build the index from YOUR NX installation (one-time — see time note below)
nxopen-mcp index --nx-path "D:\Siemens\NX12.0"

# 3. Register with Claude Code (user scope: available in every project)
claude mcp add -s user nxopen -- nxopen-mcp serve

# 4. Ask Claude Code to write NXOpen code — it now queries real APIs.

index looks for NXOpen*.xml doc files under <nx-path>\UGII\managed (falling back to <nx-path> itself), and for NXOpen*.dll assemblies in the same folder.

Extras: [embed] pulls in FlagEmbedding (downloads the ~2GB BGE-M3 model on first use) — required for indexing and semantic search. [reflect] pulls in pythonnet so get_class can show inherited members; skip it and indexing still works, just without inheritance chains.

How long does indexing take? Honest numbers: a full NX 12 doc set is ~100k members; on an 8-core laptop CPU that's several hours of embedding (memory-bandwidth-bound — --workers N helps mainly on machines with more memory channels; a CUDA GPU helps a lot). Plan to run it overnight, or copy a teammate's index (see Sharing a pre-built index).

First semantic query is slow by design. serve starts instantly, and get_class / get_member respond immediately, but the first search_api / find_builder call loads the BGE-M3 model (~1–2 min). After that, semantic queries take seconds. If your MCP client shows the first search "hanging", it's the one-time model load — let it finish.

By default the index is written to ~/.nxopen-mcp/index.db; override with --db <path> on both index and serve.

If nxopen-mcp isn't on your PATH (e.g. installed into a venv), use the full path to the executable (Windows: <venv>\Scripts\nxopen-mcp.exe) in the commands and configs below.

.mcp.json (Claude Code / other MCP-aware clients)

{
  "mcpServers": {
    "nxopen": {
      "command": "nxopen-mcp",
      "args": ["serve"]
    }
  }
}

If you built the index at a non-default path, pass it explicitly:

{
  "mcpServers": {
    "nxopen": {
      "command": "nxopen-mcp",
      "args": ["serve", "--db", "D:\\path\\to\\index.db"]
    }
  }
}

Codex

Codex CLI reads MCP servers from ~/.codex/config.toml (Windows: C:\Users\<you>\.codex\config.toml; create the file if it doesn't exist). Add:

[mcp_servers.nxopen]
command = "nxopen-mcp"
args = ["serve"]

If nxopen-mcp isn't on PATH, use the full executable path (double the backslashes on Windows):

[mcp_servers.nxopen]
command = "D:\\path\\to\\venv\\Scripts\\nxopen-mcp.exe"
args = ["serve", "--db", "D:\\path\\to\\index.db"]

Alternatively, one CLI command does the same thing:

codex mcp add nxopen -- nxopen-mcp serve

Restart Codex, then verify with codex mcp list (or just ask it to look up an NXOpen class — you'll see the nxopen tool calls). Note the same first-semantic-query model load (~1–2 min) applies here.

Cursor

Add the same JSON block as .mcp.json above to Cursor's MCP settings (project .cursor/mcp.json, or global via Settings → MCP).

Tools

toolpurpose
search_apiHybrid semantic search over the API (dense + exact-name by default, sparse optional). Accepts English or 中文 queries; use when you don't know the exact class/member name.
get_classFull member list for a class, including members inherited from its ancestor chain. Use when you know the class name.
get_memberExact signature, parameters, return value, NX version, and license requirement for one member.
find_builderGiven a CAM operation name (e.g. "cavity milling", "hole drilling"), finds the matching *Builder class, its creator method, and a Builder → Commit → Destroy code skeleton.

Architecture

NXOpen*.xml / *.dll  (your NX install)
        │
        ▼
  indexer/parser.py        one XML doc-comment member -> one MemberRecord
  indexer/inheritance.py   optional: reflect DLLs (pythonnet) for base-class chains
        │
        ▼
  indexer/embedder.py      BGE-M3 dense vector + sparse token weights per record
        │
        ▼
  indexer/build.py         writes members, dense_vec (sqlite-vec), sparse_postings
        │                  into a single SQLite file (index.db)
        ▼
  retrieval/store.py       exact-name lookup, class/member/inheritance queries
  retrieval/hybrid.py      dense ANN + exact CamelCase match (default),
                           optional sparse channel, RRF fusion
        │
        ▼
  server.py                4 MCP tools (FastMCP, stdio transport)
  cli.py                   `nxopen-mcp index` / `nxopen-mcp serve`

Design decisions:

  • BYO-Docs licensing. This repository contains no Siemens XML/DLL files. Users point nxopen-mcp index at their own licensed NX installation; the resulting index is a local SQLite file that never leaves the machine and is never committed (see .gitignore).
  • One-member-one-chunk. Each indexed unit is a single API member (type, property, method, field, or event) rather than an arbitrary text window, so retrieval results map 1:1 onto something an agent can act on (a class, a method signature) instead of a fragment of a doc page.
  • RRF fusion, not score blending. When the sparse channel is enabled, dense and sparse rankings are combined with Reciprocal Rank Fusion, which is scale-free and doesn't require calibrating dense-vs-sparse score magnitudes against each other (the evaluated default runs dense + exact only). Exact CamelCase name matches are promoted ahead of the fused list outright, since a literal name in the query is a much stronger signal than similarity.
  • Inheritance via reflection, with graceful degradation. Ancestor chains (needed by get_class to show inherited members) are extracted by reflecting the NXOpen DLLs with pythonnet ([reflect] extra) at index time. If the extra isn't installed or DLLs aren't found alongside the XML docs, indexing still succeeds — get_class simply has no inherited members to show.

Evaluation

Measured on a real index built from an NX 12 installation (97,913 API members) against a 73-query golden set (eval/golden.jsonl, mixed English / Traditional Chinese, four query styles — semantic description, exact class name, member lookup, builder idiom — spanning CAM, Features, Sketch, Assemblies, Drawings, UF, and BlockStyler):

python eval/run_eval.py --db ~/.nxopen-mcp/index.db
configRecall@5Recall@10MRR
dense-only58.90%68.49%0.420
sparse-only34.25%38.36%0.193
dense+exact (default)58.90%68.49%0.445
dense+sparse+exact49.32%61.64%0.409

With vs. without the tool: hallucination test

Same model (Claude Haiku), the original 33 golden questions, one variable — whether the nxopen-mcp tools are available. Answers were graded against the golden set; "hallucinated" means the proposed member does not exist anywhere in the real 97,913-member index:

metricclosed-book (no tool)with nxopen-mcp
exactly correct13/33 (39.4%)31/33 (93.9%)
wrong but real API13/33 (39.4%)2/33 (6.1%)
hallucinated (API does not exist)7/33 (21.2%)0/33 (0%)
time to answer all 3384 s321 s (44 tool calls)

The closed-book hallucinations are the dangerous kind — plausible names like NXOpen.CAM.MillGeometryBuilder (real name: MillGeomBuilder) or NXOpen.Session.Parts.Open (real: NXOpen.PartCollection.Open) that read fine and fail at compile time. Tool-assisted answering costs ~7 s per question and eliminated hallucinations entirely.

Evaluation-driven default. The original design fused dense, sparse and exact-name channels with uniform RRF. Measurement showed BGE-M3's sparse channel hurt on this corpus — on the initial 33-query set a weight sweep (w_sparse ∈ {0.5, 0.3, 0.15}) never recovered the dense-only baseline, and expanding the golden set to 73 queries replicated the finding (Recall@5 58.9% → 49.3% when sparse is fused). The exact-name channel — after reordering its matches (types first, shortest name first, capped at 3) — matches dense recall while improving MRR (0.420 → 0.445) and guaranteeing literal-name hits. The default is therefore dense + exact; the sparse channel remains available via the channels parameter of search().

Demo

A real session: asked to write NXOpen code that sets the spindle speed, Claude Code calls search_api (semantic search, English or Chinese) and get_class (members + inheritance chain), then writes code in which every member — FeedsBuilder, SpindleRpmToggle, SpindleRpmBuilder.Value — exists in the real API, with NX version info to prove it.

Sharing a pre-built index

The index is a single SQLite file (~500 MB for a full NX 12 doc set), so teammates can skip the hours-long build:

  1. Install nxopen-mcp (the [reflect] extra is not needed — inheritance chains are already baked into the index).
  2. Copy the index file to ~/.nxopen-mcp/index.db (or keep it elsewhere and pass --db <path> to serve).
  3. Register the server: claude mcp add -s user nxopen -- nxopen-mcp serve

A full step-by-step onboarding guide (with troubleshooting): docs/setup-prebuilt-index.md (繁體中文版).

The BGE-M3 model (~2 GB) still downloads on the first semantic query — it encodes the query text, independent of the index. Exact lookups (get_class / get_member) never need the model.

Licensing boundary: the index embeds text from Siemens' API documentation. Sharing it within an organization whose seats are licensed for NX is reasonable; do not redistribute index files publicly — anyone outside your license should build their own with nxopen-mcp index.

License & IP

Code: MIT (see LICENSE). This repository contains no Siemens files — no NXOpen XML docs, no DLLs. The index is built locally from your own licensed NX installation's documentation via nxopen-mcp index and never leaves your machine.

↑↓ navigate openesc close