Memory

hybrids3

Try it

Self-hosted lightweight object storage speaking three interfaces at once — the S3 API (boto3 / AWS-SDK compatible, AWS Sig V4), a plain HTTP API (curl-friendly, Bearer auth), and MCP (Streamable HTTP tools for agents). Buckets/objects with SQLite metadata and flat-file storage on disk; buckets are config-defined (no create/delete API), each with a public/private flag, a private key + public key pair, optional TTL expiry, and a max upload size. Auth is per-bucket keys plus a cross-bucket master key; presigned GET/PUT URLs for handing out single-object access. Use when the user wants to put/get/list/delete files in a self-hosted S3-compatible store, generate presigned upload/download links, drive their own boto3-compatible object storage, or connect an agent to object storage over MCP.

What it does

Lightweight object storage that speaks three interfaces from one container: the **S3 API** (boto3 / any AWS SDK, AWS Signature V4), a **plain HTTP API** ( + Bearer token), and **MCP** (Streamable HTTP tool definitions for AI agents). Metadata lives in SQLite, object bytes are flat files on disk.

The skill document

hybrids3

Lightweight object storage that speaks three interfaces from one container: the S3 API (boto3 / any AWS SDK, AWS Signature V4), a plain HTTP API (curl + Bearer token), and MCP (Streamable HTTP tool definitions for AI agents). Metadata lives in SQLite, object bytes are flat files on disk.

Buckets are configuration, not state — they're defined in the server's config.yaml and there is no API to create or delete them. Each bucket carries: a public/private flag, a private key (Bearer token + S3 signing secret), a public_key (the aws_access_key_id identifier), an optional ttl (objects auto-expire after last write), and an optional max_file_size. A single cross-bucket master_key operates on every bucket and is the only credential that can list all buckets.

Object keys support nested paths (reports/2024/jan.pdf). Content type is auto-detected on upload (libmagic sniff + extension fallback). Presigned GET/PUT URLs hand out time-limited single-object access without exposing a key.

For installation, configuration, and container setup, see references/setup.md.

Security & safety

  • Public buckets are world-readable. A bucket with public: true serves GET/HEAD/LIST to anyone who can reach the port — no auth at all. Only store data there that's meant to be openly readable; keep sensitive data in a public: false bucket.
  • Bind loopback, not all interfaces. The container listens on 8080. Publishing it as -p 8080:8080 / compose "8080:8080" makes it reachable from the network by default. Use -p 127.0.0.1:8080:8080 (loopback) unless remote access is intentionally required, and for real deployments put the service on an internal Docker network behind a reverse proxy instead of exposing the port directly — see references/setup.md and Behind a Reverse Proxy.
  • DELETE, presigned PUT, and MCP delete_object are destructive & irreversible. There's no undo, no versioning, no recycle bin — a deleted object (or one overwritten via PUT) is gone. An agent must NEVER call DELETE / delete_object, or generate/use a presigned PUT that overwrites, unless the user explicitly asked for that exact key to be removed or replaced; confirm the specific bucket + key first, never enumerate-then-bulk-delete, and treat bulk cleanup as requiring explicit per-object user confirmation.
  • Bucket keys, master key, and presigned URLs are bearer credentials. Whoever holds a bucket's private key or the master_key (or a live presigned URL) can act as that bucket/master until the credential is rotated or the URL expires — there's no per-caller revocation. Never paste these into shared prompts, logs, or presigned URLs handed to untrusted parties beyond the one object they're meant to grant.
  • This skill is a consumer, not an operator. It talks to an instance the user already runs; it doesn't provision, harden, or expose the server. Don't use this skill to reconfigure config.yaml, change bucket visibility, or alter port bindings on the user's behalf without being explicitly asked.

When To Use

  • Put / get / list / delete files in a self-hosted, S3-compatible object store.
  • Drive existing boto3 / AWS-SDK code against your own endpoint (change endpoint_url + credentials, nothing else).
  • Store artifacts, uploads, reports, backups that services and agents read/write.
  • Generate a presigned PUT URL so a third party can upload one specific key without your bucket key.
  • Generate a presigned GET URL so a third party can download one private object for a limited time.
  • Connect an AI agent to object storage over MCP — structured upload_object / download_object / list_objects / etc. tools.
  • Auto-expiring scratch storage — set a bucket ttl and objects clean themselves up.

