Tofu MCP

Tofu MCP #

Hono + Bun MCP server that gives AI agents read-only PostgreSQL access to the BonsAI database. Lives at apps/tofu-mcp.

What you can do #

Tool Purpose
list_schemas List schemas (public only)
list_tables List tables in a schema
describe_table Columns + constraints for a table
get_relationships Outgoing + incoming foreign keys
list_enums Postgres enum types + values
execute_sql Run a SELECT — every query wrapped in BEGIN TRANSACTION READ ONLY

Use it from Claude Code (local dev) #

  1. Run mise dev (or mise mcp-dev) inside your Coder workspace. tofu-mcp comes up as the tofu-mcp docker compose service on port 3002.
  2. The project-scoped .mcp.json already points Claude Code at http://localhost:3002 with the static TOFU_MCP_API_TOKEN header.
  3. claude mcp list should show tofu-mcp - ✓ Connected.

If it’s not connected, check docker logs tofu-mcp and that TOFU_MCP_API_TOKEN is in your shell env (Doppler-injected).

Use it from claude.ai (web connector) #

Public URL: https://mcp.gotofu.com (prod EKS). Auth flow:

  1. claude.ai hits / → 401 + WWW-Authenticate: Bearer ... resource_metadata=…
  2. claude.ai fetches /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server
  3. claude.ai calls POST /register (DCR) → we return the pre-provisioned Clerk client_id
  4. claude.ai calls GET /authorize?... → tofu-mcp 302-redirects to ${CLERK_ISSUER}/oauth/authorize with all query params forwarded
  5. User auths at Clerk (PKCE) → Clerk redirects to https://claude.ai/api/mcp/auth_callback?code=…
  6. claude.ai calls POST /token → tofu-mcp forwards to ${CLERK_ISSUER}/oauth/token, injecting TOFU_MCP_CLERK_CLIENT_SECRET if needed, returns Clerk’s JWT
  7. JWT is sent on every subsequent MCP request; tofu-mcp validates iss + client_id against the Clerk app

Why the proxy #

The MCP authorization spec (2025-06-18) added RFC 9728 support so resource servers can advertise an external authorization_servers URL. claude.ai does not honor this yet (anthropics/claude-ai-mcp#82). It always constructs /authorize, /token, /register from the MCP server’s base URL, ignoring authorization_endpoint / token_endpoint in the metadata. So we run a thin proxy in src/routes/oauth-proxy.ts that forwards those calls to Clerk. ChatGPT and Claude Code CLI follow the new spec correctly and would also work against the direct discovery flow.

Clerk OAuth app setup #

The Clerk OAuth app referenced by TOFU_MCP_CLERK_CLIENT_ID must have https://claude.ai/api/mcp/auth_callback registered as an allowed redirect URI. TOFU_MCP_CLERK_CLIENT_SECRET is needed because Clerk treats this app as confidential and requires the secret on /oauth/token.

Dev workspaces are gated behind Cloudflare Access — claude.ai’s egress IPs (160.79.104.0/21) need an Allow policy on the mcp--* subdomain to bypass.

Auth model #

Two paths, same endpoint:

  • Static Bearer token (TOFU_MCP_API_TOKEN) — used by Claude Code locally and CI. Set in Doppler.
  • Clerk JWT — claude.ai web connector. tofu-mcp validates issuer and client_id (Clerk doesn’t put aud on OAuth access tokens, so we pin to the Client ID instead).

src/middleware/auth.ts is the single decision point for authenticated MCP requests. The OAuth proxy routes in src/routes/oauth-proxy.ts are intentionally unauthenticated.

Develop on it #

mise mcp-dev          # docker compose up tofu-mcp + tail logs
mise mcp-test         # bun test
mise mcp-check        # format + lint + typecheck

Code is bun-native TS — no build step. bun --watch reloads on file change inside the container.

Important env vars #

Var Where What
TOFU_MCP_DATABASE_URL Doppler Read-only Postgres URL. In dev_local docker uses database:5432; prod uses YugabyteDB Cloud with mcp_readonly user.
TOFU_MCP_API_TOKEN Doppler Static Bearer for Claude Code
TOFU_MCP_URL .env (rewritten by override-coder-env.sh in Coder, defaults to https://mcp.gotofu.com) The server’s public URL — used in WWW-Authenticate and the OAuth discovery resource field
TOFU_MCP_CLERK_JWKS_URL Doppler Clerk JWKS for JWT validation
TOFU_MCP_CLERK_ISSUER_URL Doppler Clerk issuer for JWT validation
TOFU_MCP_CLERK_CLIENT_ID Doppler OAuth app client ID, validated against the client_id claim. Also returned from the /register proxy.
TOFU_MCP_CLERK_CLIENT_SECRET Doppler OAuth app client secret, injected into the /token proxy when forwarding to Clerk

Safety (defense in depth) #

  1. Dedicated read-only Postgres user (mcp_readonly) in prod
  2. Connection-level default_transaction_read_only=true
  3. Per-query BEGIN TRANSACTION READ ONLY in execute_sql
  4. Regex blocks COMMIT/ROLLBACK/BEGIN/SAVEPOINT in execute_sql

Deploy #

EKS via Kustomize: deployment/resources/tofu-mcp/ + deployment/overlays/prod/tofu-mcp/. Same CI pipeline as every other service — image is built, tag is replaced in kustomization, kubectl apply -k rolls it out. Hosted at mcp.gotofu.com behind ALB + WAF (Anthropic egress 160.79.104.0/21 must be allowlisted).