No DOM · No accessibility tree · No browser surface

Give your coding agent
eyes on macOS.

Plumshot lets AI agents capture and annotate native macOS windows, menus, sheets, and the menu bar — UI that has no DOM and no accessibility tree, so Playwright and browser tools can't see it. It runs locally next to your agent: no account, no token, no upload.

macOS 14 Sonoma or laterScreen Recording permissionNode ≥ 18100% on-device

MyApp — Preferencescapture_window → annotate

What your agent hands back — a marked-up PNG, not a paragraph of coordinates.

Screenshots become a conversation

Loop A

The agent shows you

Your agent grabs a window, draws a red box and an arrow, numbers the problem, and hands back a marked-up PNG — instead of a paragraph of pixel coordinates. Small captures and every annotation come back inline as a base64 PNG, so a vision agent sees the result in a single call.

capture_window → annotate → show the human
Loop B

You show the agent

Mark up a screenshot yourself — in Plumshot, Preview, anything — and your agent just reads the marks. Your circles, arrows, and notes are the instructions. No capture, no permission needed.

you annotate → agent reads the PNG → agent acts

Pick how you drive it

Headless CLI

plumshot …

Best when you're driving from a shell or a non-MCP script. Every capture prints JSON: { path, width, height, bbox }. The CLI is the signed Plumshot.app binary.

stdio MCP server

Default for MCP clients
plumshot-mcp

A zero-dependency stdio server (server.mjs, Node ≥ 18). Best inside Claude Code, Codex, or Cursor. Small images return inline as base64 PNG; large ones return a path.

Claude Code skill + plugin

/plugin install plumshot

Best for the easiest install and built-in guidance on when to reach for Plumshot. One marketplace command brings the MCP server and the skill in together.

No hosted URL, no network, no API key — the inverse of every cloud MCP. Your captures never leave your Mac.

Give your agent eyes in 4 steps

The CLI is the signed app binary, so you build the app once, then wire any client to it.

  1. Build the app

    Builds the Plumshot scheme with Apple Development signing — so the Screen Recording grant sticks across rebuilds.

    ./scripts/run_dev.sh
  2. Put the CLI on PATH

    ln -sf "$PWD/scripts/plumshot" /usr/local/bin/plumshot
  3. Grant Screen Recording

    System Settings ▸ Privacy & Security ▸ Screen Recording → enable Plumshot → relaunch. annotate needs no permission.

  4. Verify

    plumshot doctor
      screen recording (TCC): granted ✓
      accessibility (AX):     not granted (optional)
      bundle id:              com.plumshot.app
      binary:                 …/Plumshot.app/Contents/MacOS/Plumshot

    One more smoke test before wiring a client: capture a window and list targets.

    plumshot capture-window --app Finder --out /tmp/cap.png
    plumshot list-targets

Wire it into your client

Jump to your client. Every block ends with the same verify ritual: run plumshot doctor.

Claude Code

The easy button — install the plugin (brings the MCP server and the skill together):

/plugin marketplace add .
/plugin install plumshot

Or register the MCP server yourself — a .mcp.json at the repo root:

{
  "mcpServers": {
    "plumshot": {
      "command": "node",
      "args": ["mcp/plumshot-mcp/server.mjs"]
    }
  }
}

…or one line from the CLI:

claude mcp add plumshot -- node mcp/plumshot-mcp/server.mjs

Then run plumshot doctor (or the doctor tool) to confirm the CLI resolves and Screen Recording is granted.

OpenAI Codex

Add the server to ~/.codex/config.toml with an absolute path to server.mjs:

[mcp_servers.plumshot]
command = "node"
args = ["/Users/you/Documents/GitHub/Plumshot/mcp/plumshot-mcp/server.mjs"]

[mcp_servers.plumshot.env]
PLUMSHOT_APP = "/Applications/Plumshot.app"

The env table is optional — drop it if your dev build resolves on its own.

Then run plumshot doctor (or the doctor tool) to confirm the CLI resolves and Screen Recording is granted.

Cursor

Add the server to .cursor/mcp.json (project) or ~/.cursor/mcp.json (global), absolute path:

{
  "mcpServers": {
    "plumshot": {
      "command": "node",
      "args": ["/Users/you/Documents/GitHub/Plumshot/mcp/plumshot-mcp/server.mjs"],
      "env": { "PLUMSHOT_APP": "/Applications/Plumshot.app" }
    }
  }
}

Then run plumshot doctor (or the doctor tool) to confirm the CLI resolves and Screen Recording is granted.

Other MCP clients

Any MCP host launches the server over stdio — point it at:

node mcp/plumshot-mcp/server.mjs

For a non-default build, set PLUMSHOT_APP (path to the .app) or PLUMSHOT_BIN (full binary path) in the host's env block.

