koala73/worldmonitor · critical · GrantConfigError

MCP_PRO_GRANT_HMAC_SECRET is not set

Error message

MCP_PRO_GRANT_HMAC_SECRET is not set

What it means

readGrantSecret() loads the HMAC secret used to sign and verify MCP pro grant tokens from the environment. If MCP_PRO_GRANT_HMAC_SECRET is unset or empty it logs a value-free operator warning and throws GrantConfigError, refusing to mint or accept grants, because signing with an empty secret would be silently insecure.

Source

Thrown at api/_mcp-grant-hmac.ts:87

async function importHmacKey(secret: string): Promise<CryptoKey> {
  return crypto.subtle.importKey(
    'raw',
    ENC.encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign', 'verify'],
  );
}

/** Reads the env var. Throws GrantConfigError if missing/empty. */
export function readGrantSecret(env: NodeJS.ProcessEnv = process.env): string {
  const secret = env.MCP_PRO_GRANT_HMAC_SECRET ?? '';
  if (!secret) {
    // Operator-visible and value-free; the PR #3646 MCP_* env-name inventory
    // that used to sit here served its diagnostic purpose and is gone (#7278).
    console.warn('[mcp-grant-hmac] MCP_PRO_GRANT_HMAC_SECRET is not set');
    throw new GrantConfigError('MCP_PRO_GRANT_HMAC_SECRET is not set');
  }
  return secret;
}

/**
 * Sign a grant payload. Returns the wire-format token
 * `<base64url(payloadJson)>.<base64url(sig)>`.
 *
 * Deterministic for a given (payload, secret) pair: stringifies once,
 * signs the exact bytes, encodes both halves with base64url-no-pad.
 */
export async function signGrant(payload: GrantPayload, secret?: string): Promise<string> {
  const sec = secret ?? readGrantSecret();
  const json = JSON.stringify({ userId: payload.userId, nonce: payload.nonce, exp: payload.exp });
  const payloadBytes = ENC.encode(json);
  const key = await importHmacKey(sec);
  const sig = new Uint8Array(await crypto.subtle.sign('HMAC', key, payloadBytes));
  return `${base64UrlEncode(payloadBytes)}.${base64UrlEncode(sig)}`;

View on GitHub (pinned to 9361220cc0)

Solutions

  1. Set MCP_PRO_GRANT_HMAC_SECRET in the deployment environment (Vercel project settings or .env.local) to a strong random value, then redeploy/restart.
  2. Verify the exact env name with `vercel env ls` or by printing Object.keys(process.env) filtered on MCP_ in a scratch endpoint — no typos, correct environment scope.
  3. If the value exists but the error persists, confirm the function actually receives env vars (correct project, correct environment: production/preview/development) and that no build-time inlining stripped it.
  4. After rotating or first setting the secret, expect previously issued grants to fail verification; re-mint grants.

Example fix

// before (deploy without secret)
// vercel deploy  -> readGrantSecret throws GrantConfigError
// after
echo "MCP_PRO_GRANT_HMAC_SECRET=$(openssl rand -hex 32)" >> .env.local
# or: vercel env add MCP_PRO_GRANT_HMAC_SECRET production
vercel deploy
Defensive patterns

Strategy: validation

Validate before calling

function hasGrantSecret(env = process.env): boolean {
  return typeof env.MCP_PRO_GRANT_HMAC_SECRET === 'string' && env.MCP_PRO_GRANT_HMAC_SECRET.length > 0;
}
if (!hasGrantSecret()) throw new Error('Set MCP_PRO_GRANT_HMAC_SECRET before calling grant APIs');

Type guard

function isGrantEnvConfigured(env: NodeJS.ProcessEnv): env is NodeJS.ProcessEnv & { MCP_PRO_GRANT_HMAC_SECRET: string } {
  return typeof env.MCP_PRO_GRANT_HMAC_SECRET === 'string' && env.MCP_PRO_GRANT_HMAC_SECRET.length > 0;
}

Try / catch

try {
  const secret = readGrantSecret();
  // sign/verify grant
} catch (err) {
  if (err instanceof GrantConfigError) {
    // deployment misconfiguration: fail request with 503, alert operator
  } else throw err;
}

Prevention

When it happens

Trigger: Any call path that mints or verifies an MCP pro grant (e.g. api/internal/mcp-grant-mint.ts mintGrantHandler, api/oauth/authorize-pro.ts) when process.env.MCP_PRO_GRANT_HMAC_SECRET is missing or set to the empty string at runtime.

Common situations: Deploying the Edge function without configuring the secret in Vercel env vars; running locally without a .env.local entry; a typo'd env name (e.g. MCP_GRANT_HMAC_SECRET); the secret configured only for one environment (preview but not production); a redeploy that dropped env configuration.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01). Data as JSON: /api/errors/6fc8295db091139a. Report an issue: GitHub.