ProGear MCP Servers
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ProGear MCP ServersCheck current inventory levels for basketballs."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ProGear MCP Servers
One MCP gateway for the ProGear basketball-equipment demo, hosting four domains — Inventory, Customer, Sales, and Pricing — each speaking the real MCP protocol (Streamable HTTP transport) and secured by your own Okta org (its own Custom Authorization Server + scope set per domain).
This is a deliberately smaller sibling of ProGearSalesAI: no Auth0 FGA, no LangGraph orchestrator, no frontend. The gateway itself just validates whatever bearer token it's given — it doesn't care how the caller got it. packages/local-tester (local-only, not deployed) demonstrates one way a caller might: human PKCE login + an agent doing the ID-JAG exchange to mint that token, mirroring the original app's Cross-App Access flow.
Deployment shape
One process, one Render service, one build/start command. packages/gateway mounts all 4 domains at different paths behind a single Express app:
Mount | Scopes | Tools |
|
|
|
|
|
|
|
|
|
|
|
|
Each mount validates against its own Okta Custom Authorization Server (different issuer/audience per domain) even though they all run in the same process — a token issued for the inventory auth server can't be used against /customer/mcp, and within a mount, each tool call checks its required scope against the granted scopes in the caller's token (a token missing inventory:write can call check_stock but not update_inventory_quantity).
packages/mcp-inventory, mcp-customer, mcp-sales, mcp-pricing also still work as standalone servers (their own server.ts + /mcp + /health, single-domain env vars) if you ever want to split them back into separate deployments — the gateway just imports each one's tool-registration logic (./tools export) and mounts it under its own auth config instead of calling .listen() itself.
Related MCP server: Auth0 MCP Server Demo — CRM
Data
Seeded from a ported snapshot of the ProGearSalesAI demo dataset: 90 inventory SKUs, 90 pricing entries, 34 customers, tier/volume discount tables (packages/shared/src/data/initial_data.json). Sales orders/quotes are in-memory only. All state resets on process restart — this is a tools server, not a system of record.
Project structure
packages/
shared/ # ported data + store, JWKS auth + scope enforcement, HTTP/MCP transport helper
mcp-inventory/ mcp-customer/ mcp-sales/ mcp-pricing/ # tool definitions + standalone entrypoint each
gateway/ # the actual deployment: mounts all 4 at /inventory, /customer, /sales, /pricing
local-tester/ # local-only: PKCE login + agent ID-JAG exchange, calls the deployed gateway (see its own README)Local development
npm install
npm run build # builds shared + all 4 domains + gateway, in dependency order
npm run dev:gateway # tsx watch, all 4 mounts on one port (default 3000)Without the relevant Okta env vars set for a mount, that mount returns 500 on every /mcp request unless you set ALLOW_INSECURE=true, which skips token validation and grants every scope on every mount — local dev only, never set this in a deployed environment.
Okta setup
Set OKTA_DOMAIN once, plus OKTA_<DOMAIN>_AUTH_SERVER_ID + OKTA_<DOMAIN>_AUDIENCE per domain — these are the exact same env var names already used in the ProGearSalesAI backend (OKTA_CUSTOMER_AUTH_SERVER_ID, OKTA_INVENTORY_AUDIENCE, etc.), so existing values can be copied over as-is:
OKTA_DOMAIN=https://your-org.okta.com
OKTA_INVENTORY_AUTH_SERVER_ID=... OKTA_INVENTORY_AUDIENCE=api://progear-inventory
OKTA_CUSTOMER_AUTH_SERVER_ID=... OKTA_CUSTOMER_AUDIENCE=api://progear-customer
OKTA_SALES_AUTH_SERVER_ID=... OKTA_SALES_AUDIENCE=api://progear-sales
OKTA_PRICING_AUTH_SERVER_ID=... OKTA_PRICING_AUDIENCE=api://progear-pricingSee .env.example for the full list, including which vars from a ProGearSalesAI-style .env don't apply here (Anthropic key, CORS, the AI Agent's own private key/client ID — this gateway validates incoming tokens, it doesn't self-issue any). Tokens are validated by signature + issuer + audience against each domain's own Okta JWKS endpoint (jose's createRemoteJWKSet) — no shared secret needed on this side.
Deploying to Render
Single service, either via the dashboard or the included Blueprint.
Manual (New → Web Service):
Field | Value |
Language | Node |
Root Directory | (blank — npm workspaces monorepo, build runs from repo root) |
Build Command |
|
Start Command |
|
Health Check Path |
|
Blueprint: render.yaml at the repo root defines the same single progear-mcp-gateway service — New → Blueprint, point at this repo, then fill in the 9 Okta env vars it prompts for (marked sync: false).
Connecting an agent
Each mount exposes MCP over Streamable HTTP at POST/GET/DELETE <mount>/mcp (stateless — no session persistence across requests) plus its own GET <mount>/health; there's also a top-level GET /health listing all mounts.
Get an access token from Okta for the relevant Custom Authorization Server + scopes (e.g. client-credentials grant for a service/agent identity), then:
Claude Code CLI:
claude mcp add --transport http progear-inventory \
https://<your-render-url>/inventory/mcp \
--header "Authorization: Bearer <token>"Repeat per domain (/customer/mcp, /sales/mcp, /pricing/mcp) with a token scoped to that domain's audience.
Any other MCP client / agent SDK: point it at the mount's /mcp URL with an Authorization: Bearer <token> header on every request.
OAuth discovery (no static token)
Clients that implement the MCP authorization spec can find Okta on their own instead of being handed a token. Each mount publishes RFC 9728 protected-resource metadata at the root of the gateway, path-scoped to the endpoint it describes:
GET /.well-known/oauth-protected-resource/inventory/mcp
GET /.well-known/oauth-protected-resource/customer/mcp
GET /.well-known/oauth-protected-resource/sales/mcp
GET /.well-known/oauth-protected-resource/pricing/mcp{
"resource": "https://<your-render-url>/inventory/mcp",
"authorization_servers": ["https://your-org.okta.com/oauth2/<inventory-auth-server-id>"],
"scopes_supported": ["inventory:read", "inventory:write", "inventory:alert"],
"bearer_methods_supported": ["header"],
"resource_name": "ProGear Inventory MCP"
}A 401 from /mcp now also carries the pointer, so a client that calls the endpoint cold learns where to authenticate:
WWW-Authenticate: Bearer resource_metadata="https://<your-render-url>/.well-known/oauth-protected-resource/inventory/mcp"(When a token was presented but failed validation, the challenge additionally carries error="invalid_token" and error_description.)
The URLs in these documents are derived from the incoming request (X-Forwarded-Proto + Host, with trust proxy on — correct on Render). Set PUBLIC_BASE_URL=https://<your-render-url> only if something in front of the gateway rewrites the Host header.
To let a client complete the flow, register an OIDC public client (Authorization Code + PKCE) in your Okta org, add the client's redirect URI (Claude.ai uses https://claude.ai/api/mcp/auth_callback), and grant the domain's scopes in that Custom Authorization Server's access policy.
Two things to note about these documents: there is no unscoped /.well-known/oauth-protected-resource — it would be ambiguous across four resources, so it 404s on purpose — and the gateway serves no /.well-known/oauth-authorization-server. That second document comes from Okta at {issuer}/.well-known/oauth-authorization-server, which is what the client follows next. Both endpoints exist only on the gateway; the standalone per-domain dev servers (packages/mcp-*) don't have them.
Connecting Claude Code with a pre-registered client ID
claude mcp add takes a client_id, plus a fixed callback port — which you need, because Okta requires exact redirect URIs and won't accept a random port:
claude mcp add --transport http \
--client-id 0oaXXXXXXXXXXXXXXXX \
--callback-port 33418 \
progear-inventory https://progear-mcp-servers.onrender.com/inventory/mcpThen in Claude Code: /mcp → select progear-inventory → Authenticate. That fetches the protected-resource document above, redirects you to the inventory Custom AS, and stores the token.
Repeat per domain (one Okta app can serve all four):
claude mcp add --transport http --client-id 0oaXXXXXXXXXXXXXXXX --callback-port 33418 \
progear-customer https://progear-mcp-servers.onrender.com/customer/mcp
claude mcp add --transport http --client-id 0oaXXXXXXXXXXXXXXXX --callback-port 33418 \
progear-sales https://progear-mcp-servers.onrender.com/sales/mcp
claude mcp add --transport http --client-id 0oaXXXXXXXXXXXXXXXX --callback-port 33418 \
progear-pricing https://progear-mcp-servers.onrender.com/pricing/mcpOkta side, before that works:
An OIDC app of type Native or SPA (public client): grant types Authorization Code + Refresh Token, PKCE required, no client secret.
Sign-in redirect URI
http://localhost:33418/callback. If auth fails with aredirect_urimismatch, read the actualredirect_uriout of the authorize URL in your browser's address bar and register that instead.In each of the four Custom Authorization Servers, an access-policy rule that allows this client and grants that domain's scopes.
Okta ignores the RFC 8707 resource parameter Claude sends and stamps aud from the Custom AS's own audience setting — which is exactly why the per-domain AS split works here without extra wiring.
If you'd rather skip OAuth entirely for a quick test, the static-token path still works:
claude mcp add --transport http progear-inventory \
https://progear-mcp-servers.onrender.com/inventory/mcp \
--header "Authorization: Bearer <token from the demo app>"This server cannot be deployed
Maintenance
Related MCP Connectors
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Marketplace gateway: 100+ services and 1,400+ tools behind one MCP connection with unified auth
Unified gateway exposing 150+ tools across all NexGenData MCP servers via one endpoint.
Self-hosted federated MCP gateway: one OAuth 2.1 MCP server in front of N apps, user-level scopes.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceDemonstrates MCP remote authentication boundary with OAuth 2.0, Keycloak token introspection, audience and scope validation, and protected tools.-
- FlicenseNot gradedqualityCmaintenanceA secure MCP server for CRM operations (contacts and deals) with Auth0 OIDC authentication, role-based access control (sales-rep read-only vs sales-manager full access), and on-behalf-of token exchange.-
- FlicenseNot gradedqualityBmaintenanceAn MCP server for inventory management (list, add, delete, comment on items) that demonstrates Auth0 for MCP capabilities including JWT bearer auth, fine-grained authorization with FGA, CIBA step-up approval, and Token Vault integration.1-
- FlicenseNot gradedqualityCmaintenanceA remote MCP server on Cloudflare Workers protected by Okta custom authorization server, demonstrating tool-level authorization via JWT access tokens and scopes/groups.-