Screen Recording is the only gate

Plumshot needs just one macOS permission — Screen Recording — for the capture tools. Not Accessibility (except scrolling auto-scroll), not Full Disk Access, and it makes no network requests.

  • Captures are local files you choose to share — nothing is uploaded. No account, no token.
  • annotate needs no permission at all — it composites onto a PNG you already have. Only the capture_* tools touch the screen.
  • Images ≤ 5 MB come back inline as base64 PNG; larger ones return a path. capture_full and capture_scrolling always return a path by design.
  • capture-scrolling's auto-scroll additionally needs Accessibility — run plumshot doctor to check, or pass --no-auto-scroll and scroll yourself.
  • Redact before you share: cover secrets with a blur shape (style blackOut for a hard cover) before surfacing an image.

The 9 MCP tools

All coordinates are top-left origin, image pixels. Every capture also prints a JSON envelope: { path, width, height, bbox }.

ToolArgumentsReturns
capture_regionx, y, w, h (required), display? (0)Inline PNG + path
capture_windowapp (required, substring), window? (title)Inline PNG + path
capture_previous_regionInline PNG + path · repeats last region
capture_fulldisplay? (under cursor)Path only (large)
capture_scrollingx, y, w, h (required), display? (0), max_seconds? (30)Path only · needs Accessibility
annotateimage (path), shapes (array or {shapes:[…]}), out?Inline PNG + path · no permission
capture_textinput? (PNG path) | x, y, w, h | app + window?; keep_linebreaks? (true)JSON { text, chars } · on-device OCR
list_targetsJSON { displays, windows }
doctorPermissions + signing status

Marks are a declarative shape spec

No mouse, no GUI — your agent authors arrows, boxes, labels, and redactions as JSON.

shapes (top-left image pixels)
{ "shapes": [
  { "type": "box",       "x": 980, "y": 60, "w": 260, "h": 96,
                         "color": "#FF3B30", "lineWidth": 3, "filled": false },
  { "type": "arrow",     "from": [880, 200], "to": [1000, 110] },
  { "type": "text",      "x": 700, "y": 210, "text": "label is clipped", "fontSize": 30 },
  { "type": "number",    "x": 1110, "y": 108, "n": 1 },
  { "type": "highlight", "x": 120, "y": 320, "w": 400, "h": 28 },
  { "type": "blur",      "x": 60, "y": 60, "w": 300, "h": 40,
                         "style": "pixelate|secureBlur|smoothBlur|blackOut" }
] }

Defaults: color #FF3B30 (red), lineWidth 3, text fontSize 28, number marker 36, blur pixelate. Pipe the spec via --shapes - (stdin) or --shapes file.json. On Retina, use the pixel numbers Plumshot prints — they are already device pixels.

What to actually say

The fastest way to feel the payoff — these map directly onto the two loops.

Loop A — the agent shows you

  • Capture the Preferences window of MyApp and tell me if the Save button label is clipped.
  • Screenshot the Settings sheet, draw a red box around the broken label with an arrow to it, number it 1, and show me.
  • Take a scrolling capture of this list and check that the bottom rows render.

Loop B — you show the agent

  • Read ~/shot.png — I drew arrows on what to fix. Do what they point at.
  • Here's a screenshot I marked up; what does box 1 point at?

Priming

  • Run plumshot doctor and list-targets, then capture the front window of MyApp.

When to reach for Plumshot

Use Plumshot for

  • Native AppKit/SwiftUI windows
  • NSMenu and the menu bar
  • Sheets, alerts, popovers
  • Custom-drawn views & canvases
  • Drag previews and traffic-light spacing
  • Anything with no reliable accessibility tree

Reach for something else for

  • Web UI → Playwright / a browser MCP (you get DOM, console, network)
  • Pure text extraction → capture_text reads the words directly (on-device OCR) — cheaper than shipping pixels to a vision model
  • Headless CI with no display or Screen Recording grant
  • Sub-pixel diffing → capture, then diff with an image tool

Native-macOS failure modes

When in doubt, re-run plumshot doctor — it's the universal did-it-work check.

Built for agents to read, too

Server-rendered, with machine-readable twins an agent can fetch directly.

Agent index

/llms.txt

A curated, terse index of the three surfaces, the two loops, and instructions for when to reach for Plumshot — per the llms.txt convention.

Markdown twin

/agents.md

This page as plain markdown — so an agent that fetches it gets parseable config and commands, not rendered HTML.

The Claude Code skill (plugins/plumshot/skills/plumshot/SKILL.md) ships the same "when to use Plumshot" guidance you can drop into an AGENTS.md or .cursor/rules.

Point your agent at the pixels.

Build it once, wire your client in a minute, and let your agent see the native UI no browser tool can reach.

Local-onlyNo accountNo tokenNode ≥ 18