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
- Set MCP_PRO_GRANT_HMAC_SECRET in the deployment environment (Vercel project settings or .env.local) to a strong random value, then redeploy/restart.
- 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.
- 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.
- 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
- Add MCP_PRO_GRANT_HMAC_SECRET to .env.example and deployment env checklists.
- Run a startup readiness check that asserts required env vars exist before serving traffic.
- Use the exact env name everywhere; source it from a single constants module.
- Rotate the secret via a documented runbook — setting it changes grant validity.
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
- Desktop authentication failed
- get_intel_timeline requires at least one of domain ("conflic
- ${operation} HTTP ${status}: ${safeCode}
- `call` needs a tool name, e.g. `worldmonitor call get_countr
- Invalid returnUrl: must use a trusted worldmonitor.app origi
AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01).
Data as JSON: /api/errors/6fc8295db091139a.
Report an issue: GitHub.