Skip to main content
Glama

When an AI agent tests your UI or adds visual proof to a pull request, it needs somewhere to put the screenshots. The usual workaround is a throwaway branch in the repository, committed only to get a GitHub URL for each image. tralha replaces that branch. The agent uploads the file to a bucket, asks for a link and pastes ![login](https://files.example.com/p/…) into the PR.

The same place keeps the notes and findings the agent writes along the way. Markdown documents are indexed for full-text search, so a later session can find what an earlier one discovered.

Everything is private by default. A file only becomes reachable from the internet when someone creates a link for it, and that link can be revoked.

Tralha is Portuguese for odds and ends, the stuff that piles up in a junk drawer.

The admin panel showing a screenshot with its public link and details

The wiki explains each part in more depth: the architecture, the MCP tools, cleanup rules, the security model and deployment options.

How it works

Files live in buckets. A file's key is a path like pr-128/login-before.png, and the slashes act as folders. Each bucket has its own visibility, quota, cleanup rules and image compression setting.

tralha listens on two ports:

Port

Serves

Who should reach it

8000 (private)

REST API, MCP server, uploads, admin panel

You and your agents

8001 (public)

/p/<token> links and public-read buckets at /b/…

Anyone, including GitHub's image proxy

The split is by port because a Host header can be forged from outside. Port 8001 serves nothing that needs a key. Port 8000 turns away any request without a valid key or panel session before reading its body, so it can face the internet behind HTTPS, the way an S3 endpoint does, or stay on a VPN if you prefer.

Links are random tokens stored in the database. They never expire unless you ask for an expiry, because an image in a merged PR should keep working. A revoked link stops resolving at once, although browsers and GitHub's image proxy may keep a cached copy for up to an hour.

API keys are scoped per bucket with bucket:permission pairs, such as scratch:write, pr-*:share or *:read. An agent can get a key that writes and shares in two buckets and can delete nothing.

Cleanup rules delete files older than a given age, or not accessed for a given time, optionally only under a prefix. A rule can keep any file that still has an active link.

Image compression is off until you turn it on. In lossless mode PNG and GIF become pixel-identical WebP; in lossy mode JPEG is converted too.

Related MCP server: librechat-personal-files-mcp

Quick start

With Docker:

git clone https://github.com/Lusqinha/tralha.git && cd tralha
cp .env.example .env            # the defaults work on your own machine
docker compose up -d --build

docker compose exec tralha tralha bucket create pr-assets
docker compose exec tralha tralha bucket create scratch --ttl 7d
docker compose exec tralha tralha key create admin --scope '*:admin'

Without Docker, using uv:

uv sync
uv run tralha bucket create pr-assets
uv run tralha key create admin --scope '*:admin'
uv run tralha serve             # private port 8000, public port 8001

key create prints the key once. Only its hash is stored, so copy it right away.

Open http://localhost:8000/admin and sign in with the admin key to browse files, upload by drag and drop, create links and manage buckets.

Connect an AI agent

Create a key for the agent with only the access it needs. --expires is optional:

tralha key create claude --expires 90d \
  --scope '*:read' \
  --scope 'pr-assets:write' --scope 'pr-assets:share' \
  --scope 'scratch:write' --scope 'scratch:share'

Add it to Claude Code. On a VPS, use your API domain instead of localhost:

claude mcp add --transport http --scope user tralha http://localhost:8000/mcp \
  --header "Authorization: Bearer trh_…"

Then ask for things in plain language:

Take a screenshot of the login page, upload it to pr-assets under pr-128/ and give me the markdown for the PR.

Save what you found about the token bug as a note in scratch/notes/ so we can find it later.

The MCP tools cover buckets, text documents, binary uploads, image previews for the model, links and search. Binary files never travel through MCP as base64. The agent calls create_upload, gets a single-use URL and sends the file with curl, which saves a lot of tokens.

ChatGPT connectors require OAuth, which tralha does not support yet.

Deploy on a VPS

GitHub loads PR images through its own proxy, so links must be reachable from the internet over HTTPS. The compose file ships an optional Caddy proxy that serves both ports on their own domains and gets certificates by itself.

  1. Point two DNS records at the server, for example api.example.com and files.example.com.

  2. In .env, switch to the VPS block: set TRALHA_API_URL and TRALHA_PUBLIC_URL to the two https:// addresses and TRALHA_API_DOMAIN and TRALHA_FILES_DOMAIN to the bare domains.

  3. Start everything with the proxy profile:

    docker compose --profile proxy up -d --build

Only ports 80 and 443 open on the server. Both tralha ports stay bound to localhost, and Caddy reaches them over the compose network. TRALHA_FORWARDED_ALLOW_IPS already trusts that network, so tralha sees the real client IP and HTTPS scheme, and the panel cookie gets the Secure flag.

Then connect your local agent to https://api.example.com/mcp. With port 8000 on the internet, a few extra steps help:

  • TRALHA_ADMIN_ALLOWED_IPS restricts the panel to your IPs or networks. Anyone else gets a 404 at /admin, while the API and MCP keep working from anywhere.

  • --expires on tralha key create limits how long a leaked key stays useful. tralha key list shows when and from which IP each key was last used.

  • Prefer a VPN such as Tailscale if you would rather keep the API off the internet altogether. Bind port 8000 to the VPN address and point TRALHA_API_URL at it; the agent uses that URL for uploads.

On Easypanel

In an Easypanel project, open Templates → Create from Schema and paste deploy/easypanel/schema.json. Easypanel builds the image from master, serves the API and panel on the service domain and links on files- plus that domain, both over HTTPS. After the first deploy, create an admin key from the service console with tralha key create admin --scope '*:admin'.

Configuration

All settings are environment variables, and .env.example lists them with comments.

Variable

Default

Purpose

TRALHA_PORT

8000

Private port

TRALHA_PUBLIC_PORT

8001

Public port

TRALHA_API_URL

http://localhost:8000

Base URL for upload links given to agents

TRALHA_PUBLIC_URL

http://localhost:8001

Base URL for public links

TRALHA_DATA_DIR

./data

Where files and the SQLite database are stored

TRALHA_DATABASE_URL

SQLite in the data dir

Or postgresql+psycopg://user:pass@host/db

TRALHA_MAX_UPLOAD_MB

25

Maximum size of one file

TRALHA_UPLOAD_TTL

600

Seconds an upload URL stays valid

TRALHA_GC_INTERVAL

3600

Seconds between cleanup runs

TRALHA_FORWARDED_ALLOW_IPS

127.0.0.1

Proxies trusted for X-Forwarded-* headers; .env.example sets the compose network

TRALHA_ADMIN_ALLOWED_IPS

empty (anyone)

IPs or networks allowed to open /admin

TRALHA_API_DOCS

off

Serve OpenAPI docs at /api/docs

TRALHA_API_DOMAIN, TRALHA_FILES_DOMAIN

none

Domains for the bundled Caddy

Compression and keeping originals are changed at runtime from the panel or with tralha settings compression=lossless.

Security

Uploaded files come from agents, and agents may copy content from untrusted places. tralha treats every upload as hostile:

  • Image decoding never happens in the server process. Each image is handled by a short-lived worker that cannot create processes, has capped memory and CPU, and, on Linux 5.13 or newer, runs under a Landlock ruleset that blocks running programs, opening TCP connections and touching any file other than its input and output.

  • Pillow only opens PNG, JPEG, GIF and WebP, so a file disguised as an image cannot reach decoders such as Ghostscript. Oversized images and decompression bombs are rejected before decoding. tralha does not use ImageMagick, Ghostscript, librsvg or ffmpeg.

  • The content type comes from the file's bytes, not from what the client claims. Everything served publicly carries a sandbox CSP, so an uploaded SVG or HTML file cannot run scripts, and HTML is served as a download.

  • On port 8000, a request without a valid key or panel session is rejected before its body is read. JSON bodies are capped at 1 MB, and file bodies at the upload limit.

  • An IP that sends 20 bad keys, panel logins or upload tokens within 5 minutes is locked out for 15 minutes.

  • Keys can expire, and each use records the time and client IP.

  • The admin panel uses HttpOnly, SameSite=Strict session cookies, refuses cross-origin form posts and allows no inline scripts.

  • The container runs as a non-root user with a read-only root filesystem, no Linux capabilities and a process limit.

Development

uv sync
uv run pytest
uv run ruff check . && uv run ruff format .

The tests include an escape attempt from inside the image sandbox. It is skipped on kernels without Landlock.

Work happens on develop; master receives releases.

Current limits

  • Files are stored on local disk. An S3-compatible backend is planned.

  • Video is stored as uploaded. Video compression needs ffmpeg and a background queue.

  • There are no database migrations yet, so a schema change can require starting from a fresh database.

License

AGPL-3.0. If you run a modified tralha as a service for others, you must publish your changes.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI clients to read and write to a user-owned markdown bucket via S3-compatible storage, providing a persistent shared context layer across different AI tools.
    72,595 npm
    3
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to manage media assets on any S3-compatible storage, including uploads, downloads, tagging, and collections, via presigned URLs.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides AI agents with typed tools to bootstrap sessions, ingest files, search, read, write, link, handoff, self-check, and audit markdown notes in a self-hosted Obsidian-compatible vault.
    3
    MIT