When NOT To Use

  • Creating or deleting buckets at runtime — buckets live in config.yaml; add/remove them there and restart. PUT /{bucket} is an S3-compat no-op.
  • Multipart / chunked uploads (S3's multipart protocol) — not implemented. Upload the whole object in one request.
  • Object versioning, ACLs beyond bucket-level public/private, replication, or encryption at rest — none are supported.
  • Downloading objects over 50 MB via MCP — the MCP download_object tool caps at 50 MB. Use the S3 or plain HTTP GET for large objects.
  • CORS from a browser — no CORS headers are emitted; add them at the reverse-proxy layer.
  • Provisioning, configuring, or hardening the server itself — this skill is a consumer. It talks to an instance the user already runs and trusts.

Setup

The container should already be running. Point the skill at it:

export HYBRIDS3_URL=http://localhost:8080

Verify: curl $HYBRIDS3_URL/health returns {"status":"ok"}.

Keys are per-bucket and come from the operator's config.yaml. Export the ones you'll use rather than pasting them inline:

export HYBRIDS3_KEY=          # Bearer token / aws_secret_access_key
export HYBRIDS3_PUBLIC_KEY=    # aws_access_key_id (defaults to bucket name)
# For cross-bucket / list-all-buckets operations, use the master credentials instead:
export HYBRIDS3_MASTER_KEY=
export HYBRIDS3_MASTER_PUBLIC_KEY=   # defaults to "master"

If the server runs behind a reverse proxy at a subpath, HYBRIDS3_URL includes it (e.g. http://host/storage) and every route — health included — lives under that prefix.

For install / config.yaml shape / bucket + key definitions / ports / reverse proxy, see references/setup.md.

Three Access Modes

One service, three front doors on the same port (8080). Pick per client:

ModeEndpointAuthReach for it when…
S3 APIroot (/), signed requestsAWS Sig V4 (public_key = access key id, key = secret)You already have boto3 / AWS-SDK code, or want SDK ergonomics (pagination, presign helpers, retries).
Plain HTTP/{bucket}/{key} etc.Authorization: Bearer Shell scripts, curl, quick one-offs. No SDK, no signing — just a Bearer header (and public-bucket reads need none).
MCPPOST /mcp/per-connection + per-tool auth_keyAn AI agent should manipulate storage through typed tool calls instead of raw HTTP.

They share one SQLite metadata store and one flat-file tree — an object written over S3 is readable over plain HTTP and MCP, and vice versa.

S3 API

S3 clients authenticate with AWS Signature V4. The bucket's public_key is the aws_access_key_id (travels in the request in plaintext, in the Credential= field); the bucket's private key is the aws_secret_access_key (used only to compute the HMAC locally — never transmitted). region_name is required by SDKs but the value is arbitrary — use us-east-1.

Authorization: AWS4-HMAC-SHA256 Credential=uploads-id/20240101/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=

boto3

import boto3
from botocore.config import Config

# per-bucket client — access limited to the "uploads" bucket
s3 = boto3.client(
    "s3",
    endpoint_url="http://localhost:8080",     # HYBRIDS3_URL
    aws_access_key_id="uploads-id",           # bucket public_key
    aws_secret_access_key="uploads-secret",   # bucket key (private)
    region_name="us-east-1",                  # arbitrary but required
    config=Config(signature_version="s3v4"),
)

s3.put_object(Bucket="uploads", Key="file.txt", Body=b"hello")
s3.get_object(Bucket="uploads", Key="file.txt")["Body"].read()
s3.head_object(Bucket="uploads", Key="file.txt")
s3.list_objects_v2(Bucket="uploads", Prefix="images/")
s3.delete_object(Bucket="uploads", Key="file.txt")
s3.list_buckets()                             # bucket key → only "uploads"

# presigned URLs (see MCP / HTTP presign for the same semantics)
get_url = s3.generate_presigned_url(
    "get_object", Params={"Bucket": "permanent", "Key": "doc.pdf"}, ExpiresIn=3600,
)
put_url = s3.generate_presigned_url(
    "put_object", Params={"Bucket": "uploads", "Key": "inbox/report.pdf"}, ExpiresIn=600,
)

For cross-bucket access or list_buckets() returning all buckets, build a second client with the master credentials (aws_access_key_id=master_public_key, aws_secret_access_key=master_key). Don't embed the master key in client-facing code — per-bucket keys scope access to one bucket.

Destructive & irreversible. delete_object deletes the object with no undo — no versioning, no recycle bin. An agent must NEVER call it unless the user explicitly asked for that exact key to be deleted; confirm the specific bucket + key first, scope it to the current task, and never enumerate-then-bulk-delete. On a shared instance where multiple callers use the same bucket, this can destroy another caller's data — treat bulk deletes as admin-only and requiring explicit confirmation.

aws-cli

aws --endpoint-url "$HYBRIDS3_URL" \
    --region us-east-1 \
    s3api put-object --bucket uploads --key file.txt --body ./file.txt
# credentials via env: AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY=

Supported S3 operations: put_object, get_object, head_object, delete_object, list_objects_v2 (honours Prefix), list_buckets, generate_presigned_url (GET + PUT). Not supported: multipart upload, versioning, ACLs, bucket create/delete (boto3's implicit create_bucket maps to a no-op).

Plain HTTP API

Bearer auth: pass the bucket's private key (or the master_key) as Authorization: Bearer . Public-bucket reads (GET/HEAD/LIST) need no auth; all writes and private-bucket reads require a key.

Requests carrying an AWS Sig V4 Authorization header get S3-style XML back; everything else gets JSON. Every response includes X-Request-Id (log correlation) and X-Content-Type-Options: nosniff. Errors carry "error" + "request_id" fields.

Objects

MethodPathAuthDescription
PUT/{bucket}/{key}writeUpload object. Returns ETag (MD5 of content). Content type auto-detected; override with a Content-Type header.
GET/{bucket}/{key}readDownload object. Returns ETag, Last-Modified, Content-Length.
HEAD/{bucket}/{key}readObject metadata, no body.
DELETE/{bucket}/{key}writeDelete object — always 204, even if absent.
GET/{bucket}readList objects. Query: prefix, max-keys.
POST/presign/{bucket}/{key}writeGenerate a presigned URL. Query: method (GET default / PUT), expires (seconds, 1–604800, default 3600).

Destructive & irreversible. DELETE removes the object with no undo. An agent must NEVER call it unless the user explicitly asked for that exact key to be deleted; confirm the specific bucket + key first, scope it to the current task, and never enumerate-then-bulk-delete. On a shared bucket used by other callers, this can destroy their data — treat it as admin-only.

# upload
curl -X PUT "$HYBRIDS3_URL/uploads/file.txt" \
  -H "Authorization: Bearer $HYBRIDS3_KEY" \
  --data-binary @file.txt

# upload a nested key (parent dirs created automatically)
curl -X PUT "$HYBRIDS3_URL/uploads/reports/2024/january.pdf" \
  -H "Authorization: Bearer $HYBRIDS3_KEY" \
  --data-binary @january.pdf

# download from a public bucket — no auth needed
curl "$HYBRIDS3_URL/uploads/file.txt"

# download from a private bucket
curl "$HYBRIDS3_URL/permanent/doc.pdf" \
  -H "Authorization: Bearer $HYBRIDS3_KEY"

# list objects with a prefix
curl "$HYBRIDS3_URL/uploads?prefix=images/&max-keys=50" \
  -H "Authorization: Bearer $HYBRIDS3_KEY"

# delete
curl -X DELETE "$HYBRIDS3_URL/uploads/file.txt" \
  -H "Authorization: Bearer $HYBRIDS3_KEY"

Buckets

MethodPathAuthDescription
GET/healthnone{"status":"ok"} liveness check.
GET/master or bucket keyList buckets — master key lists all, bucket key lists only its own.
HEAD/{bucket}read200 if bucket exists in config, else 404.
PUT/{bucket}writeS3-compat no-op: 200 if bucket exists in config, 404 if not. Creates nothing.
# list all buckets — needs the master key
curl "$HYBRIDS3_URL/" -H "Authorization: Bearer $HYBRIDS3_MASTER_KEY"

# a bucket key lists only its own bucket
curl "$HYBRIDS3_URL/" -H "Authorization: Bearer $HYBRIDS3_KEY"

Presigned URLs (HTTP)

POST /presign/{bucket}/{key} requires the bucket key or master key. The result grants exactly one verb on exactly one key. A GET URL can't be used to PUT (the method is baked into the signature).

# presigned GET on a PRIVATE bucket → signed, expiring URL
curl -X POST "$HYBRIDS3_URL/presign/permanent/doc.pdf?expires=3600" \
  -H "Authorization: Bearer $HYBRIDS3_KEY"
# → {"url":"http://.../permanent/doc.pdf?X-Amz-Algorithm=...&X-Amz-Signature=...","method":"GET","expires":3600}

# presigned GET on a PUBLIC bucket → plain URL, no signature, no expiry (reads are open anyway)
curl -X POST "$HYBRIDS3_URL/presign/uploads/photo.jpg" \
  -H "Authorization: Bearer $HYBRIDS3_KEY"
# → {"url":"http://.../uploads/photo.jpg","method":"GET","expires":null}

# presigned PUT → ALWAYS signed, even for public buckets (anonymous writes are never allowed)
curl -X POST "$HYBRIDS3_URL/presign/uploads/inbox/report.pdf?method=PUT&expires=600" \
  -H "Authorization: Bearer $HYBRIDS3_KEY"

# recipient uploads using only the URL — no Authorization header
curl -X PUT "" --data-binary @report.pdf

The bucket's max_file_size is enforced server-side during the upload regardless of how it was authenticated (oversized → 413). Expired or tampered presigned URLs → 403.

MCP Endpoint

An MCP server runs at POST /mcp/ over the Streamable HTTP transport. Client config:

{
  "mcpServers": {
    "hybrids3": { "type": "streamable-http", "url": "http://localhost:8080/mcp/" }
  }
}

Wire it into Claude Code:

claude mcp add --transport http hybrids3 "$HYBRIDS3_URL/mcp/"
# with endpoint-level auth:
claude mcp add --transport http hybrids3 "$HYBRIDS3_URL/mcp/" \
  --header "Authorization: Bearer $HYBRIDS3_MASTER_KEY"

Auth (two layers)

Endpoint-level (optional) — a token that authenticates the connection before any tool runs. Master key = full access; a bucket key = limited to that bucket. Send it as Authorization: Bearer , or (for clients that can't set headers) as ?auth= on the URL. A token that matches nothing → 401; no token → passes through, and per-tool auth applies.

Per-tool — every tool that touches a bucket takes an auth_key argument (the bucket's private key or the master key), checked independently of the connection token. Public-bucket reads (download_object, list_objects, object_info) accept an empty auth_key; all writes and all private-bucket ops require it.

Tools

ToolArgsAuthNotes
upload_objectbucket, key, content, auth_key, content_type="", encoding="utf-8"bucket/master keyencoding="base64" for binary. Returns size, etag, content_type.
download_objectbucket, key, auth_key="", encoding="utf-8"key on private onlybase64 for binary; UTF-8 that won't decode auto-falls back to base64. Objects > 50 MB rejected — use HTTP.
delete_objectbucket, key, auth_keybucket/master keyIdempotent.
list_objectsbucket, auth_key="", prefix="", max_keys=100key on private onlymax_keys clamped to 1–1000.
list_bucketsauth_keymaster or bucket keyMaster lists all; bucket key lists only its own.
object_infobucket, key, auth_key=""key on private onlyMetadata only (size, content_type, etag, uploaded_at, expires_at).
presign_urlbucket, key, auth_key, method="GET", expires=3600, base_url="", host=""bucket/master keymethod GET/PUT. Same public-GET-plain / everything-else-signed logic as the HTTP endpoint. expires clamped 1–604800. Set base_url to the externally-reachable URL so the link is usable.

delete_object is destructive & irreversible. It deletes the object with no undo. An agent must NEVER call it unless the user explicitly asked for that exact key to be deleted; confirm the specific bucket + key first, never enumerate-then-bulk-delete. On a shared bucket used by other callers, this can destroy their data — treat it as admin-only.

All tools return structuredContent with a plain-text fallback. Internal error details are masked.

Raw JSON-RPC

For debugging or non-MCP callers, POST JSON-RPC directly. The transport requires the Accept header below.

# tools/list
curl -s "$HYBRIDS3_URL/mcp/" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# tools/call — upload
curl -s "$HYBRIDS3_URL/mcp/" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc":"2.0","id":2,"method":"tools/call",
    "params":{
      "name":"upload_object",
      "arguments":{"bucket":"uploads","key":"note.txt","content":"hello","auth_key":"'"$HYBRIDS3_KEY"'"}
    }
  }'

Authentication (all modes)

CredentialConfig fieldRoleKeep secret?
Bucket private keykeyBearer token (HTTP/MCP) and aws_secret_access_key (S3). Signs/verifies — never transmitted.Yes
Bucket public keypublic_keyaws_access_key_id (S3) + Credential= in presigned URLs. Identifies, grants nothing. Defaults to bucket name.No
Master keymaster_keyCross-bucket god key. Works on every bucket + only credential that lists all buckets.Yes
Master public keymaster_public_keyaws_access_key_id paired with the master key (S3). Defaults to "master".No

Bucket visibility:

GET / HEAD / LISTPUTDELETE / presign
public: trueno authbucket key, master key, or valid presigned PUTbucket key or master key
public: falsebucket key, master key, or valid presigned GETbucket key, master key, or valid presigned PUTbucket key or master key

Unauthorized or non-existent bucket access always returns 404 — callers can't tell "doesn't exist" from "no access". All key comparisons are constant-time.

Typical Workflows

Upload + fetch over plain HTTP

export HYBRIDS3_URL=http://localhost:8080
export HYBRIDS3_KEY=

curl -X PUT "$HYBRIDS3_URL/uploads/report.pdf" \
  -H "Authorization: Bearer $HYBRIDS3_KEY" --data-binary @report.pdf
curl -sO "$HYBRIDS3_URL/uploads/report.pdf"     # public bucket → no auth on read

Or via the helper (see scripts/hybrids3.sh):

HYBRIDS3_URL=http://localhost:8080 HYBRIDS3_KEY= \
  scripts/hybrids3.sh put uploads report.pdf ./report.pdf
scripts/hybrids3.sh list uploads reports/
scripts/hybrids3.sh get uploads report.pdf ./out.pdf
scripts/hybrids3.sh delete uploads report.pdf
scripts/hybrids3.sh buckets                     # needs HYBRIDS3_KEY=

Hand someone a presigned upload URL

# you generate it (needs a key); they upload with no credentials
curl -X POST "$HYBRIDS3_URL/presign/uploads/inbox/theirs.zip?method=PUT&expires=600" \
  -H "Authorization: Bearer $HYBRIDS3_KEY" | jq -r .url
# → hand the URL over; recipient: curl -X PUT "" --data-binary @theirs.zip

Drop-in boto3 against your own endpoint

Point any existing boto3 code at HYBRIDS3_URL, set the bucket's public_key/key as the credential pair, region_name="us-east-1", signature_version="s3v4". See S3 API.

Agent-driven storage over MCP

Add the server (claude mcp add --transport http hybrids3 "$HYBRIDS3_URL/mcp/"), then call list_buckets to discover buckets, upload_object / download_object / list_objects to move data. Pass the bucket key as auth_key; use the master key only when you need cross-bucket reach.

Auto-expiring scratch storage

If a bucket has a ttl set (server config), objects delete themselves that long after their last write — overwriting resets the clock. Nothing to do client-side; just write to the bucket.

Related skills

Generate and edit Draw.io, Mermaid, and Excalidraw diagrams from natural language using a structured JSON spec.

by nssa.io1.0k installs47 stars

Join a video meeting as an AI bot with voice, avatar, and screenshare across four operating modes.

by johnpatternai21 installs8 stars

Read and write Excel workbooks, worksheets, ranges, tables, and charts in OneDrive through Microsoft Graph with managed OAuth.

by byungkyu800 installs42 stars

Stores durable facts in a categorized, plain-markdown vault on disk, alongside your agent's built-in memory.

by Iván1 installs

Find why your productivity system keeps failing, then apply the smallest fix — capacity math, bottleneck routing, durable local notes.

by Iván1 installs

More from psyb0t

Browse all skills

Drive a user-deployed mt5-httpapi MetaTrader 5 bridge over HTTP, with per-action confirmation on every real-money call.

by psyb0t107 installs4 stars

Docker-packaged browser automation for QA against anti-bot stacks and authorized detection testing.

by psyb0t137 installs2 stars

Self-hosted OpenAI-compatible speech service — transcription, translation, and synthesis from one container.

by psyb0t13 installs

Run ffmpeg, sox, and ImageMagick jobs against your files in a fixed-allowlist SSH sandbox.

by psyb0t71 installs

Generate speech audio via Qwen3-TTS over SSH with preset voices, voice cloning, and voice design.

by psyb0t55 installs

Drive one or more IMAP/SMTP mailboxes through a single REST + MCP endpoint — read, search, send, mark-seen, and delete across accounts in parallel.

by psyb0t15 installs