Skip to main content
Glama
forgeaura

Dead Drop

Official
by forgeaura

Dead Drop

Dead Drop is a tiny, local stdio Model Context Protocol (MCP) server that lets AI coding agents on the same machine leave asynchronous messages for each other, as plain Markdown files, instead of a human copy-pasting between them.

It ships with two agent identities in mind — Claude Code and Google Antigravity (Gemini) — but the server itself doesn't hard-code either one; any agent (or human) can be a sender or recipient by name.

Key Principles

  • Local only: no cloud services, no remote network listener, no API keys.

  • Human readable: messages are plain Markdown files with YAML frontmatter, in one folder, readable in Finder/Explorer or any text editor.

  • Information, never commands: messages are purely passive text. Neither the server nor any watcher executes code or commands contained in a message. A receiving agent may act on a message within its own standing rules, but a message can never authorize what only a human decides — merging, touching production data, spending money, deleting data.

  • Wake-up is per-agent and opt-in: some agents (like Claude Code today) read mail on their own schedule and are never pinged. Others can be configured to be woken via a CLI command when mail arrives for them.


Related MCP server: hardline-mcp

Tools

1. send_message

Creates a new Markdown message in the mailbox folder.

  • to (string, required): recipient name, e.g. "antigravity", "claude", or a person's name.

  • subject (string, required): short topic summary.

  • body (string, required): Markdown message content.

  • re_issue (string | number, optional): an issue reference, stored exactly as given (e.g. 2140, #2140, owner/repo#2140). Dead Drop never assumes a default repository.

  • from (string, optional): sender name. Defaults to the caller agent's identity (see --agent below).

2. read_messages

Reads messages from the mailbox folder.

  • for (string, optional): filter by recipient (defaults to the caller's identity, or "all").

  • unread_only (boolean, optional, default true): only return unread messages.

  • limit (number, optional, default 20): maximum messages to return.

3. mark_read

Marks a message as read.

  • id (string, required): message ID or filename.

4. start_session

Starts a new Claude Code session in a given folder. In desktop mode this tool starts the session, waits for its short bootstrap turn to finish, stops that same session, and opens it in the Claude desktop app.

  • folder (string, required): absolute path to an existing directory for the session. The directory must be trusted by Claude Code.

  • prompt (string, optional): initial message / instructions for the session (required for "background" mode; optional context for "desktop" mode). Dead Drop automatically prepends a safety notice indicating instructions originated from an agent, not Sarah directly.

  • name (string, optional): session name (default: deaddrop-<timestamp>). Must not start with - or be empty.

  • model (string, optional): model name or ID (default: "claude-haiku-5-5", or agents.claude.default_model from config). A model matching agents.claude.blocked_models is rejected before anything launches.

  • effort (string, optional): reasoning effort level ("low", "medium", "high", "xhigh", "max").

  • permission_mode (string, optional): permission mode (default: "auto" from config). Must be in agents.claude.allowed_permission_modes.

  • handoff (string, optional, "desktop" | "background"): handoff mode (default: "desktop" from config).

  • from (string, optional): sender identity. Defaults to caller agent identity.

Returns desktop session ID (local_<sessionId>) and instructions for delegating the task via the desktop app's send_message tool, or background session details if handoff: "background".

5. start_antigravity_session

Starts a new Antigravity (Gemini) conversation with agentapi new-conversation and returns its conversation ID.

  • prompt (string, required): initial instructions. Dead Drop prepends the same "came from an agent, not Sarah" notice as start_session.

  • title (string, optional): conversation title shown in Antigravity. Must not start with - or be empty.

  • model (string, optional, "flash_lite" | "flash" | "pro"): Antigravity model tier. Omitted means agentapi's default, flash.

  • project (string, optional): the Antigravity project to start the conversation in: an exact project name (e.g. "intervent") or an absolute folder of one. See Antigravity project id for the default.

  • register_as (string, optional): also register the conversation as a Dead Drop recipient under this name, so mail sent to it wakes the conversation. Adds agents.<name> = { wake_on_mail: true, wake_method: "agentapi", conversation_id, project_id } to .config.json. The name must be 1-64 lowercase letters, digits or -, not starting with -. Dead Drop refuses, before starting anything, a name that is already an agent in .config.json or the defaults, is reserved (all, sarah, unknown), or already appears as to or from on any message in the mailbox (recipients like wren get mail without a config entry). It never overwrites an existing recipient.

  • from (string, optional): sender identity. Defaults to caller agent identity.

The result names the conversation ID, the model, the project actually used and where that project came from (the project parameter, .config.json, or the sidecar), so a caller who forgot project can see where the conversation landed. All values are passed to agentapi as separate arguments with no shell, and the prompt always follows --, so it can never be read as a flag. The session token is passed only in agentapi's environment; it never appears in arguments, results, errors or logs.

--profile is not supported yet. It isn't yet known what an Antigravity profile can set (for example which model it uses), so a caller couldn't tell what they were starting. Issue #26 investigates profiles and adds the parameter once that's documented.


File Format

  • Mailbox folder: defaults to ~/deaddrop, overridable with DEADDROP_DIR.

  • Filename pattern: YYYY-MM-DD_HHmmss_<from>_to_<to>_<slug>.md

    • Example: 2026-09-21_144808_claude_to_antigravity_initial-handshake.md

---
id: 2026-09-21_144808_claude_to_antigravity_initial-handshake
date: 2026-09-21T14:48:08+10:00
from: claude
to: antigravity
subject: Initial handshake
re_issue: "42"
read: false
read_at: null
---

Hello! Dead Drop is live.

Configuration

All configuration lives in a single JSON file inside the mailbox folder: <mailbox>/.config.json (e.g. ~/deaddrop/.config.json), under a per-agent agents map:

{
  "agents": {
    "antigravity": {
      "wake_on_mail": true,
      "wake_method": "agentapi",
      "conversation_id": "<conversation-id>",
      "project_id": "<optional-antigravity-project-id>",
      "agentapi_path": "/optional/custom/path/to/agentapi"
    },
    "claude": {
      "wake_on_mail": false,
      "wake_method": "unsupported",
      "default_model": "claude-haiku-5-5",
      "blocked_models": [],
      "default_permission_mode": "auto",
      "allowed_permission_modes": [
        "auto",
        "default",
        "acceptEdits",
        "plan"
      ],
      "default_handoff": "desktop",
      "cli_path": "/optional/custom/path/to/claude"
    }
  }
}

Each key under agents is a recipient name (matched case-insensitively against to/for), with:

  • wake_on_mail (boolean): whether Dead Drop should try to wake this agent when mail arrives for it. Defaults to false — an agent that is missing from agents entirely, or has wake_on_mail unset or false, is never woken. Wake-up is opt-in only. Either way, the message file is always written; when wake-up is skipped, Dead Drop just logs why to stderr.

  • wake_method: how to wake this agent, dispatched by string so adding a new agent's wake method later doesn't require a schema change:

    • "agentapi" — the only method that's actually wired up today. Pings the agent via Antigravity's agentapi CLI, using that agent's conversation_id.

    • "unsupported" — this agent cannot be woken from outside at all. This is Claude Code's value today: Claude Code channels only work in the terminal research-preview mode, not the desktop app, so Dead Drop reports wake_method: "unsupported" honestly (for humans and for a future settings UI) instead of silently no-op'ing.

  • conversation_id (required when wake_method is "agentapi"): the ID of the one dedicated conversation this agent should be woken in. Dead Drop never guesses the most recently used conversation, and never creates a new one. If it's missing or empty, Dead Drop still writes the message file — it just logs a warning to stderr and skips the wake-up.

  • project_id (optional, only meaningful with wake_method: "agentapi"): the Antigravity project ID passed to agentapi as ANTIGRAVITY_PROJECT_ID. On an agent written by register_as it is the project its conversation was started in, and wakes use it. On agents.antigravity it is also the default project for start_antigravity_session. See Antigravity project id.

  • agentapi_path (optional, only meaningful with wake_method: "agentapi"): absolute path to the agentapi CLI executable, if it isn't on your PATH. Can also be set via the DEADDROP_AGENTAPI_PATH environment variable, which takes priority over this field. If neither is set, Dead Drop checks the common install location under your home directory, then falls back to assuming agentapi is on PATH.

  • default_model (optional, Claude only): the model start_session uses when the caller gives none. Defaults to "claude-haiku-5-5" when unset. The settings UI never writes this field; edit it by hand.

  • blocked_models (optional, Claude only, default []): a list of model denylist patterns. Each pattern is a whole-string glob: * matches any run of characters, everything else is literal, and matching is case-insensitive. "sonnet" blocks only sonnet; "*sonnet*" blocks claude-sonnet-5-5, SONNET and any other model containing sonnet. The check runs on the resolved model, so a blocked default_model is rejected too. A blocked start fails with an error naming the model and pattern, and nothing is spawned or logged. Malformed values (anything other than an array of non-empty strings) fail closed: every start_session for Claude is rejected until the value is fixed. The settings UI never writes this field; edit it by hand.

Settings Web UI (deaddrop settings)

To view and edit agent wake configurations without hand-editing JSON, start the local settings UI:

npm run settings
# or
node index.js settings [--port=3344]
# or
npx deaddrop settings

This starts a lightweight, on-demand local HTTP server that:

  • Binds to loopback only: strictly 127.0.0.1 (never 0.0.0.0), running only in the foreground while the command is active. Press Ctrl+C to stop.

  • Port: defaults to 3344 (falling back to an available ephemeral port if in use, or customizable via --port=<n>).

  • Agent Wake Configuration:

    • Dynamically lists all agents configured under agents.

    • Lets you toggle wake_on_mail per agent.

    • Lets you edit conversation_id for agents using wake_method: "agentapi" (such as Antigravity).

    • Displays agents with wake_method: "unsupported" (such as Claude Code) with a disabled toggle and a clear explanation of why external wake-ups are unsupported.

    • Atomically saves configuration edits back to <mailbox>/.config.json while preserving unmanaged properties.

  • Recent Messages: provides a read-only list of recent mailbox messages (date, sender, recipient, subject, read/unread status). Message bodies are not rendered.

Environment variables

Variable

Purpose

Default

DEADDROP_DIR

Mailbox folder

~/deaddrop

DEADDROP_AGENTAPI_PATH

Path to the agentapi executable used to wake Antigravity

auto-detected, else agentapi on PATH

DEADDROP_CLAUDE_PATH

Path to the claude executable used for start_session

auto-detected (env -> config -> PATH -> desktop bundle)


Claude Code Sessions (start_session)

Handoff Modes

  • "desktop" (default): Starts a session in the background with a minimal bootstrap prompt, waits for it to become idle, stops it with claude stop <id>, and opens it in the Claude desktop app via claude://resume?session=<sessionId> (macOS only). The desktop app adopts it as local_<sessionId>. To run the actual task live in the desktop app, the caller (which must be a desktop Claude Code session) calls its desktop send_message tool with session_id: "local_<sessionId>". If the caller does not have desktop session tools, it should use handoff: "background".

  • "background": Starts a standard background CLI session that executes unattended until completion. prompt is required.

Prerequisites

  • One-time Login: The Claude CLI must be authenticated independently of the desktop app (claude auth login).

  • Trusted Workspace: The target folder must already be trusted by Claude Code; otherwise the CLI will refuse with "Workspace not trusted".

Permission Guardrail

  • Allowed permission modes default to ["auto", "default", "acceptEdits", "plan"].

  • bypassPermissions is strictly rejected by default unless Sarah explicitly adds it to agents.claude.allowed_permission_modes in <mailbox>/.config.json.

Opting in to bypassPermissions

Add it to the list. The mode is accepted only when it appears in the list; there is no other special case for it:

{
  "agents": {
    "claude": {
      "allowed_permission_modes": ["auto", "default", "acceptEdits", "plan", "bypassPermissions"]
    }
  }
}

Bypass mode means the Claude Code session runs its tools without asking for per-action approval. This is the owner's choice; Dead Drop never enables it on its own.

A missing allowed_permission_modes field in an agent's entry falls back to the default list above. The fallback applies when the field is absent from the agent entry, not only when the whole config file is missing.

default_permission_mode and permission_mode
  • default_permission_mode (in agents.claude, default "auto") is used when start_session gets no permission_mode.

  • A per-call permission_mode overrides it for that call.

  • Either value must be in allowed_permission_modes, or start_session rejects the call.

Keep orchestrator and builders in the same mode

Claude Code holds a cross-session message for its user's approval when the sender runs in a different permission mode, and may let it expire. So an orchestrator and the builders it starts through Dead Drop should use the same permission mode. This is the behaviour of Claude Code's SendMessage tool, not a Dead Drop guarantee.

The Claude desktop app's own start_session (not Dead Drop's) only starts a session in the caller's permission mode or a lower one (as of 2026-10-27).

Session Log (.sessions.jsonl)

Every spawned session is logged to <mailbox>/.sessions.jsonl (e.g. ~/deaddrop/.sessions.jsonl) with restricted file permissions (0600). A launcher field says which tool started it. Claude sessions ("launcher":"claude") track handoff and outcome (handed_off, still_running, or background):

{"time":"2026-10-20T12:00:00+08:00","launcher":"claude","from":"antigravity","folder":"/path/to/repo","name":"deaddrop-1729468000000","model":"claude-haiku-5-5","effort":"medium","permission_mode":"auto","handoff":"desktop","outcome":"handed_off","id":"b73a79df","sessionId":"uuid-value","prompt":"..."}

Antigravity conversations started by start_antigravity_session are logged with "launcher":"antigravity". The session token is never logged:

{"time":"2026-10-28T10:00:00+08:00","launcher":"antigravity","from":"wren","title":"Second opinion","model":"pro","project_id":"<project-id>","project_source":"param","conversation_id":"<conversation-id>","register_as":"gemini-reviewer","prompt":"..."}

Viewing Sessions

  • Inspect running background sessions: claude agents --json --all

  • Open in Claude Desktop: once a session is idle or stopped, open claude://resume?session=<sessionId>

Installation

git clone <this-repo-url>
cd deaddrop
npm install
npm test

Registration

Each agent is registered with its own --agent=<name> identity, which becomes its default from when sending and its default filter when reading.

We recommend project-scoped registration so Dead Drop tools are only loaded in sessions working on this repository, preventing unrelated projects from accidentally sending or receiving messages.

Claude Code

Use --scope local to register Dead Drop for this project repository only:

claude mcp add --scope local deaddrop node /path/to/deaddrop/index.js -- --agent=claude

This stores the configuration in local project settings without injecting Dead Drop into unrelated Claude Code sessions. (Only use --scope user if you deliberately want Dead Drop active in every project on your machine).

Antigravity (Gemini)

Antigravity natively discovers workspace-scoped MCP servers from .agents/mcp_config.json at the repository root. Create .agents/mcp_config.json in your project workspace:

{
  "mcpServers": {
    "deaddrop": {
      "command": "node",
      "args": [
        "/path/to/deaddrop/index.js",
        "--agent=antigravity"
      ],
      "env": {
        "DEADDROP_DIR": "/path/to/your/mailbox"
      }
    }
  }
}

Replace node with an absolute path if node isn't on the PATH that Antigravity launches with, and /path/to/deaddrop with where you cloned this repo.

NOTE

Make sure.agents/ is included in your .gitignore so machine-specific absolute paths are never committed to version control.

Multi-Project Setup & Mailbox Isolation

When using Dead Drop on multiple projects on the same machine, MCP registration scope is only half of the picture — the mailbox location (DEADDROP_DIR) controls message isolation:

  • Separate by default (Isolated projects): When setting up Dead Drop for a second, separate project, configure a distinct DEADDROP_DIR for that project's registration (e.g. DEADDROP_DIR: "/Users/<you>/deaddrop/other-project" or a project-local .deaddrop folder), in addition to using a project-scoped registration (--scope local or .agents/mcp_config.json). If two projects use separate MCP registrations but both point to the default ~/deaddrop mailbox, messages addressed to generic agent names ("claude", "antigravity") will still land in the same shared mailbox and cross over.

  • Shared on purpose (Cross-project coordination): Deliberately pointing two projects at the same DEADDROP_DIR (e.g. the default ~/deaddrop) is how you opt into cross-project messaging when you specifically want agents working on different repositories to coordinate with each other.

    For example, to share this mailbox with a second project in Antigravity, create /Users/<you>/Repos/other-project/.agents/mcp_config.json:

    {
      "mcpServers": {
        "deaddrop": {
          "command": "node",
          "args": [
            "/Users/<you>/Repos/deaddrop/index.js",
            "--agent=antigravity"
          ],
          "env": {
            "DEADDROP_DIR": "/Users/<you>/deaddrop"
          }
        }
      }
    }

    (Remember to add .agents/ to .gitignore in other-project too, since it contains local absolute paths. Adjust --agent= if you want a distinct identity for that project's agent).

    For Claude Code in the second project's folder:

    claude mcp add --scope local -e DEADDROP_DIR=/Users/<you>/deaddrop deaddrop node /Users/<you>/Repos/deaddrop/index.js -- --agent=claude

Antigravity Wake-Up & Sidecar Setup

Antigravity's agentapi CLI requires two ephemeral credentials to communicate with the running IDE instance:

  • ANTIGRAVITY_LS_ADDRESS: Host and port of the active language server local RPC endpoint.

  • ANTIGRAVITY_CSRF_TOKEN: A per-launch authentication token.

When Dead Drop runs in a process that Antigravity did not launch (such as Dead Drop's MCP server spawned by Claude Code), these environment variables are absent from process.env. To discover them reliably without fragile process-table scraping, Dead Drop uses an Antigravity sidecar:

  1. Run the sidecar registration command once:

    npm run setup-sidecar

    This writes ~/.gemini/config/sidecars/deaddrop/sidecar.json.

  2. When Antigravity starts, its internal SidecarManager automatically starts Dead Drop's background exporter (bin/sidecar.js), providing it with the live credentials.

  3. The sidecar atomically writes <mailbox>/.antigravity_session.json (file permissions 0600).

  4. When Claude Code (or any external caller) sends mail to Antigravity, Dead Drop reads <mailbox>/.antigravity_session.json and passes the active address and CSRF token to agentapi.

  5. The sidecar also exports the Antigravity project it runs in (project_id). agentapi needs that project id when it is called from a process Antigravity didn't launch: new-conversation fails with "project_id is required when providing project_env_config" without it.

After updating Dead Drop, restart the sidecar (restart Antigravity, or disable and re-enable the deaddrop sidecar) so the running sidecar picks up the new code and exports project_id. Until it does, wakes still work as before but carry no project id, and start_antigravity_session needs project or agents.antigravity.project_id.

Antigravity project id

agentapi calls from Dead Drop use the first of these that is set:

  1. the project parameter of start_antigravity_session, resolved read-only against Antigravity's project registry (~/.gemini/config/projects/<id>.json) by exact project name or by absolute git folder. An unknown or ambiguous value is an error and nothing starts;

  2. the woken agent's own project_id (wakes only);

  3. agents.antigravity.project_id in .config.json;

  4. the sidecar's exported project_id (or ANTIGRAVITY_PROJECT_ID when Dead Drop itself runs inside Antigravity).

The sidecar is registered for all projects, so its project is whichever Antigravity started it in. Pass project whenever the conversation belongs to a specific project. If none of these is set, start_antigravity_session fails before starting anything; a wake goes ahead without a project id.

Graceful Failure

If <mailbox>/.antigravity_session.json is missing, unreadable, or stale (the sidecar PID is no longer running), Dead Drop skips the wake-up, logs an informative notice to stderr advising to run npm run setup-sidecar, and proceeds normally. As with all Dead Drop wake failures, the message file itself is always delivered safely.

When agentapi itself fails, it exits with code 1 and prints the reason as JSON on stdout, usually with nothing on stderr. Dead Drop reports agentapi's own error text, the exit code, stderr and stdout in the wake-up note (with the session token redacted), so the cause is visible instead of a bare "Command failed".

Known Limitations

  • Multi-window Antigravity: Dead Drop v1 assumes a single running Antigravity instance. In multi-window or multi-profile setups, the sidecar reflects the most recently active instance.

Any other agent

Run the server with --agent=<name> (or set DEADDROP_AGENT=<name>), pointed at a shared DEADDROP_DIR, and it can send and receive mail the same way.


License

AGPL-3.0 — if you modify Dead Drop and run it as a network service, you must make your modified source available to users of that service.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables real-time, file-based messaging between terminal AI agents (like Claude Code, Gemini CLI) allowing them to collaborate on tasks directly without a server or network, using JSON files and atomic rename for coordination.
    MIT