MCP/Integration · v1.0

Plug Gohan AI
into your app.

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

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.

HTTP + bearer auth

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.

Tenant-scoped by default

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

Your host.
Our brain.

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  │              │
└──────────────────────┘         └───────────────────┘         └──────────────┘
  • ·Transport: Streamable HTTP. POST /mcp on port 3000 by default. Stateless — each request authenticates on its own.
  • ·Host ↔ server: Authorization: Bearer gk_live_… hashed (SHA-256) against tenant_api_keys.
  • ·Server ↔ Supabase: SUPABASE_SERVICE_ROLE_KEY. Bypasses RLS — every query is filtered by the resolved tenant_id.
  • ·Local dev only: --stdio flag swaps the transport for stdin/stdout and skips API-key auth (no tenant scoping).

03/Install

Build, run, connect.
Then it’s yours.

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.

Build & run
# 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
Connect from your MCP host (Node + Streamable HTTP)
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 } });
Local dev / Claude Desktop (stdio mode, no auth)
# 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

Nine tools.
Read, mutate, brand.

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.

01get_user_routine

Read a member's active routine — days, exercises, ai_reasoning.

  • user_id:string
02list_exercises_for_day

Same shape, scoped to one day of the week.

  • user_id:string
  • day_of_week:0–6
03update_exercise

Mutate sets / reps / weight / notes. Only fields passed are touched.

  • exercise_id:string
  • sets / reps / weight_kg / notes:optional
04add_exercise

Append to the end of a routine day. order_index auto-assigned.

  • routine_day_id:string
  • exercise_name:string
05remove_exercise

Delete an exercise. Siblings keep their order_index — gaps are fine.

  • exercise_id:string
06replace_exercise

Swap an exercise while keeping its position. Internally delete + insert (new UUID).

  • exercise_id:string
  • exercise_name:string
07get_user_profile

Full profile row — display_name, fitness_level, goals, training_days_per_week.

  • user_id:string
08get_tenant_info

Brand colour + logo + slug for a gym. Drives client theming per tenant.

  • tenant_slug:string
09list_tenant_users

All users belonging to a gym. Useful for admin dashboards.

  • tenant_slug:string

05/Integration patterns

Three ways to wire it.
Pick the trust boundary you want.

01

Gym backend agent

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.

02

Your own MCP host

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.

03

Claude Desktop (dev)

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

One key per tenant.
Server-side only.

  • 01Each tenant gets its own gk_live_* API key. Compromise of one key exposes one tenant — never the whole cluster. Rotate by inserting a new row and revoking the old via revoked_at.
  • 02SUPABASE_SERVICE_ROLE_KEY lives only inside the MCP server process. Tools bypass RLS, but queries are auto-filtered by the tenant_id resolved from the bearer key — your host code never has to remember to filter.
  • 03Run inside a trusted environment (your backend, not end-user devices). The bearer key is not for browsers — it gives full tenant-wide access. End users authenticate to your app; your app authenticates to Gohan.
  • 04user_id and X-External-Id must resolve to a profile inside the same tenant as the API key. Cross-tenant access errors out instead of returning a stranger’s data.