Content
# Visio MCP Server
Drive Microsoft Visio from Claude — or any MCP client — and expose patent figures as structured, queryable context for downstream LLM workflows.
Two roles at once. A **write surface** that bakes USPTO 1.84 conventions into every edit (Arial, ALL CAPS, 1.0pt strokes, sample-and-replicate lead lines, hub-fanout U-shape connectors, 1.84(g) margin guard). A **read surface** that tells downstream LLM workflows what's actually in a Visio document: which numeral points to which shape, which connector is a flow arrow vs. a leader, what reference numerals exist in each figure's range. The reads do real semantic work that's not derivable from rendering alone, which makes the server useful to consumers beyond the agent doing the editing (drafting, QC, cross-doc consistency).
Built for U.S. patent attorneys who keep their drawings in Visio. It works two ways:
- **On Windows** — run Claude (Code or Desktop) and Visio on the same machine. The installer registers everything locally; no VM involved.
- **On a Mac** — keep drafting on macOS while Visio runs in a Parallels Windows VM. Claude Code on the Mac reaches the server in the guest over the network.
Either way, the server drives Visio through COM, which means it binds to a **running** Visio instance — you keep Visio open with your document while you work, and a small system-tray app starts, stops, and monitors the server process alongside it. The server exposes structured tools over MCP (streamable HTTP, or stdio via a bundled bridge for clients that need it), and every edit respects Visio's undo stack and the document's masters/themes.
## Demos
**Flow chart from a method claim (single tool call).** `build_flow_chart` constructing a complete patent-style flow chart: stadium START/END, six step boxes, connectors, reference numerals 102–114, "FIG. 1" title. Includes cold-start leader synthesis and ALL CAPS enforcement.

Higher-quality MP4: [`flow-chart-demo.mp4`](demos/flow-chart-demo.mp4)
**Computer-architecture diagram (multi-call build).** A modern desktop architecture (CPU, system DRAM, GPU + VRAM subsystem, display, PCH, and four PCH-attached peripherals) built with `add_box`, `add_connector` (orthogonal U-shape routing for the PCH bus fanout), `add_reference_number` (sample-and-replicate lead lines), and `add_text` for the FIG. 2 title.

