# MCP — drive MyBenchLab from an AI agent

MyBenchLab exposes its bench engine over the **Model Context Protocol (MCP)**. An external agent (Claude Desktop, the Claude.ai web connector, n8n, or your own script) can read the DSL contract, author benches, compile/validate/simulate them, and import/export the `.mbl` file format — all scoped to the token owner's own benches.

The access stack (Personal Access Tokens, OAuth 2.1, and the MCP gateway) is provided by the shared `zodo:auth-mcp-kit` package. The connector id is **`mybenchlab`** and the gateway is mounted at **`/mcp`**.

## 1. Mint a token

Log in, open **Settings → API tokens** (`/settings/tokens`), and click **Generate new token**:

1. Name it (e.g. "Claude Desktop personal").
2. Pick the scopes you want to grant. Scopes are per-tool and grouped by category — use *Select all* to grant the full surface, or a group toggle for just *Benches* or *Type catalog*.
3. Choose an expiration (90 days recommended).

The token is shown **once** in plaintext (`mblmcp_…`). Copy it immediately; it is stored only as a hash and cannot be shown again. You can revoke any token at any time from the same screen.

## 2. Connect a client

**Claude Desktop** — add the server to `~/.config/Claude/claude_desktop_config.json` (or your OS equivalent):

```json
{
  "mcpServers": {
    "mybenchlab": {
      "url": "https://mybenchlab.ai/mcp",
      "headers": { "Authorization": "Bearer mblmcp_YOUR_TOKEN" }
    }
  }
}
```

**curl** — every call is standard MCP over Streamable HTTP:

