Skip to main content
Glama
lkxlzx

MIDAS NX MCP Connector

by lkxlzx

MIDAS NX MCP Connector

Zero-dependency (stdlib only) MCP server for MIDAS Gen / Civil NX. Exposes the MIDAS NX Open API to an LLM host through six tools, backed by an offline-generated Endpoint Registry and safety guards that encode the hard-won live-testing pitfalls.

LLM host  <--JSON-RPC/stdio-->  midas-mcp  <--HTTP + MAPI-Key-->  MIDAS Gen/Civil NX

Requirements

  • Python 3.9+ (tested on 3.14). No third-party packages.

  • A running MIDAS Gen NX / Civil NX with the Open API enabled and a MAPI-Key.

Related MCP server: midas-bridge-mcp

Quick start

Credentials live in a JSON config file, grouped into per-product profiles, so MIDAS Gen NX, Civil NX and a cloud endpoint each keep their own URL + key:

# 1) create config.json next to the project (it is gitignored)
cp config.example.json config.json     # then fill in the keys
{
  "active_profile": "MIDAS GEN NX",
  "profiles": {
    "MIDAS GEN NX":   { "base_url": "http://localhost:3030/gen",   "mapi_key": "<key>" },
    "MIDAS CIVIL NX": { "base_url": "http://localhost:3030/civil", "mapi_key": "<key>" }
  }
}
python -m midas_mcp                                # stdio, active profile
python -m midas_mcp --profile "MIDAS CIVIL NX"      # switch product
python -m midas_mcp --list-profiles                 # what is configured
python -m midas_mcp --show-config                   # resolved settings (key redacted)
python -m midas_mcp --transport http --port 8100    # Streamable HTTP on 127.0.0.1

config.json is discovered automatically (cwd, then project root, then the user config dir); --config <path> or MIDAS_MCP_CONFIG overrides it. Environment variables still win over the file, so a one-off or CI run needs no edits: MIDAS_MAPI_KEY="ci key" python -m midas_mcp --profile "MIDAS CIVIL NX". The key is never accepted on the command line.

One-shot run

A steel portal frame end to end — build, analyse, self-verify against load equilibrium, print the report — is one command or one tool call:

python -m midas_mcp.frame --spec specs/portal-frame.json          # the report
python -m midas_mcp.frame --spec specs/portal-frame.json --json   # + the verdict

Both commands need midas_mcp importable in that shell: either pip install -e . once, or prefix the command with PYTHONPATH=src (set PYTHONPATH=src in PowerShell). midas_frame_run exports the child's PYTHONPATH itself, so the tool path works without an install.

The MCP tool is midas_frame_run, e.g. {"spec_path": "specs/portal-frame.json"}. Both paths run the same driver, so they cannot drift apart.

Varying the frame needs no second spec file — pass the keys to override inline, and the report describes the frame that was actually built:

{"spec_path": "specs/portal-frame.json",
 "spec": {"span": 24.0, "eave": 7.0, "ridge": 9.5,
          "sections": {"COLUMN": {"id": 1, "name": "COLUMN_H450X200X9X14",
                                  "vsize": [0.45, 0.2, 0.009, 0.014, 0, 0, 0, 0]}}},
 "clear": true}

Verified live: that call answers ok: true, 18/18 steps, 13/13 criteria, 4/4 balance checks, and a report whose model table reads 24 m / 7 m / 9.5 m with the new section names — none of the 20 m frame's numbers survive into it.

specs/portal-frame.json is the validated 20 m span / 6 m eave / 8 m ridge frame. Every key is optional — an omitted key keeps the validated value — and an unknown key is refused by name, never silently ignored. A run refuses to build on a non-empty MIDAS document unless --clear is passed, so a report never mixes two models.

