Skip to main content
Glama
 ██████╗  █████╗ ██╗     ██╗     ███████╗██████╗ ██╗   ██╗
██╔════╝ ██╔══██╗██║     ██║     ██╔════╝██╔══██╗╚██╗ ██╔╝
██║  ███╗███████║██║     ██║     █████╗  ██████╔╝ ╚████╔╝
██║   ██║██╔══██║██║     ██║     ██╔══╝  ██╔══██╗  ╚██╔╝
╚██████╔╝██║  ██║███████╗███████╗███████╗██║  ██║   ██║
 ╚═════╝ ╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝╚═╝  ╚═╝   ╚═╝

WinLab Gallery

The lab's photo wall for members: photos and short videos in Nextcloud, everything else in its own Postgres, and every action also an MCP tool.

nextjs postgres nextcloud docker mcp

CI  Image  License: MIT

WinLab members share lab photos here: upload from a phone, react, and find who is in them. Only signed-in members see anything; every file passes through the app, which checks the session first.

> "What did we post from last year's orientation?"
  ⚡ list_photos { "query": "迎新" }
✓ { "photos": [{ "name": "合照", "sequenceSize": 6, ... }], "nextOffset": null }

What it does

  • Shows the wall: newest taken first, photos uploaded together as one card, filtered by search, uploader or person.

  • Searches by meaning: "生日蛋糕" finds the cake photos even when nobody named them, through EmbeddingGemma 2 vectors of each photo (optional; off, search matches text only).

  • Takes uploads: photos up to 30 MB (HEIC turns into JPEG) and videos up to 60 seconds and 100 MB (re-encoded to 720p H.264).

  • Stores files in Nextcloud under one folder of one account, with a 480 px and a 2048 px WebP made at upload.

  • Lets members react: 8 reactions.

  • Serves MCP at /mcp, with the gallery as the OAuth 2.1 authorization server.

Related MCP server: eq-mcp-gateway

Deploy

With Docker Compose, on any machine with Docker:

curl -fsSLO https://raw.githubusercontent.com/NYCU-WinLab/gallery/main/compose.yaml
curl -fsSL -o .env https://raw.githubusercontent.com/NYCU-WinLab/gallery/main/.env.example
# set POSTGRES_PASSWORD, BETTER_AUTH_*, KEYCLOAK_* and NEXTCLOUD_* in .env
docker compose up -d

This starts Postgres, runs the migrations once, then serves the gallery from ghcr.io/nycu-winlab/gallery on 127.0.0.1:3000.

IMPORTANT

Put a reverse proxy with TLS in front, at the host inBETTER_AUTH_URL, and let it pass request bodies of 100 MB. MCP clients only accept an HTTPS resource, and the Keycloak redirect URI must match it.

Use

  1. In your Keycloak realm, create a confidential OpenID Connect client with the redirect URI <BETTER_AUTH_URL>/api/auth/callback/keycloak, and put its ID and secret in .env. Give its service account view-users so /mcp can check that a member still exists.

  2. In Nextcloud, create an app password for the account that holds the files, create the folder NEXTCLOUD_ROOT in it, and put both in .env.

  3. Open BETTER_AUTH_URL and sign in.

  4. Make the first admin, once, after that member has signed in (later admins are added on /admins):

    docker compose exec postgres psql -U gallery -d gallery -c \
      "insert into admins (user_id) select id from users where username = '<their account>'"
  5. Add the MCP server to a client, for example claude mcp add --transport http gallery <BETTER_AUTH_URL>/mcp, then ask it to call whoami.

Configure

Set these in .env; .env.example documents every one.

Key

What it sets

Default

POSTGRES_PASSWORD

The database password, used by every service

required

BETTER_AUTH_URL

The public URL; the MCP resource is <url>/mcp

required

BETTER_AUTH_SECRET

Signs sessions and OAuth queries

required

KEYCLOAK_ISSUER

The realm URL, e.g. https://auth.example.com/realms/lab

required

KEYCLOAK_CLIENT_ID, KEYCLOAK_CLIENT_SECRET

The gallery's Keycloak client

required

NEXTCLOUD_URL, NEXTCLOUD_USER, NEXTCLOUD_APP_PASSWORD

Where the files live

required

NEXTCLOUD_ROOT

The folder in that account the gallery owns

gallery

GALLERY_VERSION

Image tag

main

GALLERY_BIND, GALLERY_PORT

Where web listens on the host

127.0.0.1, 3000

COMPOSE_PROFILES, EMBED_URL

search and http://embed:8000 turn on search by meaning (about 3 GB of memory)

empty

OTEL_EXPORTER_OTLP_ENDPOINT

OTLP/HTTP endpoint for traces and error logs; off when empty

empty

How it works

flowchart LR
  member[Member] -->|browser| pages[Pages]
  agent[MCP client] -->|bearer token| mcp["/mcp"]
  pages --> actions[Actions]
  mcp --> actions
  actions --> pg[(Postgres)]
  member -->|"PUT /api/upload"| media[sharp and ffmpeg]
  media --> nc[(Nextcloud)]
  member -->|"GET /media"| cache[Disk cache]
  cache --> nc

An upload streams to a temp file, becomes an original plus two WebP sizes, and goes to Nextcloud as <root>/<photo id>/; only then is the row written. /media/<id>/<thumb|large|original> checks the session, keeps thumb and large on local disk after the first read, and streams originals with Range so videos seek. Only lib/actions, lib/auth and lib/db touch the database, and CI checks that every action is in the registry the MCP tools come from.

Develop

bun install
cp .env.example .env.local   # point DATABASE_URL at a local Postgres
bun run db:migrate
bun run dev

Uploads need ffmpeg and ffprobe on the PATH. CI runs typecheck, lint, format, the design token check, the action registry check and a build, then builds the image and probes a real docker compose up. See CLAUDE.md for the conventions.

Limitations

  • One web container: the media cache and upload temp files are on its own disk.

  • Search by meaning scores every photo per search and embeds on CPU (about 5 seconds a photo on 2 cores), which suits a lab's few thousand photos, not a large archive.

  • Video re-encoding runs inside the upload request, so a 60 second clip takes a while on a small machine.

Contributing

Issues and PRs welcome: start with CONTRIBUTING.md.

License

MIT, made in a lab that keeps every group photo.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    B
    maintenance
    A remote MCP server that lets MCP clients self-register over OAuth 2.1 and call read-only tools for organization identity, place and region browsing, dashboards, metrics, and ranking, forwarding the caller's own Auth0 token so each service still sees the real user. It also exposes a bearer pass-through surface for first-party apps holding an existing token.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables any MCP client to read and edit database schemas (DBML/SQL), Mermaid diagrams, and shared board layouts, along with the organizations, projects, documents, and marketplace listings around them. It runs as a hosted OAuth 2.1 endpoint, so there is nothing to install and access is limited to what the signed-in user can already see.
    MIT