Your AI agent produces something worth looking at. It uploads, gets one URL, and hands it over. You open it and read the thing properly. Fifteen days later it is gone.
agent ──POST /api/v1/files (or MCP get_upload_url)──►┐
│ small-share-app
agent ◄──── https://share.your-net/f/<32 random chars> ─┤
│ │ sqlite + files on disk
│ "here you go" │
▼ │
human ──opens the URL in a browser────────────────────►┘
header: name, size, uploaded, expires in 14d, [Download] [Delete]
below: the file, rendered, in its own frame
One Perl process, one SQLite file, one directory of blobs. About 1,500 lines.
Markdown gets real typography and drawn mermaid diagrams. Images are shown. PDFs open in the browser's own viewer. Reading it is the point.
A stateless Streamable HTTP server at /mcp. One line to
register, and it explains itself to the agent on connect.
MCP tools hand out URLs; the agent moves files with curl. A 3 MB PDF has no business passing through a context window.
A drop zone on the home page, so you can hand a screenshot or a spec to an agent and paste back the URL.
Files are deleted after fifteen days and the URL dies with them. This is a hand-off, not storage — nothing to clean up later.
An agent can be talked into writing anything. A sanitiser allowlist, a sandboxed iframe with no same-origin, and a strict CSP. Three layers, none trusted alone.
No database server, no object store, no queue. Back up one folder and you have backed up the service.
Ships with a Tailscale sidecar, so it lives on your tailnet with a real certificate and no ports open to anyone else.
The image build runs the suite, so an image whose tests fail cannot be published. 90% coverage floor, plus a browser suite that checks mermaid really draws.
Docker and a Tailscale auth key. That is the whole list.
git clone https://github.com/melo/small-share-app
cd small-share-app
cp .env.example .env # put a Tailscale auth key in it
docker compose up -d
The app publishes no host ports. A tailscale serve sidecar
joins your tailnet as its own node, gets a Let's Encrypt certificate
automatically, and proxies to the app. You end up with
https://share.<your-tailnet>.ts.net.
claude mcp add --transport http share https://share.<your-tailnet>.ts.net/mcp
No local Tailscale? docker compose -f
docker-compose.local.yml up -d --build runs it on
127.0.0.1:8080 for you to put behind your own reverse proxy.
Environment variables, all optional except the auth key.
| Variable | Default | What it does |
|---|---|---|
TS_AUTHKEY | — | Tailscale auth key. Required for the tailnet stack. |
TS_HOSTNAME | share | The node name, and so the hostname. |
SHARE_TTL_DAYS | 15 | Retention, and the ceiling any upload may ask for. |
SHARE_MAX_BYTES | 33554432 | Per-file limit. 32 MB. |
SHARE_NOTICE | empty | One line of deployment truth — who can reach this, who to ask. Shown to humans and to agents. |
SHARE_BASE_URL | from the request | The base of every URL handed out. |
One thing to understand, stated plainly.
There is no authentication. Anything that can reach the
port may upload, list and delete. That is a deliberate trade: it removes
the token-distribution problem entirely, so an agent container needs no
credential, no mount and no rotation. It is the right trade on a private
network and the wrong one on the open internet. The share URL itself is 32
characters from /dev/urandom — about 190 bits — and every
response carrying it is marked noindex,
no-referrer and no-store.