The verdict is one JSON line: {ok, analysis, criteria, verifications, steps, failed, note, report}. ok is deliberately hard to earn — it needs analysis == "SUCCESS", a non-empty criterion list, no failed criterion, and self-consistency checks that actually ran and passed; exit code 0 means the same thing. Everything the report says about the run — the tool list, the artifact directory — is read back from the run itself rather than hardcoded, and a refused run hands back no report at all. The tool wraps that verdict rather than returning it bare: {ok, endpoint, method, status, category, message, report, data}, where data is the verdict above and report is the rendered report. A refusal answers category: "MODEL_NOT_EMPTY" with an empty report, so a caller that reads only the top level still cannot mistake it for a run.

Verified live on Gen NX 2027: 18/18 steps, 13/13 criteria, 4/4 self-consistency checks, ANALYSIS = SUCCESS, exit 0, about five minutes.

Long runs: progress, and not waiting for them

The run above answers only when it is finished, which is three to six minutes. Two ways out, and they compose:

  • Progress. Pass params._meta.progressToken on a blocking tools/call and the server emits one notifications/progress per driver step as it is printed

    • 18 for a complete run. Verified live: 18 notifications, progress 1..18, the right token, all before the response. A background call gets none: MCP progress belongs to a request that is still in flight, and that one is answered at once. Over --transport http there is no progress channel at all, and the server's own instructions say so rather than advertising it.

  • Polling. {"background": true} returns {job_id, running, status: 202} straight away; midas_frame_status {"job_id": ...} then reports the steps the driver has printed so far, and the poll that finds running: false returns the same envelope as the blocking call, report included. One frame run at a time: a second run, or any other write, is refused while one is in flight, because the driver builds in the live document. Reads stay allowed.

Verified live: the job id came back in 0.0 s; the polls showed steps_done 0 → 5 → 7 → 11 → 12 while the run was in flight; the finished poll carried the 9541-character report, 13/13 criteria and 4/4 self-checks with ANALYSIS = SUCCESS; and the blocking call's 18 progress notifications arrived before its response. A job does not survive a server restart, and midas_frame_status says so rather than pretending the id was never valid.

The six tools

Tool

Purpose

Maps to

midas_doc

project control + analysis

POST /doc/<CMD>, e.g. NEW/SAVE/ANAL

midas_db_query

read data; search endpoints; schema introspection

GET /db/X (or /info/db/X)

midas_db_assign

create/update data; run POST actions

POST/PUT, wrapper by registry

midas_db_delete

delete specific ids

DELETE /db/X/<id>

midas_frame_run

one-shot steel portal frame: spec → model → analysis → verified report

python -m midas_mcp.frame

midas_frame_status

progress of a midas_frame_run job, and its report once done

the run's own stdout, kept in the server

Every MIDAS endpoint is addressed by a registry key (DB:NODE, POST:TABLE:REACTIONG, DESIGN:RC:KDS-41-20-2022:DCO), never by a raw URL the model supplies. The registry carries {uri, methods, wrapper, selector, notes} per endpoint (see registry/registry.json, ~590 endpoints).

Guard rails (validated on live Gen NX)

  • Crash guard. Boundary/load records keyed on node/element ids (CONS, CNLD, BMLD, NSPR, SSPS, ELNK, RIGD, …) are refused locally unless the referenced id exists. Assigning a missing id crashes MIDAS (everything after it returns 502); we never let that happen.

  • Delete safety. An empty target_ids is rejected — it never means "delete all". Bulk delete requires delete_all=true and MIDAS_MCP_ALLOW_BULK_DELETE=1.

  • Payload corrections. EIGV.TYPE is forced to LANCZOS; MATL PARAM.P_TYPE is forced to 2 (P_TYPE 1 silently zeroes POISN/THERMAL/DEN/MASS); file paths are converted to Windows backslashes (EXPORT_PATH, which fails on forward slashes).

  • Retry policy. Only idempotent GET retries transient statuses (502/503/504). ANAL, DELETE, IMPORT/EXPORT and design actions are never auto-retried.

  • Success by body, not status. MIDAS returns Wrong Field error bodies with an HTTP 2xx; the connector classifies success from the body.

  • No wrapper injection. Assign/Argument are chosen server-side from the registry; the model cannot twist the request shape.

