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

WinLab Portal

The lab's internal apps on one Postgres: sign in with Keycloak, and everything you can click is also an MCP tool.

nextjs · postgres · docker · mcp

CI  Image  License: MIT

WinLab members book rooms, file receipts, sign documents and open the lab door through one portal. This is its rewrite: plain Postgres instead of a hosted backend, one app at a time, and every action an agent can call as well as a member can click.

> "Who am I on the portal?"
  ⚡ whoami {}
✓ { "name": "王小明", "username": "ming", "email": "ming@example.com" }

What it does

  • Signs members in with the lab's Keycloak (OIDC); there are no passwords here.

  • Defines each action once: a page and its MCP tool run the same code, so neither can drift.

  • Serves MCP at /mcp, with the portal itself as the OAuth 2.1 authorization server (Client ID Metadata Documents, plus dynamic registration for older clients).

Related MCP server: Heavenly Health Protocol

Deploy

With Docker Compose, on any machine with Docker:

curl -fsSLO https://raw.githubusercontent.com/NYCU-WinLab/portal/main/compose.yaml
curl -fsSL -o .env https://raw.githubusercontent.com/NYCU-WinLab/portal/main/.env.example
# set POSTGRES_PASSWORD, BETTER_AUTH_URL, BETTER_AUTH_SECRET and the KEYCLOAK_* keys in .env
docker compose up -d

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

IMPORTANT

Put a reverse proxy with TLS in front, at the host inBETTER_AUTH_URL. 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.

  2. Open BETTER_AUTH_URL and sign in.

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

    docker compose exec postgres psql -U portal -d portal -c \
      "insert into admins (user_id, app) select id, 'portal' from users where username = '<their account>'"
  4. Add the MCP server to a client, for example claude mcp add --transport http portal <BETTER_AUTH_URL>/mcp, approve it on the consent page, 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 portal's Keycloak client

required

PORTAL_VERSION

Image tag

main

PORTAL_BIND, PORTAL_PORT

Where web listens on the host

127.0.0.1, 3000

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 -->|sign in| kc[Keycloak]
  mcp -. verifies against .-> as[Portal OAuth server]

Keycloak proves who a member is; the portal keeps its own session and is the authorization server for MCP, so the lab-wide realm never opens client registration. An MCP token carries the member's id and acts with exactly their permissions. Only lib/actions, lib/auth and lib/db may touch the database, and CI checks that every action is in the registry the MCP tools are built from.

Develop

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

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. Schema changes go through bun run db:generate; see CLAUDE.md for the conventions.

Limitations

  • Only the sign-in, member record and MCP surface exist so far; the apps move over from portal.winlab.tw one at a time.

  • One instance: DPoP replay records are in Postgres, but nothing else has been tried with more than one web container.

Contributing

Issues and PRs welcome: start with CONTRIBUTING.md.

License

MIT · made in a lab whose door has its own MCP tool

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A self-hostable MCP permissions gateway for legal-tech systems that connects legal apps once and exposes only the tools each team member should use, enforcing a thin policy and audit layer over vendor APIs.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI clients to securely access, query, and mutate normalized user-controlled health data (e.g., from Apple Health or Supabase) through a bounded set of MCP tools, with optional OAuth and sandboxed deployment.
    MIT
  • 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.
    -