```bash
curl -s https://mybenchlab.ai/mcp \
  -H "Authorization: Bearer mblmcp_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

The gateway also supports **OAuth 2.1** (DCR/PKCE) for clients that prefer it — see `/.well-known/oauth-authorization-server`.

## 3. Tools

Each tool requires the matching scope `mybenchlab:<tool>`. Every tool acts only on the calling token owner's benches.

| Tool | What it does |
| ---- | ------------ |
| `kinds_catalog` | The engine's catalog: component kinds, connections, ports and categories with docs + params + icons, plus the generated `.d.ts` DSL contract. **Read this first.** |
| `bench_list` | List your benches (metadata only). |
| `bench_get` | One bench: metadata, sources, and a compiled summary (components/connections/drivers + BOM). |
| `bench_create` | Create a bench from DSL source. Compiles best-effort and returns errors/warnings. |
| `bench_build_frame_bench` | Create a workshop bench from **parameters** (length, height, profile, shelves, casters) — a deterministic builder emits validated geometry, no DSL to author. Only `length` is required; leg columns are derived from the length. Legs can be square (`legSize`) or rectangular (`legW`/`legD`); pass a named profile (`legProfile`/`railProfile`, e.g. `metalon-50x30-2.0`) as a shorthand for the raw dims. |
| `bench_build_caster_dolly` | Create a rolling platform / welding cart from **parameters** (four casters or leveling feet, a top, an optional pull handle). Only `width` is required. |
| `bench_build_steel_shelf` | Create a steel shelf unit from **parameters** (U-channel base, round-tube posts, flat-bar rails, wood shelf, optional gusset). Only `width` is required. Named profiles (`channelProfile`/`postProfile`/`barProfile`) are a shorthand that expands to the raw member dims. |
| `bench_rename` | Rename one of your benches. |
| `bench_update_source` | Replace a source file's content and recompile. |
| `bench_remove` | Delete a bench. |
| `bench_compile` | Recompile from stored sources; returns errors + a document summary. |
| `bench_validate` | Run the static validators (connectivity / envelope / interference). Returns `{ ok, errors, warnings, meta }`. |
| `bench_run` | Compile and simulate for N ticks (default 120, max 1200); returns final ω (rad/s + RPM) per port and any conflicts. |
| `bench_export` | Export the bench as a `.mbl` JSON object (sources + compiled document + BOM). |
| `bench_import` | Import a `.mbl` document (string or object) as a new bench. |
| `part_authoring_guide` | Returns the **part-authoring contract**: the feature-list schema (sketch → base op → modifiers), the conventions (mm, closed CCW profile) and worked examples (drilled plate, U-channel). Call it first so YOU (the connected model) know how to build the geometry — no server model involved. |
| `part_create` | Create an **editable** parametric part from a feature list **you** author (see `part_authoring_guide`). The server MATERIALIZES it with the engine (rejects it if it is not a sane solid) and **adds it to a BENCH (a project)** — pass `benchId` to choose one, or omit it to reuse your sticky **active bench** (the first call with no benchId creates one and fixes it as active; later calls keep adding to it). The library ("Minhas peças") is **opt-in**: pass `saveToLibrary:true` (and an optional free-text `category`) to also save the part there. No server model, no API key. Returns `{ ok, benchId, benchCreated, componentId, name, metrics, weightKg, bbox, savedToLibrary, benchUrl, libraryUrl? }` or `{ ok:false, reason, errorInfo }` (fix the pointed feature and call again — the failure happens **before** any bench is created or touched). |
| `part_use_bench` | Select or read the **active bench** used by `part_create` when called without a `benchId`. With `benchId` → verify ownership, make it active, return its info; without → return the current active bench (or `{ active:null }`). |
| `part_from_spec` | *Fallback:* the SERVER calls a model to turn a free-text **dimensional spec** into the feature list, then materializes + saves it. Needs a server-side ai-kit provider/key and is **gated by `partFromSpec.enabled`**. Prefer `part_create` (you author the list yourself, no key). |
| `part_list` | List your Part Design bodies (plus the platform seeds): name, material, volume, weight, bbox, **category**. Pass `category` to filter the library by an exact bucket. |
| `part_get` | One body in detail: the editable feature list, the snapshot (volume/weight/bbox) and the spec sheet. |

The DSL itself is documented in [/docs/dsl](/docs/dsl); the static workbench in [/docs/static-workbench](/docs/static-workbench); the file format in [/docs/file-format](/docs/file-format). `kinds_catalog` returns the same contract in machine-readable form so an agent never has to guess.

## 4. Worked example

**Step 1 — learn the DSL.** Call `kinds_catalog` (no arguments). It returns the component kinds (`statics.steelTubeSquare`, `mech.spurGear`, …), the connection kinds (`statics.weld`, `statics.rest`, `mech.mesh`, …), the port types, and the authoritative `.d.ts`. Read it before writing any source.

**Step 2 — create a bench.** Call `bench_create` with a small static assembly — two upright legs with a rail resting on top and welded to each. A declared envelope is authored as extents from the origin corner `(0,0,0)..(x,y,z)`, so keep the whole assembly in the positive octant:

```js
export default defineBench(({ statics }) => {
  const legL = statics.steelTubeSquare({
    id: 'leg_l',
    width: 50,
    wallThickness: 2,
    length: 700,
    transform: { position: [25, 350, 25], rotation: [-90, 0, 0] },
  });
  const legR = statics.steelTubeSquare({
    id: 'leg_r',
    width: 50,
    wallThickness: 2,
    length: 700,
    transform: { position: [625, 350, 25], rotation: [-90, 0, 0] },
  });
  const rail = statics.steelTubeRectangular({
    id: 'rail',
    width: 40,
    height: 20,
    wallThickness: 2,
    length: 600,
    transform: { position: [325, 710, 25], rotation: [0, 90, 0] },
  });

  statics.weld(legL, rail);
  statics.weld(legR, rail);

  statics.envelope({ x: 700, y: 760, z: 80 });
});
```

`bench_create` returns `{ benchId, errors, warnings }`. Because the compile is best-effort, the bench is created even if a validator complains — you fix it with `bench_update_source` and re-check.

**Step 3 — validate.** Call `bench_validate` with the `benchId`. It compiles and runs the static validators (connectivity / envelope / interference), returning `{ ok, errors, warnings, meta }`. The assembly above returns `ok: true`; if you tighten the envelope or move a part off its neighbour, the errors point at the exact part — adjust positions, envelope, or weld targets and re-check until `ok` is `true`.

**Step 4 — export.** Call `bench_export` with the `benchId` to get the `.mbl` JSON — sources, the compiled document, and the rolled-up BOM (identical parts grouped with quantity + weight). Store it, feed it to another tool, or hand it back to the user.

For kinematic benches, use `bench_run` instead of `bench_validate`: it simulates the gear train and returns each port's final angular velocity so the agent can confirm the output speed before finishing.

## Notes

- **Editable parts, not meshes.** `part_create` (and the `part_from_spec` fallback) never returns baked geometry. It uses the SAME declarative feature list the Part Design editor authors (sketch → pad/revolve/pocket → holes/fillet/chamfer), the engine validates it, and you open it in the feature tree and keep editing dimensions. The server is the geometry authority: a feature list that does not materialize into a sane solid is rejected with a typed `{ ok:false, errorInfo }` pointing at the failing feature.
- **Parts go into a bench by default; the library is opt-in.** `part_create` adds the part to a **bench (project)** — an explicit `benchId`, or your sticky **active bench** (created on the first call, reused after; switch/read it with `part_use_bench`). It only touches the library ("Minhas peças") when you pass `saveToLibrary:true`, which stores a categorized copy (`category` free text). The library grows over time and `part_list` can filter it by `category`.
- **MCP-first: the client model authors.** `part_authoring_guide`, `part_create`, `part_use_bench`, `part_list` and `part_get` are always mounted and need NO server-side model — the connected LLM learns the contract from the guide and builds the part itself. Only `part_from_spec` (where the SERVER calls a model) is gated by `partFromSpec.enabled` and needs an ai-kit provider/key.
- **Ownership.** A token can only see and mutate the benches of the user who minted it. There is no cross-user access.
- **Determinism.** `bench_run` is deterministic: same bench + same tick count ⇒ same ω/θ. The tick cap is 1200 (20 s at 60 Hz).
- **Auditing.** Every tool call is recorded by the kit's MCP audit log.
- **Scopes are least-privilege.** Grant only the tools an integration needs; revoke and re-mint to change them.