Higher-quality MP4: [`computer-arch-demo.mp4`](demos/computer-arch-demo.mp4)
## What it does
**Reads (structured context for any MCP client):**
- Enumerate pages, list shapes with IDs / text / bounding boxes / master, against the live document, not a stale `.vsdx`.
- `list_reference_numbers` pairs each numeral with its target via layered glue → target-aware geometry → proximity classification, with `confidence` tier and `leader_kind` (`connector` / `linear_shape` / `underline` / `none`) so consumers can filter by trust before relying on the data.
- Render pages to clean high-DPI PNGs via Visio's own `Page.Export` (no window chrome) for visual verification or LLM consumption.
- `find_shapes` text search across one page or the whole doc.
**Writes (edits with USPTO conventions baked in):**
- `add_box`, `add_text`, `add_connector` (orthogonal U-shape routing for hub fanouts), `add_reference_number` (sample-and-replicate lead lines with cold-start synthesis fallback), `build_flow_chart` (full skeleton in one call), `move_element_with_references`, `renumber` (guarded by `expected_count`), and more. All auto-uppercase text, all 1.0pt black strokes, all snap to a 0.01" grid.
- Every write re-reads state via COM first; destructive writes require `expected_*` assertions; each tool wraps its work in a Visio undo scope so ⌘Z reverses one Claude action.
## Use cases for patent drafting
Concrete workflows a patent attorney hits while preparing an application. Each is a plain-English ask in any Claude session with the server connected — the tools named are what runs underneath.
### Reading the drawings
- **Generate an element list from the figures.** Render every figure page, read each reference numeral and the component its leader points to, and produce a canonical `# | Element | First fig. | Also in | Notes` table grouped by series. This is the `/element-list` slash command (`list_pages` → `render_page` per figure). The starting point for a specification's "Brief Description of the Drawings" and reference-numeral list.
> /element-list
- **Reconcile drawing numerals against the specification.** Pull every reference number from the drawings (`list_reference_numbers`, which pairs each numeral with its target and gives a `confidence` tier), then diff against the element list in your spec. Surfaces drift in both directions: numerals in the figures that the spec never describes (undescribed elements — a §112 risk), and numerals in the spec with no matching callout in the drawings.
> Compare the reference numbers in the Visio drawings to the element list in my specification and flag any that appear in one but not the other.
- **Audit numbering-convention compliance.** Check the figures follow house numbering — first digit(s) encode the figure number (FIG. 2 → 200-series), even steps, repeated element types sharing a base number with letter suffixes (130A/130B/130C, not 130/132/134). Catches gaps, collisions, and a numeral pointing at two different components across figures.
> List all reference numbers by figure and tell me if any break the numbering convention or are reused for different elements.
- **Locate an element across the figure set.** Text search every page for a label or numeral (`find_shapes`) — useful when a claim term needs a consistent numeral and you need to see everywhere it already appears.
> Where does "SECURE ELEMENT" appear across the drawings, and what numeral is it?
- **Visual QA before filing.** Render a figure (or a cropped region) to a clean high-DPI PNG to eyeball leader targets, arrowhead direction, or margin compliance without opening Visio yourself.
> Render FIG. 3 and confirm every box has a reference number with a leader line.
### Editing the drawings
- **Generate a flow chart from a method claim.** Turn the steps of a method/process claim into a USPTO-style flow chart — START/END terminators, one step box per limitation, connectors, and reference numerals — in a single `build_flow_chart` call (numbers start at `fig_base + 2`, START conventionally unnumbered). Decision points add via `add_decision` / `add_decision_branch`.
> Build a flow chart on a new page from the steps of claim 1, numbered in the 400 series.
- **Add or correct reference numbers.** Drop a numeral with a leader line to a component (`add_reference_number`) — it samples and replicates an existing hand-drawn leader's style, or synthesizes one on a blank doc, and auto-picks the next free number in the figure's range. Fixes the mechanical, error-prone part of drawings by hand.
> Add reference number 312 pointing at the controller box in FIG. 3.
- **Fix elements flagged during reconciliation.** Once the spec/drawing diff surfaces an undescribed or miscalled element, add the missing callout or relabel in place — repeated instances get letter suffixes per house style (`add_reference_number(..., suffix='B')`).
> The three antenna elements should be 240A, 240B, 240C — relabel them.
- **Renumber to resolve collisions.** When two numerals collide or a series needs to conform, `renumber` rewrites them — guarded by an `expected_count` assertion so it won't run against a document that changed under you.
- **Draft a system or architecture diagram.** Lay out boxes and connectors for an apparatus/system figure from a description — hub-and-spoke fanouts route as orthogonal "down-across-down" buses automatically (`add_box` + `add_connector`).
> Draw a block diagram: a central processor connected to memory, a radio, and three sensors.
## Why this exists
Visio doesn't run on macOS, so patent attorneys end up tabbing between a Mac (where they draft) and a Windows VM (where they draw). Claude can help with the drawings (adding/correcting reference numbers is mechanical, repetitive, and easy to get wrong by hand), but only if it can see the live document and apply edits with high enough fidelity that the file stays clean for the human user.
`.vsdx` file-level manipulation is fine for reading and bad for writing (master shapes, themes, and connection points break easily). COM automation against a running Visio keeps everything coherent.
## Requirements
**Common to both setups:**
- Microsoft Visio (any modern version; tested on Visio 16), running on Windows.
- Claude Code or Claude Desktop (or any MCP client).
**Windows (native) — Claude and Visio on the same machine:**
- That's it. Python and dependencies are installed by the setup script.
**macOS — Visio in a Parallels VM:**
- macOS with Parallels Desktop, hosting a Windows guest with Visio.
- The project folder visible to both sides via a Parallels shared folder (default `\\Mac\Home\...`).
- Claude Code on the Mac.
Python and dependencies are installed by the setup script — you don't need to install anything yourself first.
## Quick start
Two paths — pick the one matching your setup.
### Windows (native)
Claude and Visio on the same Windows machine. No VM involved.
**1. Clone the repo and open Visio** with any `.vsdx` (so the COM probe has an active document):
```sh
git clone https://github.com/LeonardHope/Visio-MCP-Server-for-Patent-Attorneys.git
```
**2. Set up the server.** In PowerShell, from the cloned folder:
```powershell
PowerShell -ExecutionPolicy Bypass -File .\scripts\vm_setup.ps1
```
Installs Python (via `winget` if missing), creates `.venv`, installs `pywin32 / fastmcp / pillow / pystray`, probes Visio COM, and creates a **Visio MCP** Desktop shortcut.
**3. Launch the Visio MCP shortcut.** The tray icon turns green; the server listens on `http://127.0.0.1:8765/mcp`.
**4. Register with Claude on the same machine.** For Claude Code:
```powershell
claude mcp add --transport http visio http://127.0.0.1:8765/mcp
```
For Claude Desktop, point a stdio entry at the bundled `visio-mcp-bridge` — the Windows installer in [`installer/`](installer/) wires this up (and the Claude Code registration) automatically.
**5. Verify.** Run `/mcp` in Claude Code → `visio: ✓ Connected`.
### macOS (Visio in a Parallels VM)
**1. Clone the repo on the Mac**, into a folder that's visible to the VM via Parallels shared folders (your Mac home is the default):
```sh
git clone https://github.com/LeonardHope/Visio-MCP-Server-for-Patent-Attorneys.git ~/Projects/Visio-MCP-Server-for-Patent-Attorneys
```
**2. Open Visio in the VM with any `.vsdx`** so the COM probe in the next step has an active document to enumerate.
**3. In the VM, run the installer.** Open PowerShell, navigate to the cloned folder via the shared-folder mount, and run:
```powershell
cd "\\Mac\Home\Projects\Visio-MCP-Server-for-Patent-Attorneys"
PowerShell -ExecutionPolicy Bypass -File .\scripts\vm_setup.ps1
```
This installs Python (via `winget` if missing), creates `.venv`, installs `pywin32 / fastmcp / pillow / pystray`, runs the Visio COM probe, and creates a **Visio MCP** shortcut on your Desktop.
**4. Double-click the Desktop shortcut.** The tray icon turns green when the server is running. Right-click for status, start/stop/restart, view logs, copy connection URL, and "Start with Windows" (auto-launch on VM boot).
**5. On the Mac, register the server with Claude Code:**
```sh
cd ~/Projects/Visio-MCP-Server-for-Patent-Attorneys
./scripts/install-mac.sh
```
This auto-discovers the VM's IP via `prlctl` (or prompts), registers the MCP server at **user scope**, and copies the project's slash commands (`/element-list`, etc.) to `~/.claude/commands/`. The `mcp__visio__*` tools and slash commands are then available in **every** Claude Code session, including ones running from a matter folder, not just the cloned-repo folder.
**6. Verify in Claude Code.** Open a session anywhere and run `/mcp`. You should see `visio: ✓ Connected`. Or just ask:
> List the pages in Visio.
>
> Render FIG. 1 with shape IDs overlaid.
>
> /element-list
Total install time: ~5 minutes if Python is already on the VM; ~10 if `winget` has to fetch it.
## Available tools
36 tools, grouped by what they unlock. See `Visio MCP Tool Plan.md` for the cross-cutting write contract (pre-read state, `expected_*` assertions, atomic undo scopes).
**Style enforcement built into every tool that writes text:** input is auto-uppercased (USPTO 37 CFR 1.84(p)(3) minimum-height friendly and consistent with house style). Pass mixed-case text and you'll get uppercase shapes back.
**Read / inspect**
| Tool | What it does |
| --- | --- |
| `list_pages` | Page names, dimensions, shape counts. |
| `list_shapes(page, fields=None)` | All shapes on a page: ID, name, text, bounding box, master. Pass `fields=['id','text']` (any subset of `id`, `name`, `text`, `bbox`, `master`) to cut tokens — bbox is the largest field per shape. |
| `list_reference_numbers(page)` | Each numeral on a page paired with its target. Candidate pool includes 1D connectors and thin 2D rectangles (aspect ≥ 4:1, area ≤ 0.1 sq in) so rectangle-tool leaders aren't missed. Two pre-filters run before matching: inter-box flow connectors (both endpoints inside distinct LABEL shapes — numerals deliberately excluded since real leaders often terminate inside both the numeral's wide text container AND the target box) are dropped, and candidates are assigned to numerals via Hungarian min-cost bipartite matching so adjacent numerals don't swap leaders in dense figures (Voronoi-style nearest-numeral was the previous heuristic and could leave a sibling numeral unmatched even when an acceptable alternative existed). Target scoring sorts by (text_rank, distance, area) — text-bearing inner labels beat unlabeled enclosing containers; targets that enclose the numeral itself are skipped. When the winner is a text-empty container AND exactly one text-bearing inner shape sits within 0.5" of the endpoint, the target swaps to that inner shape (container-specificity fix for nested figures); ambiguous cases with multiple inner candidates leave the container as target. After per-numeral matching, target-collision resolution demotes the losing numeral when two share a target; the loser falls through to proximity, which now also excludes already-claimed targets so it doesn't re-pick the same one. `label_inside_target_text` (when the matched target is an unlabeled container) finds the title via a wide-thin-near-edge heuristic. Returns `confidence`, `leader_kind` (`connector` / `linear_shape` / `underline` / `none`), `target_text`, and optionally `label_inside_target_text`. |
| `find_shapes(text_pattern, page=None, regex=False, fields=None)` | Search shapes by text content (case-insensitive substring or regex). Pass `fields=['page','shape_id','text']` to drop bbox when geometry isn't needed. |
| `render_page(page, annotate_ids=False, dpi=150, bbox=None)` | PNG of the page (or region) via `Page.Export`. `bbox` (in inches, Visio coords) crops to a sub-region — token cost scales with pixel area, so a 2"×2" crop is ~4% of a full letter page. Default DPI dropped from 200→150 (Arial 12pt still renders ~25px tall, well above Claude vision's reliable-read floor); bump to 200+ for subpixel detail like arrowhead direction. |
| `export_pdf(output_path=None, include_backgrounds=True)` | Whole-doc PDF via `Document.ExportAsFixedFormat`. |
**Page setup**
| Tool | What it does |
| --- | --- |
| `new_page(name, width_in, height_in, background_page, insert_after)` | Create a page; optionally assign a background and slot it before the BG-* pages. |
| `clone_page(source_page, new_name, insert_after=None)` | Create a new empty page that copies the source's dimensions and BG reference. Defaults insertion to immediately after the source. Setup-only — does not duplicate shapes. |
| `backup_page(page, name=None)` | Snapshot a page (WITH all its shapes) to a timestamped backup (`'<source> — backup YYYY-MM-DD HH-MM-SS'` by default). Standalone version of the auto-backup `delete_shape` triggers when bulk-delete confirmation fires; use it before any non-trivial edit sequence as a manual rollback point. Duplicates shapes via `Page.CreateSelection + Selection.Copy + Page.Paste` — the only cross-page shape-copy path COM exposes. Side effect: overwrites the user's Visio clipboard. Distinct from `clone_page`, which is intentionally empty (use `clone_page` to start a new figure with matching setup). |
**Reference numbers**
| Tool | What it does |
| --- | --- |
| `add_reference_number(page, target_shape_id, number=None, side, suffix=None)` | Number + leader. Sample-and-replicates an existing user-drawn leader if any exist; otherwise synthesizes a fresh lead line (`Page.DrawSpline` through 4 control points) so blank docs Just Work. Picks next free number from the figure's range if omitted. `suffix` (single uppercase letter A-Z) tags an additional instance of an existing element type per house style: three target devices are 130A, 130B, 130C, not 130/132/134. Auto-pick treats 130/130A/130B as one slot. Bound to target via `Data1` so `move_element_with_references` can carry it along. **Margin retry**: if the requested `side` would push the numeral past 1.84(g) margins, falls back through the other sides (right → left → top → bottom) and returns the actual side used. **START guard**: refuses to number a shape whose text is "START" — flow-chart START terminators are conventionally unnumbered. |
| `renumber(old, new, scope, page, expected_count=None, allow_collision=False)` | Guarded find-and-replace. `expected_count` aborts on count mismatch; `allow_collision` (default False) aborts if `new` already exists in scope. |
| `move_element_with_references(page, target_shape_id, x, y, expected_bbox)` | Move a target plus its bound numeral + leader as a unit. Use this instead of `move_shape` when the target carries a reference number. |
**Shape edits** (all guarded by `expected_text` / `expected_bbox`)
| Tool | What it does |
| --- | --- |
| `set_shape_text(page, shape_id, text, expected_text, preserve_style=False)` | Change a shape's text; normalizes style to Arial regular by default. Refuses shapes that contain Visio dynamic field codes (e.g., =PAGENUMBER() / =PAGECOUNT() in BG-P / BG-L title blocks) since writing Shape.Text would clobber the fields; use `replace_in_shape_text` for those. |
| `replace_in_shape_text(page, shape_id, old_substring, new_substring, expected_count=None)` | Surgical substring replacement via Shape.Characters.Begin/End so dynamic field codes elsewhere in the shape's text stay intact. Use this for BG-P / BG-L title-block edits (file numbers, matter labels) where "Page X of Y" must keep updating. Replaces all occurrences (gated by `expected_count`); post-write verification catches the edge case where a field sits BEFORE the substring (source-text indexing diverges from rendered position) and asks you to undo + edit in Visio directly. Text is NOT auto-uppercased — title-block content may legitimately be mixed case. |
| `format_shape_text(page, shape_id, bold=None, italic=None, underline=None, font_size=None, color=None, substring=None, occurrence=None, expected_count=None)` | Character formatting on all or part of a shape's text. Style flags (bold/italic/underline) are tri-state (True on, False off, None unchanged); `font_size` (in points) and `color` ((r,g,b) tuple of 0-255 ints or '#RRGGBB' hex) set absolute values. Without `substring` formats the whole shape; with `substring` formats every match (or just the Nth via `occurrence`, with optional `expected_count` assertion). Uses Shape.Characters.Begin/End + CharProps so dynamic fields elsewhere in the text stay intact. |
| `move_shape(page, shape_id, x, y, expected_bbox=None)` | Move a shape's center to (x, y). |
| `resize_shape(page, shape_id, w, h, expected_bbox=None)` | Resize keeping the center fixed. |
| `delete_shape(page, shape_id, expected_text, confirm_bulk=False)` | Delete a shape; `expected_text` required. Three structural guards: refuses if it would empty the page (use `new_page` instead of clearing); refuses the 5th-or-later delete on the same page within 60s unless `confirm_bulk=True` (surfaces bulk wipes to the user); and when `confirm_bulk=True` is used, **auto-duplicates the page (with shapes) to a timestamped backup before proceeding** (one snapshot per delete-burst, returned in the result under `backup_page`) so a wrong-call destructive sequence stays recoverable even past Visio's 20-undo limit. |
| `unlock_shape(page, shape_id)` | Clear all `Lock*` cells so the shape can be moved/resized/edited in the Visio UI. |
**Add elements**
| Tool | What it does |
| --- | --- |
| `add_box(page, x, y, w, h, text)` | Plain black rectangle, 1.0pt outline, no fill, Arial 12pt centered text. |
| `add_rounded_box(page, x, y, w, h, text, corner_radius)` | Rectangle with rounded corners (stadium by default), used as a flow-chart start/end terminator. |
| `add_oval(page, x, y, w, h, text)` | Ellipse. |
| `add_master_shape(page, master_name, x, y, w=None, h=None, text="", stencil_name=None, apply_house_style=True, suppress_dialogs=True)` | Drop a Visio master from a stencil onto a page. Searches document masters → open stencils → explicit `stencil_name` → known patent-flowchart stencils (Basic Flowchart Shapes, Basic Shapes). `apply_house_style=True` (default) overrides theme-aware fill/strokes with 1.0pt outline + no fill + Arial 12pt centered ALL CAPS to match patent figures. `suppress_dialogs=True` (default) disables Visio events around the drop so masters with a built-in `EventDrop` wizard (e.g., 'Off-page reference') don't pop a modal dialog mid-drop; you lose the wizard's auto-hyperlinking, which patent figures don't need. |
| `add_decision(page, x, y, w=2.0, h=1.2, text="")` | Flow-chart decision diamond drawn directly via Geometry edits (4 vertices at the bbox edge midpoints). No master/stencil dependency; sidesteps the rendering issues that the live Decision master had with theme overlays. Style matches `add_box`. |
| `add_off_sheet_connector(page, x, y, w=1.0, h=0.5, text="", point_direction="right")` | Static pentagonal off-sheet connector — rectangle reshaped to a 5-vertex pentagon via Geometry edits. No master/stencil dependency, no `EventDrop` wizard to defuse, so always silent. `point_direction`: `right` / `left` / `up` / `down`. Style matches `add_box`. |
| `add_text(page, text, x, y, anchor, style, width, rotation_deg)` | Freeform text-only; `style="title"` is Arial 24pt for figure titles. |
| `add_connector(page, from_shape_id, to_shape_id, arrow=True, line_style="solid", routing="auto", from_anchor=None, to_anchor=None)` | Edge-to-edge line between two shapes. **Endpoints**: `from_anchor` / `to_anchor` (`top-left | top | top-right | right | bottom-right | bottom | bottom-left | left | center`) override the auto-router's edge-midpoint pick — use them for clean fan-from-corner geometry. **Routing**: `auto` (existing behavior — Y-overlap → straight horizontal hop; vertically stacked → orthogonal U-shape with bus-tap "down-across-down" pattern that lets a peripheral sitting under the hub still tap the same horizontal bus the off-axis peripherals form), `straight` (direct line), `diagonal` (direct line with corner-to-corner endpoints picked by relative position; convention for breakout-fan diagrams), `orthogonal` (forces U-shape even when endpoints align). **`line_style`**: solid / dashed / dotted / dash-dot — patent figures use dashed lines for non-load-bearing references and "see other figure" indicators. **No inline labels**: text is never applied to connector lines (Visio rotates it to the line angle, which reads upside down on right-to-left arrows) — place any label as a separate `add_text` shape near the line. |
| `set_connector_style(page, shape_id, line_style=None, weight_pt=None, arrow=None)` | Update style on an existing connector without re-adding (which would renumber the shape and break references). Only the params supplied are touched. |
| `add_flow_label(page, target_shape_id, number, text_x, text_y)` | Routine-label number for an entire flow chart (the figure's base number, e.g. "200" for FIG. 2), with a curved arrow pointing at the target. Sample-and-replicates an existing curved arrow's geometry. |
| `build_flow_chart(page, steps, fig_number=None, ..., center=True)` | High-level skeleton builder. Takes a list of step descriptions and produces stadium START + step boxes + stadium END + connectors + reference numerals + FIG. X title in one call. Bakes in conventions: terminator width matches step boxes, START unnumbered, ALL CAPS, **vertical centering inside the USPTO drawing area** (default; pass `center=False` for the legacy top-anchored layout), cold-start safe. |
| `add_decision_branch(page, decision_id, yes_step, no_step, yes_label="YES", no_label="NO", box_width=2.5, box_height=0.7, v_gap=0.6, h_split=1.5)` | Add YES + NO step boxes under an existing decision diamond, with two straight connectors fanning from the diamond's bottom vertex and two free-floating Y/N text labels placed between the diamond and the step boxes. Labels are NOT on the connector lines (patent figures keep YES/NO horizontal regardless of connector angle). Scope is one decision with two outcomes — compose multiple calls for multi-level trees and route loop-back edges with `add_connector` directly. |
**Composition**
| Tool | What it does |
| --- | --- |
| `align_shapes(page, shape_ids, edge)` | Align to left/right/top/bottom/center-x/center-y. |
| `distribute_shapes(page, shape_ids, axis)` | Evenly space horizontal / vertical. |
**Document**
| Tool | What it does |
| --- | --- |
| `save()` | Save (with one retry on shared-folder sync conflicts). |
| `save_as(path)` | Save to a new path. |
| `undo()` | Visio's native undo: one tool call's worth per call (atomic via `BeginUndoScope`). |
## House style
Default style is built around USPTO drawing requirements (37 CFR 1.84) plus stricter house overrides:
- Reference characters in Arial 12pt (≥ 1/8" minimum).
- **All text uppercased.** Uppercase letter-height meets 1.84(p)(3) at smaller point sizes than lowercase x-height. Tool-enforced on every text setter.
- Lead lines: **1.0pt** black, no arrowhead, never crossing. (1.84(q) permits 0.5pt minimum; house style uses 1.0pt for visibility at typical USPTO PDF render DPIs.)
- Connectors and shape outlines: **1.0pt**. Everything that's a stroke is the same weight for visual consistency.
- Default boxes: plain black rectangle, 1.0pt outline, no fill.
- Placement snaps to a 0.01" grid (tight enough to avoid centering drift between different-width shapes that share a center).
- Reference numbers start at 100 and step by 2; the first digit(s) encode the figure number (FIG. 1 → 100, 102, …; FIG. 10 → 1000, 1002, …).
- Multiple instances of the same element type share a base number with a unique uppercase-letter suffix: three target devices are 130A, 130B, 130C, not 130/132/134.
- Placements outside USPTO 37 CFR 1.84(g) margins (top 2.5cm, left 2.5cm, right 1.5cm, bottom 1.0cm) are refused at the tool level. BG-P / BG-L are exempt (they are the margin frames).
- In flow charts, START is conventionally unnumbered; numbers begin at `fig_base + 2` for the first step.
See [`Visio MCP Tool Plan.md`](./Visio%20MCP%20Tool%20Plan.md) for the full style guide and tool-surface design.
## Future work
Things planned or partially designed but not yet built. Issues and PRs welcome on any of these.
Roughly ordered by importance — items at the top address active agent pain points or USPTO-compliance risk; items toward the bottom are speculative, narrow, or depend on earlier items.
**Tools and behavior**
1. **Stricter line-weight enforcement.** Refuse strokes < 1.0pt at the tool level (`set_connector_style`, `_apply_line_style`, `add_master_shape(apply_house_style=False)` paths). House style mandates a 1.0pt floor; thinner strokes have shipped from agents and would violate USPTO 37 CFR 1.84(q) at typical render DPIs.
2. **`waypoints=[(x, y), ...]` on `add_connector`.** Explicit intermediate points so the agent can route lines around obstacles. Composable with the existing `routing` modes (no waypoints → existing auto/straight/orthogonal/diagonal logic).
3. **`glued=True` on `add_connector`.** Drop a Visio Dynamic Connector master and glue endpoints to the from/to shapes; Visio's built-in router handles obstacle avoidance, and connectors follow shapes when they move. Opt-in; default stays static so existing predictable geometry is preserved. House-style overrides (1.0pt black, no theme effects) applied automatically.
4. **Diagonal-routing guard on `add_connector`.** Refuse `routing="diagonal"` between two non-decision shapes — sequential flow uses `auto`/`orthogonal`; `diagonal` is for fan-from-decision layouts only. Documentation alone hasn't stopped the misuse.
5. **Page fingerprint guards.** `expected_shape_count` (and possibly `expected_shape_ids`) parameter on bulk-modifying tools — same pattern as `expected_text` / `expected_bbox` applied at the page level. Aborts when the page has changed since the agent's last read; catches drift before a destructive op compounds the problem.
6. **`replace_shape_with_master`.** Swap a shape's type (rectangle → diamond, off-sheet connector, etc.) preserving text, bbox, and reference-numeral bindings (Data1). Lets the agent revise flowcharts iteratively instead of redrawing entire pages.
7. **`add_continuation_marker(from_page, from_step_number, to_page)`.** Multi-page flow chart helper — `add_off_sheet_connector` plus auto-numbered cross-references ("CONT'D ON FIG. 5" / "RETURN TO FIG. 4 OP. 412") in one call. Composable from existing primitives today; promote to a tool if/when the convention proves recurring.
8. **Auto-render after writes.** Every write tool was originally specced to auto-return a `render_page(bbox=affected_area)` PNG for visual confirmation. Currently writes return structured data only; verification requires a separate `render_page` call.
9. **Cross-sub-figure number-uniqueness check.** `add_reference_number(number=None)` only checks the current page. For sub-figure series sharing a range (e.g., FIG. 1A–1D all use 1xx), it should check all matching pages.
10. **`delete_page`** — companion to `new_page`.
11. **Per-page PDF export.** `export_pdf` covers the whole document only.
12. **Squiggle tightness knob** on `add_reference_number`. The sample-and-replicate path can distort at extreme leader aspect ratios.
13. **Self-bootstrapping figure registry.** A per-matter map of figures, reference numerals, element names, and source-spec terms, built from the live Visio document, not an external file. Foundational for the slash commands below.
14. **Hybrid `.vsdx` direct read access (secondary path).** COM-against-live-Visio stays the canonical write path. But for *file-at-rest* workflows that don't need live state (building the figure registry without requiring Visio to be open, batch-linting `.vsdx` files in CI, cross-document audits across archived matters), direct XML parsing of `.vsdx` would be faster and wouldn't require Visio to be running. Risk is complexity (two access paths invite confusion about which sees what), so introduce only when a concrete file-at-rest workflow needs it. Reads only; writes via `.vsdx` remain out of scope (master/theme/connection-point fragility).
15. **`/check-figures` slash command.** Pre-filing QC for 37 CFR 1.84 compliance (margins, font sizes ≥ 1/8", line weights, leader non-crossing) plus cross-document consistency (every spec number drawn somewhere; every drawn number defined in spec). Depends on the figure registry.
16. **`/draft-description-of-drawings` slash command.** Synthesize the spec's "Description of Drawings" prose from what's actually in the figures. Depends on the figure registry.
**Distribution / packaging**
- `install-windows.ps1` for Windows-only users (no Mac / Parallels). The server and tray app are already Windows-native; this just registers the MCP server with Claude Code on the same Windows machine and copies the slash commands.
- Pre-built `.exe` bundle via PyInstaller so users don't need Python pre-installed.
- GitHub Actions for releases.
**Explicitly out of scope** (don't build these without first re-litigating)
- ~~`add_shape_from_master`: primary user doesn't use Visio stencils.~~ Revisited 2026-04-28: shipped as `add_master_shape` (and `add_decision` wrapper) when the drafting agent needed real Decision diamonds. House-style overrides (1.0pt outline, no fill, ALL CAPS) keep dropped masters USPTO-compliant.
- `draw_graph` with Visio's built-in auto-layout (hierarchical / tree / radial / compact-tree). Produces compositions that fight patent-drawing conventions. Not even as a starting point.
- Wrapping the server as a Windows service: it's an interactive on-demand thing, not a background daemon.
- Multi-user / authenticated MCP access: the architecture assumes single-user single-machine.
- Direct `.vsdx` file-level **writes**: master / theme / connection-point fragility makes hand-editing the XML risky. COM is the write path. (Read-only `.vsdx` access for file-at-rest workflows is *not* out of scope; see Future work above.)
## Security
The server is **unauthenticated** — anything that can reach `http://<host>:8765/mcp` can drive Visio via COM. By default it binds to `0.0.0.0` (all interfaces) so the Mac can reach it across the Parallels network, which means any other machine on the same network can reach it too. This is a deliberate single-user, single-machine design; run it accordingly:
- Keep port 8765 on a network you trust (a Parallels host-only / shared network, or localhost) — never port-forward it to the public internet.
- For a Windows-native setup (Claude and Visio on the same box), launch the server directly with `python server/server.py --host 127.0.0.1` to bind loopback only, so no other machine can reach it. (The tray app binds all interfaces by default.)
Multi-user / authenticated access is explicitly out of scope.
## Troubleshooting
**Tray app shows "Stopped" right after launch.**
Open View Logs from the tray menu. Most common causes: (a) Visio isn't running in the VM, (b) port 8765 is already in use, (c) Windows firewall is blocking inbound connections.
**Mac says `Failed to connect`.**
Confirm the tray icon is green. Then in an elevated PowerShell:
`New-NetFirewallRule -DisplayName "Visio MCP" -Direction Inbound -LocalPort 8765 -Protocol TCP -Action Allow`
**`install-mac.sh` can't find the VM IP.**
Open the tray menu in the VM, click *Copy connection URL*, paste the IP into the script: `./scripts/install-mac.sh <VM_IP>`.
**Different port.**
Set the port in `server/tray.py` (`DEFAULT_PORT`); currently not configurable from the tray menu. Pass the matching port to `install-mac.sh`.
## Architecture and decisions
`Visio MCP Tool Plan.md` is the committed design spec. It captures what's built, what's planned, the cross-cutting write-tool contract (pre-read, `expected_*` assertions, atomic undo scopes, auto-render), and rejected alternatives that shouldn't be re-litigated without cause.
## Contributing
Issues and PRs welcome. A few things to know before contributing:
- Read the plan doc before adding tools. The tool surface is opinionated.
- Specialized tools must do work beyond their lower-level equivalents. `add_reference_number` earns its keep (leader routing, number selection, style); a `set_figure_label` does not (just `add_text` with hardcoded position). When in doubt, expand `add_text` rather than adding a new tool.
- Every write tool must re-read live state via COM before mutating, and destructive writes (`delete`, `renumber`, text changes, large moves) must accept and verify `expected_*` assertions.
## License
Copyright © 2026 Leonard Hope. Licensed under the [MIT License](./LICENSE) — free to use, modify, and redistribute, including for commercial purposes, with attribution.
Connection Info
You Might Also Like
buddy
Your persistent AI coding companion — the /buddy rescue mission. A...
Vera
Local code search combining BM25, vector similarity, and cross-encoder...
agent-base
Agent Base is a source-level research project on coding agents. It compares...
mitmproxy-mcp
MCP Server that wraps mitmproxy and exposes it as a tool to any MCP client,...
nothumanallowed
NotHumanAllowed — AI Agent Tools, CLI, Documentation & MCP Integration
claude-view
10 Claude sessions running. What are they doing? Live dashboard — monitor,...