Skip to main content
Glama
meghamshb2006

remote-fusion-mcp

README.md
# remote-fusion-mcp

A **remote MCP server for Fusion 360**, hosted on an Azure VM. It exposes 36
Fusion tools over Streamable HTTP at one stable HTTPS domain. Hermes calls it
from a WSL sandbox; a connector on the Windows laptop dials out to it and drives
the local Autodesk Fusion MCP. The user watches geometry change in their own
Fusion window.

Fusion's API only runs in-process on the desktop, so the modeling itself can
never happen in the cloud. What the remote server provides is the endpoint,
identity, tenant isolation, tool allowlisting, rate limiting, replay protection,
and audit.

```
Azure VM                    apps/server
  /mcp        Streamable HTTP MCP endpoint (stateless, JWT)
  /connector  WebSocket hub for desktop connectors
     ^                    ^
     | HTTPS              | outbound WSS
     |                    |
WSL sandbox          Windows host           <- same laptop
NemoHermes           connector + Fusion 360
                       apps/connector
```

Both arrows point up. Nothing dials into the laptop, which is why this works
behind NAT and through an OpenShell egress allowlist.

## Layout

See `docs/architecture.md` for the full module map, the boundary rules and the
conventions. Short version:

- `packages/contracts` — the only thing both sides share: tool definitions, wire
  schemas, auth claims, error codes. The broker never imports Fusion code; the
  connector never imports MCP transport code.
- `apps/server` — one Node service: MCP endpoint, connector hub, auth, registry,
  guard (rate limit + replay), audit.
- `apps/connector` — desktop client: outbound WS, auto-reconnect, and a pluggable
  Fusion backend (`mock` for dev, `mcp` to wrap the real local server).
- `tests/smoke.mjs` — end to end test, no Fusion required.

## Quick start (no Fusion needed)

```bash
npm install
cp .env.example .env        # dev secrets are fine for local

# terminal 1
npm run server

# terminal 2: mint a connector token, then run the connector
CONNECTOR_TOKEN=$(node tests/mint-token.mjs connector t1 userA devA) \
BROKER_WS_URL=ws://localhost:8080/connector FUSION_BACKEND=mock \
npm run connector
```

Then drive it as Hermes would, using a Hermes token:

```bash
node tests/mint-token.mjs hermes t1 userA   # prints a JWT for the Authorization header
```

Point any MCP client at `http://localhost:8080/mcp` with
`Authorization: Bearer <that token>`.

## Run the tests

```bash
npm test          # coverage + smoke
```

`coverage` checks the surface is fully wired: every tool has a mock handler and
a local name mapping, no tool exposes arbitrary execution, all are annotated.

`smoke` runs the whole path end to end with no Fusion: registration, tools/list,
document, rectangle, parameter update, extrude, fillet, pattern, mass
properties, export, undo, invalid arguments, cross-user isolation, unavailable
session, and scope-based denial.

## Skills

Five workflow templates served as MCP prompts from the VM: `create-box`,
`parametric-part`, `mounting-plate`, `inspect-design`, `safe-edit`. They live on
the server, so editing one changes it for every connected user with no desktop
redeploy. They only reference declared tools, and `npm run coverage` fails the
build if that ever stops being true.

## Tool surface

36 tools across document, inspection, sketch, parameters, features, bodies,
measurement, export, view, and timeline. All lengths in centimeters, all angles
in degrees. The map in `packages/contracts/src/index.ts` is the whole surface —
nothing outside it exists, which is how default-deny is enforced structurally.
There is deliberately no `run_python` or generic `api_call` tool; those are
remote code execution on a user's desktop.

## Wiring the real Fusion

On the Windows machine, with Fusion open and its MCP add-in started:

```bash
FUSION_MCP_URL=http://127.0.0.1:8765/mcp npm run introspect
```

That prints the local server's real tool names. Update `LOCAL_TOOL_NAMES` in
`apps/connector/src/fusion/tool-map.ts` to match — the shipped names are placeholders
and are not to be trusted. Then set `FUSION_BACKEND=mcp`.

The local schema is the source of truth for argument shapes. If it disagrees
with ours, translate in `backend.ts` rather than bending the remote surface.

## Deploy to the Azure VM

See `docs/azure-vm.md` for the full walkthrough. Short version:

```bash
sudo ./infrastructure/azure/setup-vm.sh fusion-mcp.example.com
```

Installs Node and Caddy (automatic TLS), creates an unprivileged service user,
generates fresh secrets, and runs the server under systemd. Open 443 and 80 in
the NSG; leave 8080 closed — the server binds locally and Caddy fronts it.

`docker compose up` runs the server plus a demo mock connector for local dev.

One VM, one server process is the intended shape, and the in-memory session
registry is correct for it. Only multi-replica needs Redis; `SessionRegistry` in
`apps/server/src/registry.ts` is the seam if that day comes.

## Security model

- North (Hermes to broker): OIDC + short-lived JWT. Dev uses HS256; production
  swaps to RS256 with JWKS.
- South (connector to broker): device token on the outbound WS. Production swaps
  to mTLS or a device bound credential. The connector never opens an inbound port.
- Default deny: only the closed tool surface exists. A token `scope` can further
  restrict it per user. No arbitrary code execution tool exists.
- Every call is rate limited, deduped within a short window, and audited with a
  correlation id.
# custom-fusion-mcp