9 tools, one binary
Routine reads, exercise CRUD, profile reads, multi-tenant branding, tenant user listings. All exposed as MCP tools through a single Node process.
MCP/Integration · v1.0
Nine tools. One Node process speaking the Model Context Protocol over HTTP, with per-tenant API keys at the front door. Your members keep using your app. Your data stays in your Supabase. Our intelligence drops in via any MCP-aware agent you already run.
01/What you get
Routine reads, exercise CRUD, profile reads, multi-tenant branding, tenant user listings. All exposed as MCP tools through a single Node process.
Streamable HTTP transport on /mcp. Each request carries an Authorization: Bearer key that we hash against tenant_api_keys and resolve to a tenant_id. stdio mode is still there as a --stdio flag for local dev.
Every Postgres query is auto-scoped to the tenant resolved from the API key. A key for gym A cannot read or mutate gym B's users, routines, or branding — even if it asks for them by ID.
02/Architecture
Three actors, two boundaries. The MCP host authenticates with a bearer key the server resolves to a tenant. The server uses a service-role key to talk to Supabase. Tenant isolation is automatic, not a host responsibility.
┌──────────────────────┐ HTTPS ┌───────────────────┐ HTTPS ┌──────────────┐ │ MCP host │ ──────▶ │ gohan-ai server │ ──────▶ │ Supabase │ │ Claude Desktop, │ ◀────── │ POST /mcp │ ◀────── │ (Postgres) │ │ your gym backend, │ bearer │ verify API key → │ tenant- │ │ │ custom agent… │ gk_live_│ resolve tenant_id │ scoped │ │ └──────────────────────┘ └───────────────────┘ └──────────────┘
03/Install
Node ≥ 18, a Supabase project with the Gohan AI schema, and a tenant API key issued from tenant_api_keys. The server starts even with missing env — auth fails on the first request so your host can decide what to do.
# clone this repo, then cd mcp-server npm install npm run build # emits dist/index.js SUPABASE_URL =https://your-project.supabase.co \ SUPABASE_SERVICE_ROLE_KEY =your-service-role \ PORT =3000 \ node dist/index.js # HTTP transport listening on /mcp
import { Client } from '@modelcontextprotocol/sdk/client/index.js' ; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' ; const transport = new StreamableHTTPClientTransport( new URL( 'https://mcp.your-gym.com/mcp' ), { requestInit: { headers: { 'Authorization' : `Bearer ${process.env.GOHAN_API_KEY}` } }, }); const client = new Client({ name: 'my-gym-app' , version: '1.0.0' }, { capabilities: {} }); await client.connect(transport); const result = await client.callTool({ name: 'get_user_routine' , arguments: { user_id } });
# stdio bypasses API-key auth — local dev only { "mcpServers": { "gohan-ai": { "command": "node", "args": ["/path/to/mcp-server/dist/index.js", "--stdio" ], "env": { "SUPABASE_URL": "https://your-project.supabase.co", "SUPABASE_SERVICE_ROLE_KEY": "your-service-role" } } } }
04/Tool reference
Read tools return JSON-stringified payloads. Write tools return a human-readable status string. Errors are returned in the same shape with the text prefixed by Error:. Tools never throw to the host. Any tool that takes user_id also accepts an X-External-Id request header — handy if you keep your own user IDs.
get_user_routineRead a member's active routine — days, exercises, ai_reasoning.
list_exercises_for_daySame shape, scoped to one day of the week.
update_exerciseMutate sets / reps / weight / notes. Only fields passed are touched.
add_exerciseAppend to the end of a routine day. order_index auto-assigned.
remove_exerciseDelete an exercise. Siblings keep their order_index — gaps are fine.
replace_exerciseSwap an exercise while keeping its position. Internally delete + insert (new UUID).
get_user_profileFull profile row — display_name, fitness_level, goals, training_days_per_week.
get_tenant_infoBrand colour + logo + slug for a gym. Drives client theming per tenant.
list_tenant_usersAll users belonging to a gym. Useful for admin dashboards.
05/Integration patterns
Your backend runs an LLM agent that orchestrates your business logic plus Gohan tools over HTTP with a single tenant API key. End users only ever talk to your backend — the API key never leaves your infrastructure. Production default.
Connect via @modelcontextprotocol/sdk Streamable HTTP transport. One tenant key per host. Tenant scoping is automatic — your code never has to filter by tenant_id.
Drop the JSON config with --stdio into claude_desktop_config.json and restart. The 9 tools appear under gohan-ai. stdio bypasses API-key auth — useful for prototyping, not for production.
06/Security