Skip to main content
Glama
oktaforai-okta

ProGear MCP Servers

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

/inventory/mcp

inventory:read, inventory:write, inventory:alert

list_products, search_inventory, check_stock, get_low_stock_alerts, get_inventory_summary, update_inventory_quantity

/customer/mcp

customer:read, customer:lookup, customer:history

get_customer, search_customers, get_customers_by_tier, get_top_customers, get_customer_summary

/sales/mcp

sales:read, sales:quote, sales:order

list_orders, get_order, get_pipeline, create_quote, create_order, cancel_order

/pricing/mcp

pricing:read, pricing:margin, pricing:discount

get_price, get_category_pricing, calculate_bulk_price, get_discount_structure

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-pricing

See .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

npm install && npm run build

Start Command

node packages/gateway/dist/server.js

Health Check Path

/health

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/mcp

Then in Claude Code: /mcp → select progear-inventoryAuthenticate. 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/mcp

Okta 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 a redirect_uri mismatch, read the actual redirect_uri out 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>"

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Exposes ERP data (production orders, inventory, BOM, customers) to any MCP client through a backend-neutral interface, with a mock backend for testing.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    A 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.
  • F
    license
    Not graded
    quality
    B
    maintenance
    MCP server that enables AI platforms to search products, customers, and warehouses, and prepare and submit sales orders to a fixed ERP endpoint with per-session bearer authentication.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/oktaforai-okta/progear-mcp-servers'

If you have feedback or need assistance with the MCP directory API, please join our Discord server