docs: MCPJam API (preview) reference#2536
Conversation
…dation Adds the boring infrastructure that PRs 2-5 build on: - `@workos-inc/node` ^10.2.0 dependency (server-side SDK; the existing `@workos-inc/authkit-react` only covers the browser flow). - `server/services/workos-client.ts` — memoized `getWorkOSClient()` reading `WORKOS_API_KEY`. Mirrors the backend `createWorkOSClient` factory in `convex/lib/vault.ts`. - `server/middleware/request-local.ts` — typed wrappers over Hono's `c.set`/`c.get` so the bearer middleware and `authorizeBatch` can share a single WorkOS validation result per request without paying ~200ms twice. - `server/services/identity.ts` — `resolveUserByExternalId(externalId)` via `ConvexHttpClient`, matching the string-path call style used by `local-server-resolver.ts` and `servers.ts`. The `users:getByExternalId` query is added in the parallel backend PR `claude/workos-api-keys-backend`; the call is cast to bypass codegen drift until that PR lands. No call sites use these yet — wired up in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…key rate limit Adds a new `sk_` branch to `bearerAuthMiddleware` BEFORE the guest-JWT branch. Real WorkOS JWTs always begin with `eyJ`, so the prefix is unambiguous and never falls through. On `sk_…`: - Check request-local cache; otherwise call `WorkOS.apiKeys.createValidation`. ~200ms latency, single global WorkOS endpoint — memoization is non-optional because the same request later hits `authorizeBatch` and would re-validate. - Per-key token bucket (60/min sustained, burst 10) rejects floods with `429` + `Retry-After`. Rejection happens BEFORE the Convex user lookup so a flood can't tie up the DB either. - Resolve the WorkOS user id to an MCPJam user via the new `resolveUserByExternalId` helper. Set `authMethod="workos_api_key"`, `workosApiKeyId`, `workosUserId`, `mcpjamUserId` for downstream handlers (next commit consumes these in `authorizeBatch`). - Structured `auth.workos_api_key` log carries metadata only — no key value, no full token. Extends `ContextVariableMap` so the four new variables are typed. Adds `resetWorkOSRateLimitForTests` so the test in commit 6 can exercise rate-limit behavior deterministically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s when calling Convex
When `bearerAuthMiddleware` resolves the caller via a WorkOS `sk_` key,
the original bearer is the API key itself — Convex's identity layer
can't validate it (it only knows JWTs and guest tokens). Instead,
Inspector exchanges:
Authorization: Bearer <sk_…>
↓
Authorization: Bearer <INSPECTOR_SERVICE_TOKEN>
x-mcpjam-acting-as: <mcpjamUserId>
This keeps WorkOS validation at a single layer (Inspector) and reuses
the durable-worker service token pattern. The companion backend PR
(`claude/workos-api-keys-backend`) teaches Convex `requestIdentity`
to honor delegated-identity mode and audit-log every use.
Touched call sites — every Inspector→Convex `/web/*` fetch reached
from the `/api/v1/*` ephemeral-connection flow:
- `authorizeServer` (`auth.ts`) — single-server authorize.
- `authorizeBatch` (`auth.ts`) — multi-server authorize, the load-
bearing one for `/api/v1/.../tools` etc.
- `fetchRuntimeServerSecrets` (`utils/server-secrets.ts`) — secret
reveal that `createAuthorizedManager` calls inline when server has
hasHeaders=true but no inline headers.
A shared `buildConvexAuthHeaders(c, originalBearer)` helper in auth.ts
keeps the branching consistent for the two `authorize*` paths.
`fetchRuntimeServerSecrets` takes the equivalent context via a new
optional `workosApiKeyActingAs` arg so it can stay free of Hono types.
NOT touched (out of scope for `/api/v1/*` today; flagged for follow-up
if API keys start reaching them): `chat-history.ts` proxy helpers,
`hosted-oauth-refresh.ts`, `local-server-resolver.ts`. Each is on an
OAuth-flow or local-mode path that doesn't accept `sk_` bearers in v1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…OS REST
Adds a new sub-router for managing WorkOS API keys from the inspector UI.
The router calls WorkOS REST directly with the server-side `WORKOS_API_KEY`:
the Node SDK only exposes org-scoped helpers (`createOrganizationApiKey`
/ `listOrganizationApiKeys`), but v1 mints user-scoped keys via documented
user_management endpoints.
Auth gating, top-to-bottom:
1. `sessionAuthMiddleware` bypasses `/api/web/*` entirely (per
`session-auth.ts:103`), so this router explicitly attaches
`bearerAuthMiddleware`.
2. After bearer auth runs, a follow-up middleware rejects
`authMethod === "workos_api_key"` with 403. This is the privilege
isolation guard: `sk_…` keys cannot mint or revoke other `sk_…`
keys.
Endpoints:
- `POST /api/web/api-keys` `{ name }` → WorkOS user_management create.
Returns the full create response (one-shot `value`, `obfuscated_value`,
`id`). MCPJam neither persists nor logs the raw value.
- `GET /api/web/api-keys` → list for the session user/org. Returns
`{ items: [...] }` with `obfuscated_value` only.
- `DELETE /api/web/api-keys/:id` → fetches the key first, verifies
`owner.id === session.userId`, then deletes via WorkOS `DELETE
/api_keys/{id}`. A user passing another user's key id sees a 404
(not 403 — same response shape as "doesn't exist").
WorkOS userId / org_id are read from the session JWT's `sub` / `org_id`
claims (decoded payload only; signature is enforced upstream by
`bearerAuthMiddleware`'s WorkOS pass-through and by Convex). Structured
`api_key_created` / `api_key_revoked` audit logs carry the key id and
actor user id, never the value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new /settings/api-keys page where users can mint, list, and revoke WorkOS-backed API keys for the v1 public API. - ApiKeysRoute: table of existing keys with create + revoke actions. - CreateApiKeyDialog: name input + Create. - RevealOnceDialog: shows the full sk_... value once with copy-to-clipboard and a "won't be shown again" warning. Mirrors the SkillsTab copy feedback pattern (Copy → Check icon). - RevokeApiKeyDialog: type-the-key-name to confirm, mirroring the ChatboxDeleteConfirmDialog pattern. - api-keys client lib: typed wrappers (create/list/revoke) over the new /api/web/api-keys/* endpoints via authFetch. - Router: new /settings/api-keys route registered alongside /settings. - Sidebar: "API Keys" entry added to the settings section with the Key icon from lucide-react. Toasts via sonner. All session-authenticated through the bearer middleware applied by the sub-router (sk_ keys cannot reach this UI).
Focused tests for the security-critical new code in the bearer middleware: - Missing or non-Bearer Authorization header → 401. - sk_ token with WorkOS-rejected key → 401 UNAUTHORIZED (Convex identity resolution is NOT invoked in this path). - sk_ token validated but MCPJam user missing → 401 "Unknown user". - sk_ token valid → next() runs with c.set keys for authMethod, workosApiKeyId, workosUserId, mcpjamUserId. - Per-key token bucket admits >=10 burst then 429s. - Cross-key bucket isolation (sk_a drained does not affect sk_b). - Request-local memoization: the middleware running twice in a single request only invokes WorkOS validate once (covers /api/web/api-keys sub-router stacking). WorkOS SDK, identity resolver, and guest-JWT validator are mocked at module level. Uses resetWorkOSRateLimitForTests() exported from the middleware for clean state. Follow-up: dedicated test suite for /api/web/api-keys/* routes — needs JWT-claim mocking + WorkOS REST stubbing, deferred to a smaller PR for review clarity.
…' into claude/confident-fermi-egdjco
Completes the v1 public-API key flow against the merged backend resolver,
which now requires BOTH x-mcpjam-acting-as (WorkOS externalId) and
x-mcpjam-acting-in-org (Convex org id).
Bearer middleware:
- After resolving the MCPJam user, look up the key's org binding (memoized
per request). Missing binding -> 401 ORPHANED_KEY. Store the org id in
Hono context.
Delegated Convex calls (auth.ts + server-secrets.ts):
- Send x-mcpjam-acting-as = WorkOS user id (was incorrectly the Convex user
id, which the backend resolver would 404), plus x-mcpjam-acting-in-org.
API key mint/revoke (routes/web/api-keys.ts):
- Create accepts { name, organizationId }, resolves the Convex user id, and
writes the backend binding; on binding failure the WorkOS key is revoked.
- Revoke removes the binding best-effort.
Client:
- Create dialog adds MCPJam org selection (auto-selects a sole org, requires
explicit choice when there are several); posts organizationId.
New service client for the backend binding endpoints. Tests: orphaned-key
401, acting-as = WorkOS id (not Convex id), both headers present; fixed the
sk_ validate mock (createValidation) and made auth Context test mocks
faithful (c.get).
https://claude.ai/code/session_011asM4GkyWv5TQp35PAC3cC
Return the canonical UNAUTHORIZED code for an unbound WorkOS key and carry the specific reason in the opaque details bag (details.reason = "ORPHANED_KEY") instead of inventing a new wire code. ORPHANED_KEY is not in V1_ERROR_CODES (routes/v1/contract.ts), and the v1 boundary mapper would otherwise collapse it to INTERNAL_ERROR; the envelope already allows details. Removes the ORPHANED_KEY entry from the internal ErrorCode enum and updates the test to assert code=UNAUTHORIZED + details.reason=ORPHANED_KEY. https://claude.ai/code/session_011asM4GkyWv5TQp35PAC3cC
…EX_URL resolveUserByExternalId hard-required process.env.CONVEX_URL, but Inspector boot only requires CONVEX_HTTP_URL; a deployment honoring that contract would throw 'CONVEX_URL is not set', 500-ing all sk_ API-key auth and minting. Use getInspectorClientRuntimeConfig().convexUrl (the .convex.cloud URL derived from CONVEX_HTTP_URL, matching the rest of the server), falling back to CONVEX_URL. Also harden the result type guard to require a non-null object. https://claude.ai/code/session_011asM4GkyWv5TQp35PAC3cC
The /api/web/api-keys routes decoded the bearer's payload WITHOUT verifying the signature and trusted claims.sub to drive WorkOS key mint/list/revoke with the server's admin key. Unlike other /api/web/* routes, these never forward the bearer to Convex, so nothing verified it — a forged bearer could target another user's WorkOS id for key lifecycle operations. - Add services/authkit-jwt.ts: verify the WorkOS AuthKit access token via JWKS, pinning issuer + audience(client id) + RS256 + exp/nbf. Issuer/JWKS/audience set mirrors mcpjam-backend convex/auth.config.ts so Inspector accepts exactly the tokens Convex already accepts. Only sub/org_id are read from verified claims; guest issuer excluded (key management is JWT-only). - api-keys resolveSessionContext now awaits verification and returns 401 before any WorkOS or Convex-binding side effect; 500 only on missing config. - Tests: forged (untrusted key), unsigned (alg none), wrong issuer, wrong audience, expired, malformed, valid. Declare jose as a direct dependency. https://claude.ai/code/session_011asM4GkyWv5TQp35PAC3cC
… once per request Addresses two Cursor Bugbot findings on the api-key management surface: - api-keys: move the sk_ privilege-isolation guard BEFORE bearerAuthMiddleware. sk_ keys can never manage keys, and the sk_ prefix is the same discriminator the middleware uses, so an up-front 403 is equivalent to the old post-validation authMethod check while skipping a ~200ms WorkOS validate plus Convex identity/binding lookups on a request that always 403s. Also yields a uniform 403 for invalid/revoked keys (no validity probing via 401-vs-403). - bearer-auth: memoize the per-key WorkOS rate-limit debit per request (workosRateLimitConsumed), matching the existing validation/binding memo, so the limit can't be double counted if the middleware ever runs on both a parent and child router. Auth-cluster suite green (48 tests), including the rate-limit burst test. https://claude.ai/code/session_011asM4GkyWv5TQp35PAC3cC
Document the v1 public API exactly as it ships today, flagged as preview per standard practice for unannounced surfaces (may change, path-versioned, tolerant-reader guidance, changelog pointer): - New reference/public-api page: base URL, sk_ key auth (Settings -> API keys, one-time reveal, org scoping, revocation), envelope + cursor pagination, the 10-code error contract with status mapping, per-key rate limits (60/min, burst 10, Retry-After), and the 8 live-MCP endpoints (validate, doctor, check-oauth, tools/prompts/resources list, resources/read, export) with curl examples. Honest 'not in the API yet' list (no tool execution, no listing endpoints, key management UI-only). - reference/api-keys: add the sk_ MCPJam API key as the fourth key type with its error signatures; update quick-reference table. - docs.json: add the page to the SDK tab reference group. https://claude.ai/code/session_011asM4GkyWv5TQp35PAC3cC
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Internal previewPreview URL: https://mcp-inspector-pr-2536.up.railway.app |
WalkthroughThis PR adds documentation for the MCPJam Preview API ( Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/reference/api-keys.mdx (1)
3-3:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUpdate description to match "four" key types.
The frontmatter description still references "three different things" but the content now documents four key types. This creates confusion for users and search engines.
📝 Proposed fix
-description: "The three different things called 'API key' in MCPJam, and when you need each one." +description: "The four different things called 'API key' in MCPJam, and when you need each one."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/reference/api-keys.mdx` at line 3, Update the frontmatter description string that currently reads "The three different things called 'API key' in MCPJam, and when you need each one." to reflect four key types (e.g., "The four different things called 'API key' in MCPJam, and when you need each one.") so it matches the document body that documents four key types; ensure the frontmatter description value (the description key) is changed to mention "four" and remains concise and consistent with the rest of the content.
🧹 Nitpick comments (1)
docs/reference/public-api.mdx (1)
48-54: ⚡ Quick winConsider clarifying the
$MCPJAM_API_KEYvariable name.The curl examples use
$MCPJAM_API_KEYfor the newsk_…bearer key, butapi-keys.mdxdocumentsMCPJAM_API_KEYas the CLI/SDK authentication token (a different key type with different scoping). While context makes clear these examples are for the API key, users scanning both pages might conflate them.Either use a distinct variable name in examples (e.g.,
$MCPJAM_SK_KEYor$MCPJAM_API_SECRET_KEY) or add a brief note that this is thesk_…API key, not the CLI token.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/reference/public-api.mdx` around lines 48 - 54, Update the curl examples in public-api.mdx to avoid confusing the CLI token with the new sk_… API key by either renaming the environment variable (e.g., use $MCPJAM_SK_KEY or $MCPJAM_API_SECRET_KEY instead of $MCPJAM_API_KEY) and updating all occurrences in the example, or add a single inline note next to the Authorization header that clarifies this must be the sk_… API key (not the CLI/SDK token documented in api-keys.mdx); ensure the change is applied to the curl snippet shown and any adjacent explanatory text so readers won’t conflate the two keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@docs/reference/api-keys.mdx`:
- Line 3: Update the frontmatter description string that currently reads "The
three different things called 'API key' in MCPJam, and when you need each one."
to reflect four key types (e.g., "The four different things called 'API key' in
MCPJam, and when you need each one.") so it matches the document body that
documents four key types; ensure the frontmatter description value (the
description key) is changed to mention "four" and remains concise and consistent
with the rest of the content.
---
Nitpick comments:
In `@docs/reference/public-api.mdx`:
- Around line 48-54: Update the curl examples in public-api.mdx to avoid
confusing the CLI token with the new sk_… API key by either renaming the
environment variable (e.g., use $MCPJAM_SK_KEY or $MCPJAM_API_SECRET_KEY instead
of $MCPJAM_API_KEY) and updating all occurrences in the example, or add a single
inline note next to the Authorization header that clarifies this must be the
sk_… API key (not the CLI/SDK token documented in api-keys.mdx); ensure the
change is applied to the curl snippet shown and any adjacent explanatory text so
readers won’t conflate the two keys.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: aac92a18-337c-4dbf-a083-e1e44118c8ff
📒 Files selected for processing (3)
docs/docs.jsondocs/reference/api-keys.mdxdocs/reference/public-api.mdx
Summary
Documents the v1 public API (shipped in #2523) exactly as it exists today, flagged as Preview per standard practice for an unannounced surface that may still change.
What's in it
New page:
reference/public-api.mdx(sidebar-tagged "Preview")https://app.mcpjam.com/api/v1) andsk_…key auth: creation in Settings → API keys, one-time reveal, org scoping at mint, immediate revocation, key-hygiene guidanceORPHANED_KEY401 path{items, nextCursor}collection /{code, message, details}error envelopes, opaque cursor paginationOAUTH_REQUIREDvsUNAUTHORIZEDdistinction), additive-codes noteRetry-After+ backoff guidanceUpdated
reference/api-keys.mdx— adds thesk_…MCPJam API key as the fourth key type with its error signatures; quick-reference table updated.docs.json— page added to the SDK tab's Reference group.Accuracy notes
Everything stated was verified against the shipped code rather than the design docs: endpoint list from
server/routes/v1/*, error contract fromroutes/v1/contract.ts, rate-limit numbers frommiddleware/bearer-auth.ts, key restrictions fromroutes/web/api-keys.ts. Notably, the Convex product-state routes (/v1/me,/v1/projects, …) are deliberately not documented — they don't acceptsk_keys today (Inspector-delegated or session-JWT only), so documenting them under key auth would be wrong; the page lists project/server listing under "not yet" instead.mint broken-linkspasses for all touched pages (5 pre-existing broken links in untouchedcontributing/*pages remain).https://claude.ai/code/session_011asM4GkyWv5TQp35PAC3cC
Generated by Claude Code
Note
Low Risk
Docs-only changes with no runtime, auth, or data-path impact.
Overview
Adds preview documentation for the shipped v1 public API at
https://app.mcpjam.com/api/v1, linked from the SDK tab Reference group indocs.json.The new
reference/public-api.mdxpage coverssk_…bearer auth, org scoping, response/error conventions, rate limits (60/min, burst 10), all eight live POST endpoints (validate, doctor, check-oauth, tools/prompts/resources, resources/read, export), explicit “not in the API yet” scope, and versioning/stability expectations for preview.reference/api-keys.mdxis updated from three to four key types: a dedicated section for the MCPJam API key (sk_…) with common 401/403/429 errors, plus a new row in the quick-reference table pointing at the public API page.Reviewed by Cursor Bugbot for commit d90410a. Bugbot is set up for automated code reviews on this repo. Configure here.