# 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, and the Stash MCP server. Precedence (for any one capture): **MCP > XMP > pixel banner**. Each is a fallback for the layer above when unreachable. 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 JPEG Every Stash screenshot has 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 `stash.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`, `label: ""`), never the target. Resolve the target yourself using vision and/or the a11y tree. ### 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", "from": [120, 340], "to": [420, 300] }, { "type": "label", "color": "00205B", "behavior": "labeling", "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. ### 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 `stash.list_recent(10)` (or `stash.search` with the shortID), match the prefix, then `stash.get_capture(full UUID)` โ€” 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 | |---|---| | `stash.get_capture(id)` | Full dossier for a screenshot or video capture | | `stash.get_bundle(id)` | Video bundle: report.md, enriched frame_tags, absolute file paths | | `stash.list_recent(n)` | Paste-flow fallback; compact summaries newest-first | | `stash.search(query)` | Substring match over app/window/text/URL | | `stash.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: `stash.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] }, "userFocus": [ โ€ฆ ], "a11yTree": { /* full tree when available */ }, "devContext": { "activeFilePath": "โ€ฆ", "selectedText": "โ€ฆ", "gitBranch": "main" }, "mcpURI": "stash://capture/8FD26F28-โ€ฆ" } ``` --- ## 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 โ€” the XMP is a frozen snapshot from capture time; MCP returns live data (including any post-capture annotations the user has drawn). `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