anantgarg

MCP Gateway

Community anantgarg
Updated

MCP Gateway

A self-hosted MCP gateway with Google login and role-based access control. One Next.js app provides:

  • An MCP endpoint (/api/mcp, Streamable HTTP) that MCP clients like Claude and Cursor connect to. The first time a user performs any action, the standard MCP OAuth 2.1 flow sends them to Google to sign in — no tokens to copy around.
  • An admin dashboard (shadcn/ui) where the admin configures Zendesk and Chargebee connectors, invites users, and defines roles that control exactly which tools each user gets.

How access control works

  • The first user ever to sign in becomes admin. Admins can use every tool of every connector and manage the dashboard.
  • Everyone else must be invited by email first — un-invited Google accounts are rejected at sign-in.
  • Each user has one role. A role grants access to specific connectors with one of three levels:
    • Read — only that connector's read tools
    • Read + Write — all of its tools
    • Custom — an explicit per-tool selection
  • Enforcement happens at the MCP layer: tools/list only ever contains the tools your role grants, and every tools/call re-checks the database, so permission changes apply immediately.

Connectors are instances, not singletons — you can add two Zendesk accounts and grant a role access to only one of them. Tool names embed the connector slug, e.g. zendesk_support_search_tickets.

Setup

1. Prerequisites

  • Node.js 20+
  • PostgreSQL (docker compose up -d starts one locally)

2. Environment

cp .env.example .env

Fill in:

Variable Value
DATABASE_URL PostgreSQL connection string (the docker-compose default works out of the box)
BETTER_AUTH_SECRET openssl rand -base64 32
BETTER_AUTH_URL The app's public base URL, e.g. http://localhost:3000
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET See below
ENCRYPTION_KEY openssl rand -hex 32 — encrypts connector API keys at rest (AES-256-GCM)

Google credentials: in Google Cloud Console, create an OAuth client of type Web application and add the authorized redirect URI:

{BETTER_AUTH_URL}/api/auth/callback/google

3. Database & run

npm install
npm run db:migrate
npm run dev

Open http://localhost:3000, sign in with Google — you're now the admin.

4. Configure

  1. Connectors → add Zendesk (subdomain + agent email + API token) or Chargebee (site + API key). Use Test connection before saving.
  2. Roles → create a role and pick an access level per connector.
  3. Users → invite teammates by email and assign their role.

5. Connect an MCP client

claude mcp add --transport http gateway http://localhost:3000/api/mcp

or in a JSON-config client (Cursor, Claude Desktop):

{ "mcpServers": { "gateway": { "type": "http", "url": "http://localhost:3000/api/mcp" } } }

The client discovers the OAuth metadata, registers itself dynamically, and opens a browser for Google sign-in. Consent is automatic after login.

Tool catalog

Connector Read Write
Zendesk search_tickets, get_ticket, list_ticket_comments, list_users, get_user create_ticket, update_ticket, add_ticket_comment
Chargebee list_customers, get_customer, list_subscriptions, get_subscription, list_invoices, get_invoice create_customer, update_customer, cancel_subscription

Chargebee's cancel_subscription targets Product Catalog 2.0 sites (cancel_for_items).

Architecture notes

  • Auth: better-auth with the Google social provider for the dashboard and its mcp plugin as the OAuth 2.1 authorization server (discovery metadata, dynamic client registration, PKCE) for MCP clients. Both share one user table.
  • MCP serving: a small built-in Streamable HTTP handler (src/app/api/mcp/route.ts, stateless JSON responses per the MCP spec) that runs on both Node and Cloudflare Workers; the tool list is rebuilt per request from the caller's current role.
  • Secrets: connector credentials are AES-256-GCM encrypted with ENCRYPTION_KEY; they are never sent to the browser.
  • DB: Drizzle ORM + PostgreSQL. npm run auth:generate regenerates src/db/auth-schema.ts after changing the better-auth config; npm run db:generate && npm run db:migrate create and apply migrations.

Dev-only login

Setting ENABLE_DEV_LOGIN=true in .env (ignored in production builds) enables email/password endpoints so you can exercise the dashboard and MCP flow without Google credentials — useful for local testing with multiple fake users.

Deploying to Cloudflare Workers

The app is pre-configured for Cloudflare via @opennextjs/cloudflare (wrangler.jsonc, open-next.config.ts). The gateway stays private on Workers exactly as it is locally: every dashboard page and MCP call requires Google sign-in, only invited emails can log in, and the dev email/password login is compiled out of production builds.

1. You need a Postgres reachable from Cloudflare — e.g. Neon, Supabase, or any managed Postgres. Run the migrations against it once:

DATABASE_URL="postgresql://user:pass@host/db" npm run db:migrate

2. Create a Hyperdrive config (Cloudflare's connection pooler; the app reads its connection string automatically from the HYPERDRIVE binding):

npx wrangler login
npx wrangler hyperdrive create mcp-gateway-db --connection-string="postgresql://user:pass@host/db"

Paste the returned id into wrangler.jsonc under hyperdrive[0].id, and set vars.BETTER_AUTH_URL to your production URL (e.g. https://mcp-gateway.<your-subdomain>.workers.dev).

3. Set secrets:

npx wrangler secret put BETTER_AUTH_SECRET   # openssl rand -base64 32
npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put GOOGLE_CLIENT_SECRET
npx wrangler secret put ENCRYPTION_KEY       # openssl rand -hex 32

4. Add the production redirect URI to your Google OAuth client:https://<your-domain>/api/auth/callback/google

5. Deploy:

npm run deploy

Then open the deployed URL, sign in with Google (first login = admin), and point MCP clients at https://<your-domain>/api/mcp.

To test the Workers runtime locally before deploying: npm run preview (uses .dev.vars — copy from .dev.vars.example — and the localConnectionString in wrangler.jsonc).

Notes:

  • scripts/patch-pg-cloudflare.mjs (run automatically on npm install) fixes a broken published build of pg's Cloudflare socket adapter; without it the Workers bundle fails with Could not resolve "pg-cloudflare".
  • Old MCP access tokens expire after 1 hour; clients refresh automatically.

MCP Server · Populars

MCP Server · New

    ronak-create

    FableCut

    Zero-dependency browser video editor that AI agents can drive — JSON timeline, MCP + REST, live-reloading UI

    Community ronak-create
    runesleo

    Polymarket Toolkit

    CLI + AI Skills workflow for profiling Polymarket traders from an address

    Community runesleo
    ohad6k

    Emulo

    Mine your Claude Code and Codex logs into a local you.md agent profile.

    Community ohad6k
    vshulcz

    deja-vu

    Memory layer for coding agents: search, MCP recall, auto-context, secret redaction, stats, share and sync over the session logs Claude Code, Codex and opencode already write. One zero-dep binary.

    Community vshulcz
    Wh1isper

    mcp-email-server

    IMAP and SMTP via MCP Server

    Community Wh1isper