Symptom (from a paying customer support ticket, 2026-07-05)
Freshly generated dashboard API keys (wm_…, API_STARTER plan, apiAccess: true) are rejected on POST /mcp with:
HTTP 401 {"jsonrpc":"2.0","id":null,"error":{"code":-32001,"message":"Invalid API key"}}
The same keys work on the REST gateway (verified in Axiom: 153 × HTTP 200 user_api_key-authed requests for the affected account, Jul 3–5). Only /mcp rejects them.
Root cause
api/mcp/auth.ts (resolveAuthContext, ~lines 190–200): the X-WorldMonitor-Key branch validates the presented key only against the static WORLDMONITOR_VALID_KEYS env allowlist (legacy Starter+ env keys):
const validKeys = (process.env.WORLDMONITOR_VALID_KEYS || '').split(',').filter(Boolean);
if (!await timingSafeIncludes(candidateKey, validKeys)) {
// → 401 -32001 "Invalid API key"
Customer-issued keys live in Convex userApiKeys (SHA-256 hash lookup via /api/internal-validate-api-key, the path the REST gateway uses) and are never consulted on /mcp. Every dashboard key is therefore rejected by construction, regardless of plan/entitlement.
Compounding UX bug — misleading remediation
The no-credentials 401 on the same endpoint (auth.ts ~line 185) actively instructs users to do the thing that cannot work:
"Authentication required. Use OAuth (/oauth/token) or pass your API key via X-WorldMonitor-Key header."
This is the misleading-error-action-verb pattern: the named remediation is unreachable for every customer-issued key. Docs also present the API key header as an MCP auth method.
Impact
- API-plan customers (who pay specifically for
apiAccess) cannot use their keys with the MCP server; they conclude their entitlement is broken and file support tickets (one did on Jul 5; observed journey included buying a second subscription trying to fix it).
- OAuth works as the alternative, but nothing tells the user that keys aren't accepted here.
Proposed fix
- In
resolveAuthContext, after the env-allowlist miss, fall back to the Convex hash lookup (internal-validate-api-key, same as REST gateway), then enforce the owner's entitlement (mcpAccess === true, validUntil fresh) — mirroring runProPreChecks semantics. Map to a context kind that gets per-user quota/rate-limit + telemetry attribution (the env_key 60/min limiter keyed by raw key is wrong for user keys; key-owner userId should key the pro-style limiter/daily quota).
- Fix the 401 hint to branch on what the caller can actually do (OAuth for Pro; API keys only if the key is a valid user key with
mcpAccess).
- Docs: state explicitly which credentials
/mcp accepts per plan.
Until (1) ships, the header hint (2) and docs (3) alone would stop the support tickets.
Verification notes
- Live repro today: bogus
wm_ key → same -32001 shape; valid user key → REST 200 / MCP 401.
- The OAuth Pro path is healthy (verified end-to-end with a pro bearer incl. SSE framing, describe_tool, cache +
_execute RPC tools on api/www/apex hosts).
Symptom (from a paying customer support ticket, 2026-07-05)
Freshly generated dashboard API keys (
wm_…, API_STARTER plan,apiAccess: true) are rejected onPOST /mcpwith:The same keys work on the REST gateway (verified in Axiom: 153 × HTTP 200
user_api_key-authed requests for the affected account, Jul 3–5). Only/mcprejects them.Root cause
api/mcp/auth.ts(resolveAuthContext, ~lines 190–200): theX-WorldMonitor-Keybranch validates the presented key only against the staticWORLDMONITOR_VALID_KEYSenv allowlist (legacy Starter+ env keys):Customer-issued keys live in Convex
userApiKeys(SHA-256 hash lookup via/api/internal-validate-api-key, the path the REST gateway uses) and are never consulted on/mcp. Every dashboard key is therefore rejected by construction, regardless of plan/entitlement.Compounding UX bug — misleading remediation
The no-credentials 401 on the same endpoint (auth.ts ~line 185) actively instructs users to do the thing that cannot work:
This is the misleading-error-action-verb pattern: the named remediation is unreachable for every customer-issued key. Docs also present the API key header as an MCP auth method.
Impact
apiAccess) cannot use their keys with the MCP server; they conclude their entitlement is broken and file support tickets (one did on Jul 5; observed journey included buying a second subscription trying to fix it).Proposed fix
resolveAuthContext, after the env-allowlist miss, fall back to the Convex hash lookup (internal-validate-api-key, same as REST gateway), then enforce the owner's entitlement (mcpAccess === true,validUntilfresh) — mirroringrunProPreCheckssemantics. Map to a context kind that gets per-user quota/rate-limit + telemetry attribution (the env_key 60/min limiter keyed by raw key is wrong for user keys; key-owner userId should key the pro-style limiter/daily quota).mcpAccess)./mcpaccepts per plan.Until (1) ships, the header hint (2) and docs (3) alone would stop the support tickets.
Verification notes
wm_key → same -32001 shape; valid user key → REST 200 / MCP 401._executeRPC tools on api/www/apex hosts).