MIDAS knowledge surfaced as resources

  • midas://knowledge/pitfalls — the validated pitfall table

  • midas://knowledge/routing — tool/ordering rules (also inlined into initialize.instructions)

  • midas://registry/index — full endpoint key list by namespace midas://recipes/one-shot — read this first for a steel portal frame; then midas://recipes/modal-rs, midas://recipes/steel-frame, midas://recipes/load-balance, midas://recipes/rc-section — end-to-end worked sequences. load-balance is the equilibrium proof to run before quoting any extreme value.

Development

# offline unit + protocol conformance tests (no network)
python -m unittest discover -s tests -p 'test_*.py'

# live tests against a running MIDAS (reads config.json; see Configuration below)
MIDAS_MCP_LIVE=1 python -m unittest tests.test_live_gen -v

# regenerate the registry from the vendored docs (docs/manual, pinned commit)
python build/build_registry.py

# probe every GET endpoint in the registry against the live MIDAS
python build/verify_live.py                    # config.json is auto-discovered
python build/verify_live.py --profile "MIDAS CIVIL NX"

The registry is generated from two sources: the v2 dev pack (api_chapters/*.md, uniform Endpoint Registry tables) and the vendored upstream manual (docs/manual/*.md, 505 endpoint sections across nine formats). Upstream wins on URI/methods; local wins on key naming/wrapper. build/build_registry.py --check fails if the documented chapter counts drift. Live probe results are folded back into each endpoint's notes so callers know what this Gen build actually serves (e.g. /db/RCHK, /db/DCON design reads are unavailable).

Registering with a host

With config.json in place the host command needs no secrets in it at all.

Claude Code:

claude mcp add midas -- python -c "import sys; sys.path.insert(0,'src'); from midas_mcp.__main__ import main; main()"
# a different product:
claude mcp add midas-civil -- python -c "import sys; sys.path.insert(0,'src'); from midas_mcp.__main__ import main; main()" -- --profile "MIDAS CIVIL NX"

Claude Desktop claude_desktop_config.json — one server entry per profile:

{
  "mcpServers": {
    "midas-gen": {
      "command": "python",
      "args": ["-m", "midas_mcp", "--profile", "MIDAS GEN NX"],
      "env": { "PYTHONPATH": "G:/StructAI MCP/src" }
    },
    "midas-civil": {
      "command": "python",
      "args": ["-m", "midas_mcp", "--profile", "MIDAS CIVIL NX"],
      "env": { "PYTHONPATH": "G:/StructAI MCP/src" }
    }
  }
}

(Adjust PYTHONPATH/working dir so midas_mcp is importable. Set MIDAS_MCP_CONFIG in env only if the file is not in the auto-discovered locations.)

Configuration reference

Precedence

Highest wins:

  1. Environment variables (MIDAS_MAPI_KEY, MIDAS_BASE_URL, MIDAS_MCP_*)

  2. The selected profile block in the config file

  3. The config file's top level (shared settings)

  4. Built-in defaults

Profile selection: --profile > MIDAS_MCP_PROFILE > active_profile in the file > the only profile when exactly one is defined. Config file lookup: --config > MIDAS_MCP_CONFIG > ./config.json > <project root>/config.json

%APPDATA%\midas-mcp\config.json (or ~/.config/midas-mcp/config.json).

Config file

{
  "active_profile": "MIDAS GEN NX",
  "profiles": {
    "MIDAS GEN NX":   { "base_url": "http://localhost:3030/gen",   "mapi_key": "<key>" },
    "MIDAS CIVIL NX": { "base_url": "http://localhost:3030/civil", "mapi_key": "<key>",
                        "timeouts": { "analysis": 3600 } }
  },
  "timeouts": { "query": 30, "assign": 60, "analysis": 1800, "table": 180 },
  "allow_bulk_delete": false,
  "log_level": "warning",
  "max_workers": 8
}

A profile may override base_url, mapi_key, timeouts, registry_dir, allow_bulk_delete, log_level and max_workers. A flat file with just top-level base_url + mapi_key still works. config.json is gitignored — never commit it.

Environment variables

Env var

Meaning

Default

MIDAS_MAPI_KEY

API key override (beats the config file)

profile value

MIDAS_BASE_URL

base URL override

profile value

MIDAS_MCP_PROFILE

profile to use

active_profile

MIDAS_MCP_CONFIG

config file path

auto-discovered

MIDAS_MCP_TIMEOUT_QUERY/ASSIGN/ANALYSIS/TABLE

per-class timeouts (s)

30/60/1800/180

MIDAS_MCP_ALLOW_BULK_DELETE

allow delete_all (1/true)

false

MIDAS_MCP_LOG_LEVEL

python logging level

warning

MIDAS_MCP_MAX_WORKERS

tool-call thread pool

8

API verification (live, 2026-09-21)

docs/API_VERIFICATION_2026-09-21.md records a full cross-check of the registry against a running Gen NX build. Headline results:

  • 0 method mismatches across 379 endpoints once two doc under-reports were corrected (DB:REBC and DESIGN:SRC:AIK-SRC2K:DSRC both serve GET, which the manual omits).

  • 0 schema field mismatches across the 24 DB endpoints whose schema the server exposes at GET /info/db/<NAME>.

  • 38 endpoints are not served by the Gen build. They are kept and marked (variant: Hyper-S-only / other-product / JP-only) rather than removed, since Civil NX, Civil Designer or the Hyper-S solver do expose them. Run build/verify_api.py to refresh the evidence.

  • An end-to-end cantilever built from an empty document reproduces the closed form to 0.02% (δ = PL³/3EI, M = P·L, V = P), pinned by tests/live_workflow.py.

Variant-aware registration (v3 audit)

Per the official-source audit (MIDAS_MCP_Connector_Development_Pack_v3_official_source), every endpoint is registered with a product and variant marker so callers never assume an endpoint is universal across Civil/Gen:

  • -M1 endpoints are annotated Hyper-S-only.

  • DB:GALD is JP-only (Civil NX Japan edition).

  • DB:HHND (Heat of Hydration Result Graph) and DB:GALD are injected from the audit (they were absent from the vendored manuals) and live-probed.

  • Storey properties are /ope/STORYPROP (STORY+PROP), not the old STORPROP spelling. It is POST-only; "no valid story information" is the expected empty answer until storeys exist. /db/STOR is a no-op stub on the tested Gen build.

Limitations on the tested Gen NX build

  • /db/RCHK (rebar layout) and the /post/TABLE design-force endpoints (STEELMEMBERDESIGNFORCES, COLUMNDESIGNFORCES, BEAMDESIGNFORCES) are not served — see the endpoint notes in the registry.

  • Hyper-S (-M1) endpoints are Gen-version-specific.

  • accepts_analysis results invalidate on any model change; run /doc/ANAL again before reading result tables.

License

Proprietary and confidential - internal use only. See LICENSE. No licence is granted for redistribution or third-party use.

Note that this repository also carries MIDAS GEN NX / MIDAS CIVIL NX API documentation (api_chapters/, docs/manual/, docs/reference/, mcp/). That material belongs to MIDAS IT and is not covered by the notice above.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI clients to interact with Autodesk Revit for building design, editing, analysis, clash detection, MEP, interop, documentation, and model persistence via 48 tools.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables Claude to directly control Midas Civil NX for bridge structural analysis, allowing users to model, apply loads, and perform analysis through natural language descriptions.
    2
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI agents to interact with Bentley STAAD.Pro models for tasks like load case definition, data extraction, and property setting through natural language.
    5
    46
    MIT
  • A
    license
    B
    quality
    A
    maintenance
    Enables AI assistants to interact with bridge structural analysis software for modeling, construction stages, and code checking.
    132
    Apache 2.0