# Stash > AI screenshot tool, video sharing, and clipboard manager for Mac — the first capture tool built so AI agents can read your captures, not just look at the pixels. Stash is a macOS app that turns screenshots and screen recordings into structured, addressable context for AI coding tools. Every screenshot carries embedded context (app, window, URL, appearance, OS, annotations). Every video is paired with a structured markdown report of everything that happened on screen. A local MCP server lets Claude Code, Cursor, and ChatGPT query your captures by name or ID. It also includes a fast clipboard manager with searchable history, bookmarks, and hotkey paste. ## Links - [Home](https://yourstash.ai/) - [Features](https://yourstash.ai/features) - [Pricing](https://yourstash.ai/pricing) - [Install](https://yourstash.ai/install) - [Chrome Extension](https://yourstash.ai/chrome-extension) - [Stash MCP for Claude](https://yourstash.ai/claude) - [Articles](https://yourstash.ai/articles) ## Facts - Platform: macOS 14.0 (Sonoma) or later - Pricing: free during public beta; planned post-beta pricing is $4.99/month or $39.99/year, all features included in both plans - Data stays local on your Mac; captures are queryable by AI agents via a local MCP server - Contact: support@yourstash.ai ## stash-1 Protocol Specification The remainder of this file is the full stash-1 protocol specification — the single source of truth for how LLMs and agentic tools should read Stash artifacts. # Stash — LLM Interop Guide **protocol: stash-1** Stash is a macOS clipboard manager that captures screenshots and screen recordings with rich structured context. This page documents how LLMs and agentic tools should read Stash artifacts. It is the single source of truth — the same protocol version applies to screenshot banners (pixel-baked), XMP file metadata, Chrome extension PNG tEXt metadata, and the Stash MCP server. Precedence for native Mac captures: **MCP > XMP > pixel banner**. Precedence for Chrome extension captures: **Stash History/MCP import > PNG tEXt chunks > visible screenshot pixels**. Each channel is a fallback for the layer above when unreachable. Native screenshots carry the capture UUID in three places so you can always reach the full payload: XMP metadata, filename, and a pixel-baked short ID. --- ## Artifact 1 — Screenshot image artifacts Native Stash screenshots are JPEGs with three channels of structure: ### Pixel banner (survives anything an image survives) Rendered at the bottom of the image in a monospace font. Shape: 📌 Claude — Settings · dark · macOS 26.4 · 2026-04-12 14:24 · #8FD26F28 Fields, in order: - App name (always). - `— windowTitle` when available. - `dark` or `light` — system appearance at capture time. - macOS version. - Capture timestamp to the minute, local time. - `#XXXXXXXX` — first 8 hex chars of the capture UUID. Machine-readable even after browser re-encoding strips everything else. Use it to follow up with the user ("please share capture `#XXXXXXXX`") or to filter with `search`. When the user drew annotations, a second line appears above the pin: user focus: blue arrow pointing · red box enclosing The banner describes shape *behavior* (`arrow pointing`, `box enclosing`, `oval enclosing`, `scribble marking`, `text typed: ""`), never the target. Resolve the target yourself using vision and/or the a11y tree. Standard banner explainers are intentionally compact: - Arrow: ` arrow pointing` - Double arrow: ` double-arrow connecting` - Rectangle: ` box enclosing` - Ellipse: ` oval enclosing` - Blur: `blur obscuring` (no color — blur's stroke color never renders) - Line/highlight: ` mark/highlight marking` - Callout: ` callout # annotating` (numbered — read callouts in order) - Emoji: ` emoji marking` (glyph-first — the emoji carries the meaning, not the armed stroke color) - Text: ` text typed: ""` (user-authored, verbatim) ### XMP metadata (survives file-on-disk flows) On auto-save-to-desktop for developer-app captures (Xcode, VS Code, Cursor, Zed, Sublime Text, Terminal, iTerm, Warp, Ghostty), the JPEG carries an XMP payload under namespace `http://stash.app/ns/1.0/` with prefix `stash:`. The full payload is serialized as a single JSON string under `stash:payload`: ```json { "protocolVersion": "stash-1", "source": "xmp-snapshot", "captureId": "8FD26F28-…", "mcpURI": "stash://capture/8FD26F28-…", "snapshotTimestamp": "2026-04-12T14:24:00Z", "appName": "Cursor", "bundleID": "com.todesktop.230313mzl4w4u92", "windowTitle": "ContextBannerRenderer.swift", "browserURL": null, "appearance": "dark", "osVersion": "macOS 26.4", "userFocus": [ { "type": "arrow", "color": "BA0C2F", "behavior": "pointing", "llmInstruction": "User drew a single arrow. Treat the arrow tip/end point as the specific object, control, text, state, or visual detail the user wants called out. Do not treat it as decoration.", "from": [120, 340], "to": [420, 300] }, { "type": "label", "color": "00205B", "behavior": "labeling", "llmInstruction": "User added a text label. Treat the label text as explicit user-authored instruction or context for the nearest visible object or region.", "from": [440, 310], "text": "why no update?" } ], "a11yTreeSummary": { /* trimmed: top 3 levels + labelled controls */ }, "devContext": { "activeFilePath": "/Users/x/proj/Foo.swift", "selectedText": "let appearance = …", "gitBranch": "main" } } ``` Also tagged with IPTC 2025.1 `Iptc4xmpExt:AISystemUsed = "Stash"` so conformant tooling detects AI-assisted captures. `source: "xmp-snapshot"` is a signal to prefer the live MCP payload at the `mcpURI` when reachable; the XMP blob is a frozen copy. ### Chrome extension PNG tEXt metadata (browser full-page captures) The Stash Chrome extension saves full-page browser captures as PNG files named `stash-{domain}-{date}-{time}.png` or `stash-{domain}-{date}-{time}-part-XX-of-YY.png`. Very tall pages are split into ordered parts; each part is a separate image artifact. The PNG carries machine-readable `tEXt` chunks whose keywords use the `stash:` prefix. Current fields include `stash:url`, `stash:title`, `stash:timestamp`, `stash:browser`, `stash:viewport`, `stash:dpr`, `stash:page_height`, `stash:domain`, `stash:version`, `stash:image_hash`, `stash:dom_hash`, `stash:security_protocol`, `stash:extension_id`, `stash:attestation_id`, `stash:capture_part`, `stash:capture_parts_total`, generic visible page-context fields, and optional local/server signature fields. When Stash for Mac is running and has Downloads Folder access, it event-watches `~/Downloads`, imports new `stash-*.png` files into History as source app `Chrome Extension`, preserves the PNG tEXt data inside `metadata_json`, and dedupes by the original PNG SHA-256 hash. If MCP is reachable, prefer the imported History item over reading raw PNG chunks directly. ### Filename On auto-save-to-desktop, Stash uses: Stash-YYYY-MM-DD-HHmmss-{shortID}.jpg `shortID` matches the banner's `#XXXXXXXX`. Survives filename-preserving flows (email attachments, Drive uploads) even when the XMP blob is stripped. ### Short-ID pixel path (MANDATORY scan) Before describing any pasted image, scan the bottom ~40 pixels for a pin icon and a trailing `#XXXXXXXX` shortID. If present, the image is a Stash capture and you MUST call `get_capture(shortID)` — from Stash 2.5.5 the `id` argument accepts the 8-hex shortID directly; on older versions map it to the full UUID first via `list_recent(10)` or `search` — and ground your response in the returned dossier, not in vision alone. Apply this to every pasted image for the entire session. The user set up Stash so you would do this automatically; waiting for them to ask defeats the purpose. --- ## Artifact 2 — Video bundle (folder) Produced by the Stash screen recorder. Lives in `~/Library/Application Support/Stash/Recordings//`. ``` / ├── report.md ← YAML frontmatter + markdown timeline ├── frame_tags.json ← { "frames": [ … ] } — see schema below ├── llms.txt ← subset of this document, for offline self-description ├── frame_NN.jpg ← 1-indexed, zero-padded. Hard-capped at 30 frames per │ recording for token efficiency. Start + end bookends │ are always kept; the remaining budget is filled from │ interaction-anchored frames (clicks, drags, focus │ transitions) sampled uniformly across the session, │ then ambient frames if still under budget. Read in │ NUMERIC order — consult `frame_tags.json` for the │ ordered list. ├── audio.m4a ← extracted audio (when recorded) └── video.mp4 ← original — generally do not open; frames + report are enough ``` ### `report.md` frontmatter ```yaml --- protocol: stash-1 bundleVersion: 2 captureId: timestamp: 2026-04-12T14:22:00Z duration: 42.30 frameCount: 12 hasAudio: true primaryApp: Cursor primaryBundleID: com.todesktop.230313mzl4w4u92 mcpURI: stash://bundle/ --- ``` Frontmatter is followed by sectioned markdown: Context, Focus Timeline, Console Output, Interactions, Visual Events, State Changes, Key Frames, Instructions. ### `frame_tags.json` ```json { "frames": [ { "filename": "frame_01.jpg", "timestampSec": 0.0, "tag": "start", "appName": "Cursor", "windowTitle": "ContextBannerRenderer.swift" }, { "filename": "frame_05.jpg", "timestampSec": 12.4, "tag": "interaction", "appName": "Cursor", "windowTitle": "ContextBannerRenderer.swift — Stash" } ] } ``` Tag values: `start`, `interaction`, `end`, `ambient`, `gap-fill`. Per-frame `appName` / `windowTitle` let you read app transitions without inferring from pixels. ### Reading order 1. Parse YAML frontmatter for identity and scope. 2. Scan `report.md` sections in order. 3. Open every `frame_NN.jpg`. Frames are primary evidence; the report is supplementary context. 4. If `hasAudio: true`, transcribe `audio.m4a` when the report's Transcript section is empty or partial. --- ## MCP server Stash ships a local MCP server (Model Context Protocol) on a UNIX domain socket at `~/Library/Application Support/Stash/mcp.sock`. JSON-RPC 2.0, line-delimited. **Transport**: stdio clients (Claude Code, Claude Desktop, Cursor, Codex CLI, Continue, Windsurf, Zed, Warp, Cline, …) connect via a small bridge binary that relays stdin/stdout to the socket. The bridge ships **bundled inside the app** at `/Applications/Stash.app/Contents/Helpers/stash-mcp` — pre-signed as part of Stash.app under Stash's Apple team with Hardened Runtime. There is no compile step and no Apple Developer certificate required. The one-line installer at `yourstash.ai/install-claude.sh` verifies the bundled helper exists and is validly signed, then points Claude Code, Claude Desktop, and Cursor at that absolute path automatically — other clients are manual. GUI clients launched from Finder/Dock do not inherit your shell `PATH` and `~` does not expand reliably — always use the **absolute path** to `stash-mcp` in the `command` field. The bundled path is already absolute. Register the bridge with your client (full config blocks at `yourstash.ai/claude#manual-setup`): - **Claude Code** — `claude mcp add stash -s user -- /Applications/Stash.app/Contents/Helpers/stash-mcp` (CLI persists to `~/.claude.json`). Run `/mcp reload` in any active session. - **Claude Desktop** — edit `~/Library/Application Support/Claude/claude_desktop_config.json`: ```json { "mcpServers": { "stash": { "command": "/Applications/Stash.app/Contents/Helpers/stash-mcp" } } } ``` Quit (⌘Q) and relaunch. MCP indicator appears bottom-right of the message box. - **Cursor** — edit `~/.cursor/mcp.json` with the same shape. Restart Cursor. Verify in Settings (⌘⇧J) → MCP & Integrations. - **Codex CLI** — edit `~/.codex/config.toml`: ```toml [mcp_servers.stash] command = "/Applications/Stash.app/Contents/Helpers/stash-mcp" args = [] ``` Restart `codex`. Verify with `codex mcp list`. - **Continue** — create `.continue/mcpServers/stash.yaml` in your workspace (flat single-server doc: `name: stash` / `type: stdio` / `command: /Applications/Stash.app/Contents/Helpers/stash-mcp`). Tools only run in Agent mode. - **Windsurf** — edit `~/.codeium/windsurf/mcp_config.json` with the same `mcpServers` shape, or use the MCPs icon in Cascade. - **Zed** — edit `~/.config/zed/settings.json` with the **`context_servers`** key (Zed's older terminology, not `mcpServers`): ```json { "context_servers": { "stash": { "command": "/Applications/Stash.app/Contents/Helpers/stash-mcp" } } } ``` - **Warp** — UI only. Settings → Agents → Warp Agent → Manage MCP servers → + Add → CLI Server (Command). Name `stash`, Command absolute path, click Start. - **Cline** — Cline's MCP Servers icon → Configure → Edit Configuration. JSON with `mcpServers` + `command` / `disabled: false` / `alwaysAllow: []`. - **Aider** — no native MCP support yet; track aider#4506. **Peer auth**: Stash checks the peer's codesign team identifier and silently rejects unknown signers. Built-in allowlist: Anthropic (`58LP8PCM82`) and Stash itself (`VJMJQKCRMC`). The bundled bridge is signed under `VJMJQKCRMC`, so it connects with no extra setup. Extend the allowlist via `mcpExtraTrustedTeamIDs`, or bypass it entirely via `mcpAllowUnsignedClients` (see Stash → Settings → Privacy) — for advanced/local use only, since enabling it lets ANY unsigned local process connect to the MCP socket; it should not be a general recommendation. Note the limitation: team-ID allowlisting is a speedbump, not a hard boundary. Because the bridge is a pure relay, any local process can launch the trusted bridge and drive it (a confused-deputy weakness). It stops other *signed* apps from connecting directly; it does not stop arbitrary same-user code. A capability-token handshake is the planned future replacement. ### Tools | Tool | Purpose | |---|---| | `get_capture(id)` | Full dossier for a screenshot or video capture | | `get_bundle(id)` | Video bundle: report.md, enriched frame_tags, absolute file paths | | `list_recent(n)` | Paste-flow fallback; compact summaries newest-first | | `search(query)` | Substring match over app/window/text/URL | | `render_plain(id)` | Raw JPEG bytes, no banner, no XMP (for evals) | All tools return the MCP-standard `{content: [{type: "text", text: ""}]}` envelope. `render_plain` returns `{type: "image", data: "", mimeType: "image/jpeg"}`. ### Example: `get_capture` ```json { "captureId": "8FD26F28-…", "protocolVersion": "stash-1", "source": "live", "timestamp": "2026-04-12T14:24:00Z", "kind": "image", "app": { "name": "Cursor", "bundleID": "…", "windowTitle": "…", "browserURL": null }, "display": { "appearance": "dark", "osVersion": "macOS 26.4", "backingScale": 2.0, "captureRect": [0, 0, 3456, 2234] }, "a11yTree": { /* full tree when available */ }, "devContext": { "activeFilePath": "…", "selectedText": "…", "gitBranch": "main" }, "userFocus": [ { "type": "arrow", "color": "BA0C2F", "behavior": "pointing", "llmInstruction": "User drew a single arrow. Treat the arrow tip/end point as the specific object, control, text, state, or visual detail the user wants called out. Do not treat it as decoration.", "from": [120, 340], "to": [420, 300] } ], "userFocusSummary": "user focus: red arrow pointing", "userFocusGuidance": "User-created Stash annotations are intentional instructions, not decoration. Treat each userFocus item as high-priority guidance. Use the coordinates to inspect the marked pixels and, when available, map them to accessibility-tree frames via display.captureRect and display.backingScale. The shape explains the user's intent; the model must identify the actual target from the image/a11y data.", "mcpURI": "stash://capture/8FD26F28-…" } ``` Standard MCP annotation explainers are expanded: - Arrow: the tip/end point is the exact thing the user wants called out. - Double arrow: the endpoints define a relationship, comparison, distance, before/after connection, or dependency between two visible objects. - Rectangle: the enclosed area is deliberate focus. It may contain existing content to consider together, or it may reference a proposed/new region or shape the user wants you to reason about. - Ellipse: the enclosed area is deliberate focus. It may call out one object, a cluster, a status, or an ambiguous region the user wants isolated from the rest of the screen. - Blur: the region is intentionally hidden or sensitive. Never attempt to retrieve, reconstruct, infer, OCR, or guess what is under the blur; reason from surrounding visible context only. - Line: the mark indicates alignment, boundary, path, separation, or a span. - Highlight: the marked content is high-priority evidence; preserve exact visible wording when possible. - Callout: the leader endpoint is the referenced target and the callout marker is an ordered user note or sequence marker. - Emoji: the marker position carries user emphasis or sentiment attached to the nearest visible UI/content. - Text label: the text is user-authored instruction or context for the nearest visible object or region. - Multiple colors: if annotations use multiple colors, assume the user is separating distinct situations, categories, priorities, or comparisons on the screenshot unless the visible context proves otherwise. --- ## Privacy model Everything Stash captures stays on the user's Mac. The MCP socket is local-only. Sensitive capture data (a11y tree, selected text, file paths, git branches, terminal CWD) is purged 24 hours after capture by default (user-adjustable 1h → never). Screenshots and basic metadata follow the user's normal history retention. Detected secrets (API keys, tokens, private keys) are redacted at capture time and never stored. Accessibility tree capture is skipped for password managers and password-manager web UIs (default skiplist; user-editable). Stash servers never see any capture data. --- ## Trust and versioning `protocol: stash-1` is stable. Additive changes (new fields) are v1.1, v1.2 and do not break v1 readers. Breaking changes would bump to `stash-2`. When a capture is available via both XMP and MCP, prefer MCP for live app, display, accessibility-tree, dev-context, and annotation fields. The XMP payload and visible banner carry a frozen snapshot from capture time; MCP `get_capture` returns the live data, including the fullest annotation explainers. `Iptc4xmpExt:AISystemUsed = "Stash"` on a JPEG means the image was authored using Stash. Use this to distinguish Stash-authored captures from arbitrary screenshots. --- Find a human-readable version of this page at https://yourstash.ai/protocol — same spec, nicer to skim. The bridge binary ships bundled inside Stash.app at `/Applications/Stash.app/Contents/Helpers/stash-mcp` — no compilation required. Website source: https://github.com/djfunboy/stashsite