Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,69 @@ Python MCP server for SolidWorks automation with 154 tools, plus an optional age

I took SolidWorks' own U-Joint sample, read every part, sketch, feature and mate, and wrote it up as a plain-English prompt: eight parts, a crank sub-assembly, and a main assembly that has to move like a real joint. Then Claude Code (Opus 5.5, medium effort) built it from that prompt alone.

<details>
<summary><b>Prompt</b></summary>

> I want to build a small hand-cranked universal joint in SolidWorks: eight parts, a crank sub-assembly, and the main assembly. It's a mechanism: turning the knob turns the crank, which drives the joint through an angle, so the assemblies must still move the way described in sections 9 and 10. Please reason through each part before you start drawing, check every part once it's built, and tell me if something can't be done with the tools rather than guessing. All sizes are in millimetres (the original was drawn in inches, so the numbers are odd). SolidWorks' Y axis is up. Save every part and both assemblies in this folder, with the names given below.
>
> **1. bracket.SLDPRT: a bent plate, 6.35 mm thick and 38.1 mm wide.**
> Seen from the side (sketch on the Right plane, Y up, Z towards the front), the inside outline of the bend is an open chain:
> - a back wall going straight up 82.55 mm from the origin;
> - from its top, a top arm going forward 57.15 mm;
> - from the origin, a bottom foot going forward 44.45 mm;
> - from the front of the foot, a lip rising at 40° for 42.77 mm (it ends 27.49 mm up and 77.22 mm forward).
> The 6.35 mm thickness is on the outside of that outline: above the top arm, behind the back wall and under the foot. The 38.1 mm width is centred on the Right plane. Drill a 12.7 mm hole straight down through the top arm and on through the foot below it (one cut, both plates), centred across the width and 38.1 mm forward of the back wall's inside face.
>
> **2. Yoke_male.SLDPRT: a fork with a stub shaft.**
> - Start with a 38.1 mm diameter cylinder, 47.625 mm tall, standing on the Top plane.
> - Its bottom becomes a fork with two ears. Each ear is 19.05 mm wide and 8.89 mm thick, and the gap between them is 20.32 mm, centred. Each ear has a rounded end: a full radius of 9.525 mm, centred 9.525 mm above the bottom. Where an ear meets the solid part of the cylinder, round the shoulder with a 9.525 mm radius, so the solid part starts 29.21 mm above the bottom.
> - Drill a 9.525 mm cross hole through both ears at the centre of their rounded ends, square to the ears.
> - On top, add a 12.7 mm diameter stub, 19.05 mm tall, on the cylinder's axis. Chamfer its top edge 1.27 mm at 60°.
> - Cut a flat on the stub, 12.7 mm long down from its top. The flat is 4.7625 mm from the axis, so it is 1.5875 mm deep.
>
> **3. Yoke_female.SLDPRT:** the same 38.1 x 47.625 mm cylinder with the same fork and the same cross hole as the male yoke, but no stub. Instead, a 9.525 mm hole goes all the way through along its axis.
>
> **4. spider.SLDPRT:** a 19.05 mm cube centred on the origin. It has two 9.525 mm holes, both through its centre: one front to back, and one left to right.
>
> **5. pin.SLDPRT:** a 9.398 mm diameter rod, 38.1 mm long.
>
> **6. crank-shaft.SLDPRT: a 12.573 mm diameter shaft, 40 mm long, standing on the Top plane.**
> - Around its bottom 15.875 mm, a collar 18.923 mm in diameter.
> - A 4.763 mm fillet where the collar meets the shaft, and a 4.763 mm fillet on the collar's top outer edge.
> - In the bottom, a D-shaped socket 12.7 mm deep. It is a 12.954 mm circle with a flat 4.889 mm from the axis, to take the male yoke's stub.
> - A 2.381 mm cross hole through the collar wall, square to the socket's flat and 6.35 mm up from the bottom.
> - At the top, a matching flat 6.35 mm long, 4.889 mm from the axis.
> - A 0.794 mm x 45° chamfer on the top end.
>
> **7. crank-arm.SLDPRT: a flat bar, 75 x 19.05 x 6.35 mm.**
> - Near one end, a 12.7 mm D-hole centred 9.525 mm from the end and on the bar's centreline. Its flat is 4.889 mm from the hole's centre, towards that end. It fits the shaft's top flat.
> - Near the other end, a 4.763 mm hole 9.525 mm from that end, also on the centreline.
>
> **8. crank-knob.SLDPRT: a mushroom-shaped knob, revolved 360° about the vertical axis.** Going up from the bottom, its profile has:
> - a 4.572 mm diameter shank, 6.35 mm long;
> - a step out to a 9.525 mm diameter;
> - a concave quarter-round of radius 6.35 mm that flares out to 22.225 mm diameter, 12.7 mm up;
> - a convex half-round of radius 4.7625 mm that reaches 31.75 mm diameter at its widest;
> - a flat top 22.225 mm in diameter, 22.225 mm above the bottom.
>
> **9. crank sub.SLDASM:**
> - The crank shaft is fixed.
> - The crank arm's D-hole goes on the shaft's top flat: the hole is concentric with the shaft, the flats are parallel, and the arm sits on the shoulder where the flat starts. The flats lock the arm to the shaft, so they turn together.
> - The knob's shank goes into the arm's small hole, with the knob's step resting on the arm's top face. The knob stays free to spin in its hole, like a handle.
>
> **10. UJoint.SLDASM:**
> - The bracket is fixed.
> - The male yoke hangs under the bracket's top arm. Its stub goes up through the bracket's hole (concentric), and the top of its cylinder is flat against the underside of the arm. It must stay free to turn in that hole.
> - The spider sits centred between the male yoke's ears, with one of its holes lined up with the ears' cross hole. It pivots on that axis.
> - The female yoke's ears go around the spider's other hole, centred on the spider. Its top face is parallel to the bracket's sloped lip, so its axis points down and forward. It pivots on the spider and turns about its own axis.
> - Put one pin through the male yoke's ears and the spider, and one pin through the female yoke's ears and the spider. Each pin's ends are flush with its yoke's round outside, and each pin turns with its yoke.
> - Insert the crank sub-assembly. Its shaft's D-socket goes over the male yoke's stub: concentric, with the flats parallel, and the bottom of the shaft 1 mm above the top of the bracket. The flats lock the crank to the male yoke.
> - The result should be a mechanism with one free motion: turning the knob around the crank shaft's axis turns the male yoke, the spider rocks on its pins, and the female yoke turns about its tilted axis. Choose mates that leave exactly those rotations free and lock everything else.
>
> When it's all built, check each part's size and the assembly's component and mate counts, say which rotations your mates leave free, and tell me what you couldn't do.

