Observed since 21 Oct, 09:51 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Self-hostable notification + rendering surface for LLM agent outputs.
A self-hosted notification + rendering surface for LLM agent outputs.
Agent Drop takes an HTTP POST from an agent, stores it as a rendered markdown page at a short URL, and fires a push notification to the user with a tappable link. It sits alongside Obsidian, Notion, or whatever else — it is the "look at this now" surface, not a replacement for canonical storage.
Status: v1.5 milestone shipped — MVP, month-one, MCP server, FTS search, routing rules, and Web Push PWA delivery are all on main. Container images are published at ghcr.io/jay-uk/agent-drop:latest. See CHANGELOG.md for current release + history and ROADMAP.md for what's next.
Ingestion & rendering
POST /api/v1/drops with title + body + content (markdown) — drops render at a short, unguessable public URL.markdown-it-py with GFM, task lists, footnotes, admonitions, and Mermaid diagrams (client-side).external_url if set).Notification delivery
web_push) — the recommended default: pushes land on any device subscribed as a PWA (iOS 16.4+, Android Chrome). No third-party app in the loop, so "tap the notification → land on the drop page" is genuinely one tap. Auto-configured on first launch when VAPID keys are set.(source, priority) pairs onto specific channels at Settings → Routing. Most-specific rule wins; tie-break by explicit order then age.Inbox & UI
/ — paginated list of drops, desktop and mobile layouts, newest first.title + body + content + source + tags via SQLite FTS5 (Porter stemmer, diacritic-folding).
Auth
Agent-facing tooling
agentdrop CLI for shell / scripts (agentdrop drop, agentdrop list, agentdrop config).agentdrop-mcp server exposes create_drop and list_recent_drops as native tools in Claude Desktop / Claude Code.Deployment
ghcr.io/jay-uk/agent-drop, linux/amd64). ARM64 is supported for local dev via make docker-up.AGENTDROP_DATABASE_URL.AGENTDROP_BASE_URL to the public hostname and push payloads carry the right one-tap URL.The goal of this walkthrough is: running server → notification landing on your phone, in about five minutes. It assumes Docker Desktop (or equivalent) is running locally.
New to self-hosted services or AI agents?
docs/QUICKSTART.mdis the same flow, broken into smaller steps with every dependency called out. Use that one and come back here once Agent Drop is up.
git clone https://github.com/Jay-uk/agent-drop.git
cd agent-drop
cp .env.example .env
.envRequired:
AGENTDROP_API_KEY — a bootstrap bearer token. Generate one with python -c 'import secrets; print(secrets.token_urlsafe(32))'.AGENTDROP_ADMIN_PASSWORD — the password you'll use to log into the Settings UI.AGENTDROP_BASE_URL — the public URL of the server as seen from the outside. http://localhost:8080 for pure local testing; when you put the server behind Cloudflare / Tailscale / any reverse proxy, set this to the hostname your device will actually resolve (this is the URL baked into push notifications — if it's wrong, tap-throughs go nowhere).Recommended (enables Web Push, the zero-config delivery path):
make vapid-keys
…then paste the three printed lines (AGENTDROP_VAPID_PUBLIC_KEY, AGENTDROP_VAPID_PRIVATE_KEY, AGENTDROP_VAPID_SUBJECT) into .env. These are server identity for signing Web Push deliveries; rotate only if you have to (every subscribed device is pinned to the public key and rotation forces everyone to re-subscribe).
make docker-up
On first launch, if VAPID is set, the app auto-creates a default web_push channel so push delivery works as soon as a device subscribes. Migrations run automatically. Data persists in ./data/ (SQLite file).
Verify:
curl -sS http://localhost:8080/health
# {"status":"ok","version":"..."}
Agent Drop ships a Web App Manifest and a service worker — the app is designed to be added to your home screen on the device that will receive notifications.
AGENTDROP_BASE_URL in Safari → Share → "Add to Home Screen". You must launch from the home-screen icon (not a Safari tab) to subscribe to push — this is an iOS 16.4+ platform rule, not an Agent Drop constraint.From the installed PWA, log in with your admin password, then go to Settings → Devices and click Enable on this device. Grant notification permission. That's it — the device is subscribed.
From another machine (or the same one):
curl -sS -X POST "$AGENTDROP_BASE_URL/api/v1/drops" \
-H "Authorization: Bearer $AGENTDROP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Hello from Agent Drop",
"body": "Tap to open the rendered drop page.",
"content": "## First drop\n\nIf you are reading this on your phone after tapping a notification, everything works."
}'
Response is 201 Created with { "id", "url", "created_at", "warnings": [] }. The notification should land on the subscribed device within a second or two; tapping it opens the drop URL.
From here: install the CLI (pipx install ai-agent-drop) to drop from your shell, or register the MCP server with Claude Desktop / Claude Code — see the "Sending drops" section below.
If you'd rather route to Pushover / ntfy / Apprise / a webhook instead of (or alongside) Web Push, log into Settings → Channels and add one. Any channel you mark as default takes precedence over the auto-provisioned Web Push one. See Notification channels for per-driver details.
compose.yaml binds the container to 127.0.0.1:8080 on the host — the app is not reachable from your LAN or the public internet by default. Reach it remotely with one of:
tailscale serve on the host and let your tailnet do TLS + auth.cloudflared at http://127.0.0.1:8080; combine with Cloudflare Access for SSO.127.0.0.1:8080.Whichever you pick, set AGENTDROP_BASE_URL to the public hostname so push payloads carry the right tap-through URL. The three required env vars are AGENTDROP_ADMIN_PASSWORD (Settings UI login), AGENTDROP_SECRET_KEY (stable session cookies across restarts), and AGENTDROP_API_KEY (bootstrap bearer token for agents).
For pure local-HTTP testing without any TLS proxy, set AGENTDROP_INSECURE_COOKIES=1 in .env to allow session cookies over plain HTTP. Don't use this in production — it disables the cookie hardening that keeps sessions safe over the open internet.
Configuration is split between environment variables (server infrastructure — loaded from .env by docker compose) and the settings UI (notification channels, API keys, routing rules). See .env.example for the env-var list and Settings in the running app for everything else.
| Variable | Required | Purpose |
|---|---|---|
AGENTDROP_BASE_URL | yes | Public URL included in push notifications and returned from POST /api/v1/drops. |
AGENTDROP_API_KEY | yes | Bootstrap bearer token. Once the app is up, prefer UI-issued per-agent keys at Settings → API keys. |
AGENTDROP_ADMIN_PASSWORD | yes | Password for the single admin session used to reach Settings. |
AGENTDROP_SECRET_KEY | no | Session-cookie signing key. Auto-generated per restart if unset (logs invalidate on restart). |
AGENTDROP_INSECURE_COOKIES | no | Set to 1 to allow session cookies over plain HTTP. Local dev only — never set in production. |
AGENTDROP_DATA_DIR | no | SQLite + any future file storage. Defaults to /data (bind-mounted in compose). |
AGENTDROP_DATABASE_URL | no | Override the default SQLite URL (e.g. to use PostgreSQL). |
AGENTDROP_VAPID_PUBLIC_KEY | for web_push | EC P-256 public key (base64url). Generate with make vapid-keys. |
AGENTDROP_VAPID_PRIVATE_KEY | for web_push | EC P-256 private key (base64url). Treat as a credential. |
AGENTDROP_VAPID_SUBJECT | for web_push | mailto: or https: contact URI for the operator (push services use this). |
All per-install configuration beyond the env-var table above lives behind admin auth at /settings:
web_push, pushover, ntfy, apprise, webhook.(source, priority) pairs onto specific channels. Most-specific rule wins.last_used_at. Env AGENTDROP_API_KEY keeps working alongside these as the bootstrap key.
All channels are managed at Settings → Channels in the running app — there is no env-var path for provider credentials.
web_push) — recommended default. Fans out to all PWA-subscribed devices; requires the AGENTDROP_VAPID_* env vars above. Auto-configured on first launch when VAPID is set, so most operators never need to touch the channel form.The following drivers are available as alternatives if you'd rather route to an existing notification tool:
low → -1, normal → 0, high → 1, urgent → 2 (emergency, with retry+expire).ntfy.sh) or self-hosted. Topic names should be unguessable. Priority mapping: low → 2, normal → 3, high → 4, urgent → 5.Routing rules at Settings → Routing map drop (source, priority) pairs onto specific channels — e.g. send all urgent drops to Pushover and everything else to web_push.
Four ways to POST to /api/v1/drops — pick whichever fits the caller. All four accept the same payload shape; see the API surface table for required fields.
agentdrop)Install the CLI from PyPI and configure it once per machine:
pipx install ai-agent-drop # or: uv tool install ai-agent-drop
agentdrop config --base-url "https://drop.example.com" --api-key "agdr_..."
Config is written to ~/.config/agentdrop/config.toml (XDG_CONFIG_HOME honoured) with mode 0600. The API key is never echoed back unmasked. Per-invocation --base-url / --api-key flags override the file.
For self-hosted instances behind Cloudflare Access, add service-token credentials — they're sent as CF-Access-Client-Id / CF-Access-Client-Secret on every request:
agentdrop config --cf-access-client-id "..." --cf-access-client-secret "..."
Flag-driven drop:
agentdrop drop \
--title "Weekly market scan" \
--body "Report ready, 3 items flagged" \
--content-file ./report.md \
--source weekly-scan \
--tags research,markets \
--priority normal \
--external-url "obsidian://open?vault=Personal&file=Reports/2026-04-19"
Pipe content from another process with --content -:
generate-report.sh | agentdrop drop \
--title "Weekly market scan" \
--body "Report ready, 3 items flagged" \
--source weekly-scan \
--content -
On success, agentdrop drop prints just the drop URL on stdout (pipe-friendly). Pass --json to get the full server response. Failures (HTTP 4xx, network errors, missing config) exit non-zero with a one-line error on stderr.
Other subcommands:
agentdrop list --limit 10 --state unread # compact table, newest first
agentdrop list --json # raw server JSON
agentdrop config # print path + masked summary
agentdrop version # installed package version
agentdrop-mcp)For Claude Desktop and Claude Code users, Agent Drop ships an MCP server that exposes create_drop and list_recent_drops as native tools. Installing ai-agent-drop from PyPI puts both agentdrop and agentdrop-mcp on your PATH:
pipx install ai-agent-drop # or: uv tool install ai-agent-drop
agentdrop-mcp --version
The MCP server reuses the same ~/.config/agentdrop/config.toml the CLI writes, so if you've already run agentdrop config --base-url ... --api-key ... there's nothing more to do — just register the server with your MCP client.
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"agentdrop": {
"command": "agentdrop-mcp",
"env": {
"AGENTDROP_BASE_URL": "https://drop.example.com",
"AGENTDROP_API_KEY": "agdr_..."
}
}
}
}
Env vars override the config file, which matters for multi-host setups where the TOML file wouldn't be present. CF Access service tokens use AGENTDROP_CF_ACCESS_CLIENT_ID / AGENTDROP_CF_ACCESS_CLIENT_SECRET.
Claude Code:
claude mcp add agentdrop agentdrop-mcp
# then ensure your env / ~/.config/agentdrop/config.toml is populated
Once registered, just ask Claude:
"Drop a summary of today's commits to my phone — title 'Daily commits', body '5 PRs merged', content the bullet list above."
Claude will call create_drop and hand you back the public drop URL.
curl -sS -X POST "$AGENTDROP_URL/api/v1/drops" \
-H "Authorization: Bearer $AGENTDROP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Weekly market scan",
"body": "3 items flagged, tap to read",
"content": "# Results\n\n- AAPL up 4.2%\n- BNANA flat\n- CHERRY +28%",
"source": "weekly-market-scan",
"tags": ["markets", "research"],
"priority": "high"
}'
import os, httpx
httpx.post(
f"{os.environ['AGENTDROP_URL']}/api/v1/drops",
headers={"Authorization": f"Bearer {os.environ['AGENTDROP_API_KEY']}"},
json={
"title": "Weekly market scan",
"body": "3 items flagged, tap to read",
"content": report_markdown,
"source": "weekly-market-scan",
"external_url": "obsidian://open?vault=Work&file=Reports/2026-04-19",
},
).raise_for_status()
Point a scheduled routine at Agent Drop. The routine's final step is the POST — everything before it is domain work. Claude Code handles the scheduling; Agent Drop handles the tap-to-read.
#!/usr/bin/env bash
set -euo pipefail
report=$(claude -p "Run the weekly market scan and produce a markdown report.")
curl -sS -X POST "$AGENTDROP_URL/api/v1/drops" \
-H "Authorization: Bearer $AGENTDROP_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg title "Weekly market scan" \
--arg body "Report ready, 3 items flagged" \
--arg content "$report" \
'{title:$title, body:$body, content:$content, source:"weekly-scan", priority:"normal"}')"
The response gives you { "id": "...", "url": "...", "created_at": "...", "warnings": [] }. The url is the tap target already sent in the push — you can also log it, post it into Obsidian, or chain it into another routine.
Requires Python 3.12 and uv on PATH (uv installs to ~/.local/bin).
make install
make dev
Then hit http://localhost:8080/health.
Useful targets:
make help # list all targets
make test # pytest
make lint # ruff check + format --check
make fmt # ruff format + check --fix
make docker-build # local arm64 image tagged agentdrop:dev
make docker-release # multi-arch (amd64 + arm64) release image
Generated static assets are committed; rebuild them with the relevant script when the design changes:
uv run scripts/gen_pygments_css.py # syntax-highlighting stylesheet
uv run scripts/gen_pwa_icons.py # PWA icon set (192/512/180-apple/maskable)
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/drops | Create a drop, fire push. Returns id, url, created_at, warnings. |
GET | /api/v1/drops | Paginated list, filter by source / tag / state; full-text search via ?q=. |
GET | /api/v1/drops/{id} | Full drop detail JSON. |
PATCH | /api/v1/drops/{id} | Toggle pinned / archived / read. |
DELETE | /api/v1/drops/{id} | Soft delete. |
Schema-required fields on POST /api/v1/drops: title (≤250 chars), body (≤1024 chars), content (markdown). Optional: source, tags[], priority (low/normal/high/urgent), external_url, channel, expires_in_days. The full request / response schema is enforced in src/agentdrop/schemas/drops.py and surfaced via the auto-generated OpenAPI doc at /docs on a running server.
| Method | Path | Purpose |
|---|---|---|
GET | /d/{short_id} | Rendered drop page (the tap target). Unguessable short id; no auth by default. |
GET | /health | Liveness probe. |
| Method | Path | Purpose |
|---|---|---|
GET | /pwa/vapid-public-key | Server's VAPID public key for pushManager.subscribe. Returns null when unconfigured. |
POST | /pwa/subscribe | Register a PushSubscription for this device. Upserts by endpoint. |
POST | /pwa/unsubscribe | Delete a subscription row by endpoint. |
The inbox at /, the drop actions (/drops/{id}/pin etc.), and the /settings/* routes (channels, routing, api-keys, devices) are all admin-session-gated. Log in with AGENTDROP_ADMIN_PASSWORD at /login.
CHANGELOG.md — per-release notes, version history.ROADMAP.md — shipped milestones and what's planned next.docs/QUICKSTART.md — extended first-time setup walkthrough.docs/DEPLOYMENT.md — operational runbook for production deploys.CLAUDE.md — guidance for Claude Code when working in this repo.Agent Drop is licensed under the Apache License 2.0. You may use, modify, distribute, and use the software commercially, subject to the conditions in the license (preserve copyright + license notice, state significant changes). Contributions are accepted under the same terms.
Releases up to and including v0.3.0 were published under the MIT License; those artifacts remain MIT-licensed where they were originally distributed. The relicense to Apache 2.0 takes effect from the next release onward.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.5.1 | 4 May 2026 | 28 |
This describes our collected data; it is not a security assessment.
Observed since 21 Oct, 09:51 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:5c703e57f35e3d031be4b6f64026a2fd40d7b432a33acaa541125be4470b1371