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) #
- Run
mise dev(ormise mcp-dev) inside your Coder workspace. tofu-mcp comes up as thetofu-mcpdocker compose service on port3002. - The project-scoped
.mcp.jsonalready points Claude Code athttp://localhost:3002with the staticTOFU_MCP_API_TOKENheader. claude mcp listshould showtofu-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:
- claude.ai hits
/→ 401 +WWW-Authenticate: Bearer ... resource_metadata=… - claude.ai fetches
/.well-known/oauth-protected-resourceand/.well-known/oauth-authorization-server - claude.ai calls
POST /register(DCR) → we return the pre-provisioned Clerkclient_id - claude.ai calls
GET /authorize?...→ tofu-mcp 302-redirects to${CLERK_ISSUER}/oauth/authorizewith all query params forwarded - User auths at Clerk (PKCE) → Clerk redirects to
https://claude.ai/api/mcp/auth_callback?code=… - claude.ai calls
POST /token→ tofu-mcp forwards to${CLERK_ISSUER}/oauth/token, injectingTOFU_MCP_CLERK_CLIENT_SECRETif needed, returns Clerk’s JWT - JWT is sent on every subsequent MCP request; tofu-mcp validates
iss+client_idagainst 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 putaudon 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) #
- Dedicated read-only Postgres user (
mcp_readonly) in prod - Connection-level
default_transaction_read_only=true - Per-query
BEGIN TRANSACTION READ ONLYinexecute_sql - Regex blocks
COMMIT/ROLLBACK/BEGIN/SAVEPOINTinexecute_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).