</details>

![The original U-Joint sample beside the agent's build: the same bracket, yokes, spider, pins, crank and knob, in the same pose](docs/assets/demos/ujoint/ujoint_original_vs_agent.jpg)

![The agent's 294 tool calls replayed in SolidWorks: eight parts sketched and cut, then the crank sub-assembly and the U-joint mated together](docs/assets/demos/ujoint/test_ujoint_from_prompt_to_assembly-motion.gif)
Expand Down
Binary file modified docs/assets/demos/monitor-stand/monitor_stand_printing.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-11-06
70 changes: 70 additions & 0 deletions openspec/changes/tool-support-levels-and-progress/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
## Context

See proposal.md for why. Current state (counted 2026-11-06 on main):

- 154 registered tools. 39 say "Not available yet" (the queue's not-available list). Of the other 115, 81 are called by name in a `tests/live/` test and 34 are not. Some of the 34 are run only by demo scripts that live tests load (`save_part`, `export_stl` via `scripts/demo_monitor_stand.py`), and some never touch SolidWorks at all (the `generate_vba_*` tools, `search_solidworks_api_help`, `find_related_api_members`).
- `tool_boundary.with_tool_metadata` already adds the family tag (`tool_family`, the module name) and the MCP hints at registration; the level tag and the `warning` fit in the same place.
- The type library (`sw_type_info`, gen_py) has 418 interfaces and 9,282 methods (5,985 distinct names). A plain text scan finds about 199 of those names called in `src/solidworks_mcp/adapters/`. gen_py only exists on Windows with SolidWorks, so CI can't read it.
- The docs site is MkDocs Material, deployed to GitHub Pages on push to main (`deploy-docs.yml`).
- the queue's small-faults list lists the known faults; the agent skill's `references/errors-and-limits.md` repeats them.

## Goals / Non-Goals

**Goals:** a level for every tool that a check keeps honest; the level visible to agents (tags, description, `warning`) and people (README bar, docs page); COM coverage measured the same way every time; the README image and page regenerate from one command, and a check catches when they weren't.

**Non-Goals:**
- Fixing the faults or implementing planned tools (the queue's not-available and small-faults lists); this change only labels them.
- Licence-tier tags (#79's second half): a later change.
- Exact COM coverage. A name scan can't tell `IFeature.Name` from `IComponent2.Name`; the page says the figure is approximate.

## Decisions

### The table: `tools/support_levels.py`

A dict `tool name -> (level, reason)` in one module, beside `tool_hints.py`, so a level is changed in one line. Alternative, a decorator argument on each tool: rejected, the levels would be spread over 15 modules and harder to review as a set.

First assignment, by rule, then reviewed by hand with the user:
- `planned`: the 39 "Not available yet" tools;
- `beta`: a tool named in the queue's small-faults list or in `errors-and-limits.md` as having a fault, or known to cover only part of its COM call (`get_material`, `add_mate` without faces);
- `stable`: called by a live test or a demo script a live test runs, or a tool that never calls SolidWorks and is unit-tested (the VBA generators, the API lookups);
- `experimental`: the rest.

### "Stable needs a live test", checked by text

The check looks for the tool's name as a whole word in `tests/live/*.py` and in the `scripts/demo_*.py` files those tests load. Tools of the offline families (`vba_generation`, `type_library`, `docs_discovery`) are exempt. Alternative, recording which tools a live run actually called: rejected, live runs don't happen in CI.

### Level in the payload: one optional `warning` key

Added by the boundary to a successful result of a beta or experimental tool. It's additive, so existing clients and the stable payload shape (`status`, `message`, `execution_time`, data) are unaffected. Errors don't get it: they already carry `hint`.

### COM snapshot: `docs/assets/progress/com_snapshot.json`

`{sw_version, interfaces: {name: [methods]}}`, written by `src/utils/snapshot_com_api.py` (Windows only, run by hand after a SolidWorks upgrade). Committed so the generator and the check run anywhere. About 150 KB, acceptable.

Core interfaces, fixed in the generator: `ISldWorks`, `IModelDoc2`, `IModelDocExtension`, `IPartDoc`, `IAssemblyDoc`, `IDrawingDoc`, `IFeatureManager`, `ISketchManager`, `ISelectionMgr`, `IComponent2`, `IFeature`, `ISketch`. Coverage of each = its method names called anywhere in the adapters, out of its methods.

### Generator and check

`src/utils/generate_progress.py` writes `docs/assets/progress/progress.svg` and `progress.json` (per tool: name, family, level, reason, first sentence of its description; per core interface: covered and total). `tests/integration/test_progress_up_to_date.py` builds both in memory and compares them with the committed files; on a mismatch it prints the command to run. Alternative, regenerating in CI and committing: rejected, CI shouldn't push to branches.

### The SVG

Hand-built SVG, no new dependency. A dark rounded panel; each bar a rounded track with a gloss highlight and diagonal stripes (a pattern), a soft outline. Tools bar: green `stable`, amber `beta`, orange `experimental`, the `planned` part left as the empty track. Core COM bar: a red-to-yellow-to-green gradient fill to its percentage. A legend with counts, and the whole-API figure in small print. Inspired by the reference the user shared; drawn from scratch.

### The page: `docs/progress.md`

The SVG, then a table of every tool rendered from `progress.json` at build time (a small MkDocs hook or a generated Markdown include, so it works without JavaScript), then `docs/javascripts/progress.js` adding level and family filter chips and hover details. Colours as in the SVG, with a text label as well as colour on every row.

## Risks / Trade-offs

- [Levels get stale as tools change] → the checks above fail on the cases that can be detected; the queue's small-faults note says a fixed fault moves its tool up a level.
- [The COM figure reads as low] → the core-interface bar is the headline; the whole-API figure is small print with an explanation on the page.
- [Description openings change for ~20 tools] → agents see the change at the next connection; the description check covers it.

## Migration Plan

Additive. Clients ignoring tags and `warning` see no change apart from description openings. Rollback is removing the table and the boundary lines; the generated files can stay.

## Open Questions

- The hand review of the first assignment: the user decides the borderline tools (in tasks 1.2).
33 changes: 33 additions & 0 deletions openspec/changes/tool-support-levels-and-progress/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Why

Nothing in the tool list tells you how far to trust a tool. A tool that has been checked on real SolidWorks and uses every input sits beside one that ignores half its inputs (the queue's small-faults list) or one that can't run at all (the 39 tools in the queue's not-available list). People landing on the repo also can't see how much of SolidWorks the project covers, or that it is two things at once: a Python library for scripting SolidWorks and an MCP server for agents. #79 asked for the coverage audit; the user asked on 2026-11-06 for it as a progress bar at the top of the README, kept up to date as tools are added.

## What Changes

- Every registered tool gets exactly one **support level**, kept in a single table:
- `stable`: checked by a live test on real SolidWorks, every input used, no known fault;
- `beta`: works on SolidWorks, but has a known fault or covers only part of its COM call (e.g. `get_material` reads the name and library only);
- `experimental`: implemented, never checked on real SolidWorks;
- `planned`: registered but not implemented (today's "Not available yet" tools).
- A tool's level shows in three places: its tags (`level:beta`, next to the family tag), the start of its description (beta and experimental tools say so first), and its results (a beta or experimental tool's success adds a short `warning`).
- A check fails when the table and the code disagree: a tool missing from the table, a `planned` tool whose description doesn't say "Not available yet" (or the reverse), or a `stable` tool with no live test.
- **COM coverage:** a committed snapshot of the SolidWorks type library (interface → method names, made on Windows with a small script) and a scan of the adapter code for the COM methods it calls. Coverage is counted for a fixed list of core interfaces (application, document, part, assembly, drawing, sketch, feature, selection) and for the whole API.
- **The progress bars:** a generator writes one SVG (two glossy, striped, rounded bars on a dark panel: the tools bar split by level colour, the core-COM bar filled red to green, the whole-API figure in small print) and the data behind it. A check fails when the committed SVG or data is out of date, so adding a tool means regenerating them.
- **An interactive page** on the docs site: the same bars, then every tool and every core interface, filterable by level and family, coloured by level, with each tool's description and the COM methods it reaches on hover.
- **README:** the bar at the top, linking to the page, and a short "two tools in one" section: a Python interface for scripting SolidWorks, and an MCP tool server on top of it.

## Capabilities

### New Capabilities
- `tools/support-levels`: one support level per tool, shown in its tags, description and results, and checked against the code.
- `docs/coverage-progress`: the COM snapshot, the coverage count, the generated progress SVG and interactive page, and the staleness check.

### Modified Capabilities
- `tools/tool-descriptions`: a beta or experimental tool's description opens with its level, as a "Not available yet" tool's already does.

## Impact

- New: `src/solidworks_mcp/tools/support_levels.py` (the table), a generator under `src/utils/`, `docs/assets/progress/` (SVG and data), a docs page with a small script, tests beside each module plus a cross-module check in `tests/integration/`.
- Changed: `tool_boundary.py` (adds the level tag and the `warning`), descriptions of the beta and experimental tools, README, mkdocs nav.
- No COM or adapter code changes: the adapter is only read, as text. The type-library snapshot script needs Windows with SolidWorks, but the check and the generator run anywhere from the committed snapshot.
- Addresses #79 (the coverage half; licence-tier tags are left for later). Feeds #174 (cleanup) and the queue's not-available and small-faults lists, which move tools between levels.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## ADDED Requirements

### Requirement: COM coverage is counted from a committed snapshot

The project SHALL keep a snapshot of the SolidWorks type library's interface and method names, and count coverage by scanning the adapter source for those method names, without needing SolidWorks.

#### Scenario: Counting coverage
- **WHEN** the generator runs on any platform
- **THEN** it reports, for each core interface, the methods the adapters call out of its total, and the same for the whole API, from the snapshot and the adapter source only

#### Scenario: Refreshing the snapshot
- **WHEN** the snapshot script runs on Windows with SolidWorks installed
- **THEN** it rewrites the snapshot from the installed type library and records the SolidWorks version it came from

### Requirement: Progress bars at the top of the README

The README SHALL open with a generated progress image: the tools by support level and the core COM coverage, with the whole-API figure, linking to the interactive page.

#### Scenario: The image matches the code
- **WHEN** a tool, a level or the adapter's COM calls change and the image and data are not regenerated
- **THEN** the progress check fails and says which command regenerates them

#### Scenario: Readable in both themes
- **WHEN** the README is viewed in GitHub's light or dark theme
- **THEN** the image draws its own dark panel, so the bars and their text look the same

### Requirement: An interactive progress page

The docs site SHALL have a page with the same bars and every tool and core interface, filterable by level and family.

#### Scenario: Filtering
- **WHEN** a reader picks a level or a family
- **THEN** only the matching tools are shown, each coloured by its level, with its description on hover

#### Scenario: Without JavaScript
- **WHEN** the page is read without scripts
- **THEN** the bars and a plain table of every tool and its level still show
Loading
Loading