Skip to content

bug(mcp): /mcp rejects all customer-issued wm_ API keys — X-WorldMonitor-Key path only checks WORLDMONITOR_VALID_KEYS env allowlist #4859

Description

@koala73

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

  1. 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).
  2. 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).
  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions