dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
MCP server driving Ansys SpaceClaim headlessly through PyAnsys Geometry, plus a DSH plugin bundle. Guards the operations that report success without changing geometry.
An MCP server that drives Ansys SpaceClaim headlessly, plus a DSH plugin bundle that installs it into a DeepSeek Harness profile in one command.
The point of this project is not to wrap every API. It is to report what
SpaceClaim actually did, in a place where the honest answer matters: two
operations on SpaceClaim 2024 R2 return success and change nothing, and an
agent that believes the return value will confidently describe geometry that
was never modified. This server measures the geometry before and after, and
raises no_geometry_change instead of reporting a success it cannot see.
Nineteen MCP tools:
| Tool | Read-only | What it does |
|---|---|---|
ansys_bridge_doctor | yes | Reports detected Ansys releases, SpaceClaim.exe, Fluent root, Python, and package versions. Imports nothing heavyweight. |
scdm_session_start | no | Starts a SpaceClaim modeler (auto-detects the release; measured 14–31 s). hidden=false by default, so you see the window. |
scdm_attach | no | Connects to a SpaceClaim the user started (ApiServer add-in + fixed API_PORT). Consumes no licence, and cannot close their window. |
scdm_session_status | yes | Whether a session is live, its mode, and whether a design is visible to it. |
scdm_session_close | no | Releases the session and its licence. Launch sessions only; refused when attached. |
scdm_detach | no | Drops the gRPC channel to a user-owned SpaceClaim. Nothing else. |
scdm_open_file | no | Opens .scdoc/.scdocx/.dsco/.pmdb and reports bodies, named selections, face counts, volumes. |
scdm_list_bodies | yes | Body names, face counts, volumes for the open design. |
scdm_collisions | yes | Pairwise collision state (TOUCH/NONE/…), all pairs or a chosen list. |
scdm_min_distance | yes | Minimum distance between two bodies in metres — the continuous counterpart to collisions. |
scdm_insert_file | no | Merge another CAD file into the open design (open_file opens a document; this adds to one). |
scdm_transform | no | rotate / scale / mirror, verified by a before/after spatial fingerprint. |
scdm_inspect_geometry | yes | The eight official geometry checks: duplicate faces, short edges, small or missing faces, split and stitch faces, extra and inexact edges. Attributes short edges to tessellation chords rather than reporting a bare count. |
scdm_create_pipe | no | Creates a hollow pipe or solid rod from a ring section and an extrude. No boolean. Verifies the annulus area and the extruded volume against the analytic values. |
scdm_extrude | no | Creates geometry by extruding faces of a body already in the design (Add/ForceAdd/ForceIndependent). |
scdm_boolean | no | unite/subtract/intersect, guarded by a before/after geometry check. |
scdm_share_topology | no | Share topology, guarded by the same check. |
scdm_run_script | no | Runs an IronPython script: script_path=<file> or script=<source text> (written to a temp file). |
scdm_export | no | Exports .scdocx, STEP, IGES, Parasolid text/binary, .pmdb — with a file name you control (output_dir + file_stem). |
Before scdm_create_pipe existed, no tool in this server could create geometry:
every one either read the model or operated on bodies that already existed, so
"model from scratch" meant hand-writing IronPython. The route the creation tools use
was measured, and it deliberately contains no boolean:
# a ring section built from two semicircles per circle, then extruded
Body.CreatePlanarBody(plane, profile, False, False, removeInnerRegions=True) -> section
ExtrudeFaces.Execute(Selection.Create(section.Faces[0]), dir, MM(L), opts, None) -> tube
Why not subtract a rod from a rod: on this kernel Body.subtract returns normally,
deletes the tool body and leaves the target untouched. And why read the result
back: removeInnerRegions=False does not fail — it drops the inner loop silently
and hands back a plain outer disc (1963.495 mm² where the ring is 706.858 mm²),
so an unguarded tool builds a solid rod and reports a tube. scdm_create_pipe
checks the section area and the volume against the analytic values and refuses
anything else.
Measured live, over MCP: a 50/40/200 mm pipe came back with 4 faces and
141367.72 mm³ against an analytic 141371.67 mm³; a 20/10/30 mm pipe with
7068.394 mm³ against 7068.583 mm³; an extrusion of a 235.6194 mm² annulus by
5 mm added 1178.081 mm³ against an analytic 1178.097 mm³.
On a stator + 27 windings + pipe + inlet assembly (31 bodies, 846 faces):
subtract fails, and the failure is easy to miss. Target stator: 382 faces
and volume 0.003314505208 m³ before; 382 faces and the same volume after.
The only thing that moved is the body count, 31 → 30, because keep_other=False
deleted the tool.
That matters for how you guard it. The obvious check — "did anything change?" —
answers yes here, so a guard built on it certifies the no-op. This server
therefore judges a boolean by its target: if the named target keeps the same
face count and volume, the call failed, whatever the body count says. The same
symptom appears through raw IronPython Shape.Subtract, so it is a
kernel/geometry problem with that model, not an API-path problem.
share_topology returns True and does nothing. Stator stays at 382 faces,
winding 1 at 16, bodies at 31.
unite does work. Stator faces 382 → 390, volume
0.003314505208 → 0.003382973513 (delta 6.84683e-5, exactly winding 1's own
volume), total faces 846 → 838, bodies 31 → 30. Both number sets match the
raw IronPython result.
Full measured record, including the raw-IronPython traps (Body[](n) is a
parse-time error that kills a script silently; Document.Load breaks every
later SaveAs), is in spaceclaim/skills/ansys-spaceclaim/SKILL.md.
Measured when a SpaceClaim STEP was imported into Fluent Meshing: the import printed
Warning: ????????? and all three named collections were dropped, leaving the
mesh with no usable named selections. The geometry arrived; the names did not.
So name layers and named selections in ASCII — inlet, outlet, wall,
fluid — if the model is going to Fluent. Named selections are what every
downstream boundary-condition assignment depends on, and they are cheap to get
right at the CAD step and expensive to redo later.
Requires Windows, an Ansys installation with SpaceClaim (2024 R2 tested),
and uv.
There are three supported ways to install this in DSH. A and B differ only in
where the MCP server is loaded from; C installs the launcher once and then
starts it from PATH, so uv resolves nothing at launch:
| Option A: install from GitHub | Option B: install from a local checkout | Option C: install the launcher into PATH | |
|---|---|---|---|
| Plugin package | github:1622352030/ansys-agent-bridge | link:<path to your clone> | same as A or B — C replaces only the MCP row |
| MCP launch source | git+https://github.com/…#subdirectory=spaceclaim/python | <clone>/spaceclaim/python | the installed console script ansys-bridge-mcp (no uv, no path) |
| Network needed per launch | yes (a git fetch first) | no GitHub fetch; the PyPI index may be consulted when the environment is (re)materialised | only once, at install time |
| Dependency resolution per launch | every launch | every launch, and any change under <clone>/spaceclaim/python re-materialises the environment | none — resolved once by uv tool install |
| No VPN / GitHub unreachable | ⚠ fails, and can take the whole desktop app down | fine | fine |
| After editing this repo | reinstall required | takes effect immediately | takes effect immediately, with no re-resolution and no rebuild |
| Tracks upstream automatically | yes | no | no |
| Undoing it | dsh plugin … remove | dsh plugin … remove | uv tool uninstall ansys-agent-bridge |
If you are behind an unreliable route to GitHub, use Option B. When Option A's
git fetch fails, the bundled failOnStartupError: false hides the error, and the
automatic reconnect loop rebuilds the environment and re-imports
ansys.geometry.core / ansys.fluent.core over and over, saturating DSH's
single-threaded host event loop — every session history sits at "loading" and the
desktop stops responding. The plugin manager then decides that enabling it "would
persist a state that crashes at boot" and latches it as disabled, after which
the UI switch will not bring it back. Mechanism, diagnostics and the unlock
procedure: TROUBLESHOOTING.md.
dsh plugin --profile web add ansys-agent-bridge
That installs the package, which declares dsh.bundle, so the loader applies
its cordis.patch.yml: one MCP client layer registering the ansys server.
Restart the profile and call ansys_bridge_doctor.
Turning it off again is the same in reverse:
dsh plugin --profile web remove ansys-agent-bridge
The MCP row launches the server with uv tool run --from <this repo>, so no
clone and no virtualenv is needed on the target machine — but the first
call pays for resolving and building ansys-geometry-core. If you keep a local
checkout and want a warm environment instead, use Option B below — note the
path is <clone>/spaceclaim/python (the Python package lives under spaceclaim/);
the older <clone>/python form no longer applies.
Two steps: point the plugin at your local directory, then override the MCP row's
launch source with a --patch overlay.
# 1) point the plugin package at your local clone
dsh plugin --profile web add link:C:/path/to/ansys-agent-bridge
# 2) restate this row in your own --patch layer
# (an id-targeted override replaces the whole `config`)
# %APPDATA%\dsh-desktop\harness\profiles\web\cordis.patch.yml
- id: mcp-ansys
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: ansys
transport: stdio
command: uv
args:
- 'tool'
- 'run'
- '--quiet'
- '--from'
- 'C:/path/to/ansys-agent-bridge/spaceclaim/python'
- 'ansys-bridge-mcp'
env:
PYTHONIOENCODING: 'utf-8'
ANSYS_BRIDGE_LOG_LEVEL: 'WARNING'
toolCallTimeoutMs: 900000
failOnStartupError: false
Restart DSH for it to take effect.
Option B still hands every launch to uv tool run, which resolves dependencies and
re-materialises the isolated environment each time — and redoes that work from
scratch whenever anything under <clone>/spaceclaim/python changes (editing
src/…, for one). Install the launcher once instead, and let DSH start a plain
command:
uv tool install --editable --from <clone>/spaceclaim/python ansys-agent-bridge
✅ Re-verified 2026-10-17:
uv tool listreportsansys-agent-bridge v0.1.0with both console scripts (ansys-bridge-doctor,ansys-bridge-mcp), andansys-bridge-mcpcompletes a real MCPinitializehandshake over stdin (protocolVersionplus the full tool list on stdout) — the install is not just on paper.
The positional argument is the package name ([project].name in
pyproject.toml), not the command name. uv rejects the command-name form
itself, with no room for interpretation:
error: Package name (ansys-agent-bridge) provided with --from does not match install request (ansys-bridge-mcp).
That installs an editable tool environment and puts the console scripts on PATH —
on Windows %USERPROFILE%\.local\bin\ansys-bridge-mcp.exe, and uv tool dir --bin
prints the exact directory. If that directory is on PATH, the DSH override becomes a
bare command name, with no path and no uv:
# restate this row in your own --patch layer
# %APPDATA%\dsh-desktop\harness\profiles\web\cordis.patch.yml
- id: mcp-ansys
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: ansys
transport: stdio
command: ansys-bridge-mcp
args: []
env:
PYTHONIOENCODING: 'utf-8'
ANSYS_BRIDGE_LOG_LEVEL: 'WARNING'
toolCallTimeoutMs: 900000
failOnStartupError: false
Restart DSH for it to take effect. Editing this repo keeps working immediately:
--editable points the installed environment at <clone>/spaceclaim/python/src, so
uv's own help describes it as "changes in the package's source directory are
reflected without reinstallation" — nothing is resolved, rebuilt or fetched on the
next start. Undo the install with:
uv tool uninstall ansys-agent-bridge
!!js nor an absolute pathThat is deliberate: the harness CLI (0.1.1-rc.1) evaluates !!js in a scope without
createRequire and does not await the result, so an expression using either boots on
the Desktop app (0.1.5-rc.2) and leaves the CLI with a profile that will not start.
Both were tried and both broke it; command: uv needs neither. Override that one
line if uv is not on the PATH the harness was launched with:
command: C:/Users/you/.local/bin/uv.exe
The same !!js restriction is why spaceclaim/skills/ansys-spaceclaim/ is not wired up
by the patch. Registering it needs a path resolved at load time, which needs one
of the two constructs above. Add it explicitly instead, in your profile's own
cordis.patch.yml (next to the bundle's) — this runs inside the host, where
dshHomePath is always available and no module resolution is involved:
mkdir -p "$DSH_HOME/skills" # or %APPDATA%\dsh-desktop\harness\skills
cp -r <repo>/spaceclaim/skills/ansys-spaceclaim "$DSH_HOME/skills/"
$DSH_HOME/skills is one of the host's default skill roots, so nothing else is
needed. Without it the MCP tools still work; what you lose is the measured
operating notes and the trap list.
The server is a plain stdio MCP server, so it is not tied to DSH. Generate the block for your client:
uvx --from "git+https://github.com/1622352030/ansys-agent-bridge#subdirectory=spaceclaim/python" \
ansys-bridge-doctor --config claude # also: cursor, vscode, dsh
claude and cursor emit an mcpServers block, vscode emits a servers
block with an explicit "type": "stdio", and dsh emits the insert patch
entry for a profile's cordis.patch.yml. Paste the result into the client's
configuration.
For a client whose schema you would rather write by hand, the command is:
{
"mcpServers": {
"ansys": {
"command": "uv",
"args": [
"tool", "run", "--quiet",
"--from", "git+https://github.com/1622352030/ansys-agent-bridge#subdirectory=spaceclaim/python",
"ansys-bridge-mcp"
]
}
}
}
Use uv tool run, not uv run: measured against uv 0.11.29, uv run rejects
--from with unexpected argument '--from' found, and the MCP client shows
only Connection closed.
uvx --from "git+https://github.com/1622352030/ansys-agent-bridge#subdirectory=spaceclaim/python" \
ansys-bridge-doctor
ansys-agent-bridge 0.1.0
python 3.13.4 C:\...\python.exe
platform Windows-11-10.0.26100-SP0
ANSYS releases detected (AWP_ROOT* and standard install roots):
242 C:\Program Files\ANSYS Inc\v242
SpaceClaim.exe: C:\Program Files\ANSYS Inc\v242\scdm\SpaceClaim.exe
Fluent root C:\Program Files\ANSYS Inc\v242\fluent
Python packages:
ansys.geometry.core 0.17.2
ansys.fluent.core 0.42.1
mcp 1.28.1
Ready:
server yes
spaceclaim yes
fluent yes
Add --json for the machine-readable report.
| Variable | Default | Effect |
|---|---|---|
ANSYS_BRIDGE_UV | — | Not read by the patch (which has no !!js). Override the command: line with a --patch overlay if uv is not on PATH. |
ANSYS_BRIDGE_PRELOAD | 1 | Import the Ansys clients at start-up. Set 0 for a fast start; the first SpaceClaim call then pays that import. |
ANSYS_BRIDGE_TRANSPORT | stdio | stdio, sse, or streamable-http. |
ANSYS_BRIDGE_LOG_LEVEL | WARNING | FastMCP request logging. INFO logs one line per request. |
FastMCP runs a synchronous tool on the event loop thread
(mcp/server/fastmcp/utilities/func_metadata.py: return fn(**args) — there is
no to_thread). Lazy-importing ansys.geometry.core from inside a tool call
was measured wedging the whole process inside numpy's C extension
create_module, with no exception and no timeout — the client sees a tool
call that simply never returns:
_call_with_frames_removed (<frozen importlib._bootstrap>:488)
create_module (<frozen importlib._bootstrap_external>:1321)
<module> (numpy\_core\multiarray.py:11)
...
start (ansys_bridge_mcp\scdm.py)
scdm_session_start (ansys_bridge_mcp\server.py)
_handle_message (mcp\server\lowlevel\server.py)
The identical import finishes in 0.9 s at process start, so main()
imports numpy and both Ansys clients before accepting a request. Stacks were
captured with py-spy dump.
The same fact means long tools block the server: a 30-second SpaceClaim
start holds the event loop, so a concurrent call queues behind it. That is
acceptable here — SpaceClaim mutates one design, and serialising is what you
want — but it is why toolCallTimeoutMs in the DSH patch is 900 s rather than
the 60 s default.
scdm_session_close.ansys.fluent.core inside a tool call to read its version; that library
prints during import, which corrupted the stdio JSON-RPC stream and killed
the session mid-call. package_version() now reads metadata without
importing.The repository is split by product domain, strictly. SpaceClaim material and Fluent material never share a directory, down to the test scripts:
package.json DSH bundle manifest (`dsh.bundle.patch`) + npm entry
cordis.patch.yml the bundle's patch layer
TROUBLESHOOTING.md desktop install / hang / MCP-will-not-re-enable, and how to unlock
screenshots.json
tools/verify-patch.mjs offline check of cordis.patch.yml
spaceclaim/
python/ the MCP server (uv/pip-installable, src layout)
skills/ansys-spaceclaim/SKILL.md measured operating notes and traps
docs/feature-coverage.md implementation vs. the official API
docs/verification.md what was verified, and the bugs found
dev/tests|logs|evidence|models|scratch
fluent/
docs/mcp-audit.md audit of the official ansys-fluent-mcp
dev/tests|logs|evidence|scratch
(no code yet -- the Fluent side currently uses the official package)
dev/ holds development-time material and stays inside the repository on
purpose, so nothing is ever written into a user's model directory. Transcripts,
export dumps and the copied test models are gitignored; the test scripts and the
API inventories are kept.
The root keeps only what must be at the root: npm reads package.json there, and
dsh.bundle.patch resolves ./cordis.patch.yml relative to it.
spaceclaim/docs/feature-coverage.md is the item-by-item
comparison against the official API: every capability domain, whether it is
implemented, and — for the gaps — why. It is not a list of what the server does;
it is the list a reader needs to find what it does not do.
The headline is that the official client declares far more than this release
can run. Of 286 public methods carrying a @min_backend_version gate, only
15 are callable on 24R2; the other 271 need 25.1 through 27.1. That includes
all 44 GeometryCommands modelling methods. The comparison therefore filters by
version first, which is what separates a real gap from a method that would only
raise GeometryRuntimeError.
Two consequences worth knowing before you plan work:
RepairTools.find_*
methods carry no version gate at all, so they work on 24R2, and all eight are
behind scdm_inspect_geometry. On the assembly whose meshing failed, that tool
names the cause: two pairs of coincident faces, one on stator and one on
pip in each pair, with identical areas. Replacing "the mesh failed" with
"these two faces are stacked" is the whole point.create_*_enclosure methods need 26.1.0. For external-flow CFD, build the
domain in the SpaceClaim UI or use Fluent Meshing's enclosure instead.node tools/verify-patch.mjs # no profile needed
uv run --directory python pytest -q
verify-patch.mjs evaluates any !!js expression in the patch inside a bare
with (ctx) { eval(expr) } scope — exactly what the CLI builds — and refuses a
promise result, so a patch that would only boot on the newer harness fails here
instead of on a user's machine. It also checks that the console script the patch
launches is the one pyproject.toml declares.
MIT. See LICENSE.