Plumshot iconPlumshot
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.

Connect your agentSee the 9 tools

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

MyApp — Preferencescapture_window → annotate
Save chang…
1Save label is clipped

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

Two directions

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

Three surfaces

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 serverDefault 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.

Quickstart

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.

shell
./scripts/run_dev.sh
2

Put the CLI on PATH

shell
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
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.

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

Connect your agent

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):

claude code
/plugin marketplace add .
/plugin install plumshot

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

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

…or one line from the CLI:

shell
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:

~/.codex/config.toml
[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:

.cursor/mcp.json
{
  "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:

shell
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.

Permissions & privacy

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.

Tool catalog

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? (false)JSON { text, chars } · on-device OCR
list_targetsJSON { displays, windows }
doctorPermissions + signing status

Annotate from JSON

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.

Example prompts

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.

Positioning

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

Troubleshooting

Native-macOS failure modes

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

plumshot: command not found

Symlink the shim onto PATH: ln -sf "$PWD/scripts/plumshot" /usr/local/bin/plumshot.

doctor says Screen Recording NOT granted

Grant it and relaunch. Rebuilding with ad-hoc signing drops the grant — rebuild with Apple Development signing (scripts/run_dev.sh) so it sticks.

Captures are black / empty

The classic missing-Screen-Recording symptom even when the call "succeeds." Confirm with plumshot doctor; make sure the target is on a logged-in GUI session.

Could not find the Plumshot.app binary

Build the app, or set PLUMSHOT_APP / PLUMSHOT_BIN. The shim's error lists every path it tried.

capture-scrolling doesn't scroll

Auto-scroll needs Accessibility. Grant it, or pass --no-auto-scroll and scroll the target yourself.

node not found when the host launches the server

Node ≥ 18 must be on the host's PATH. With a trimmed environment, give an absolute path to node in the command field.

Self-ingest

Built for agents to read, too

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

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.

Connect your agent/llms.txt for agents

Local-onlyNo accountNo tokenNode ≥ 18