paperclipai/paperclip · error · ToolGatewayHttpError
input.reasonCode
input.reasonCode
Error message
Gateway bearer token is expired or invalid
What it means
This 401 ToolGatewayHttpError is thrown inside recordNamedGatewayAuthFailure when the gateway bearer token presented to the named MCP gateway cannot be resolved to any existing gateway record (findGatewayForProtocolLocator returns null). The gateway routes MCP protocol requests by hashing the bearer token and looking up tool_mcp_gateway_tokens joined with tool_mcp_gateways; when no row matches, there is no gateway to authenticate against, so the request is rejected with the caller's reasonCode (typically 'gateway_token_invalid'). Repeated failures against the same gateway/token are additionally throttled with a 429 'gateway_auth_throttled'.
Solutions
- Regenerate the gateway bearer token from the board UI (gateway settings) and update the client's Authorization header / MCP config with the new value.
- Verify the token belongs to the same Paperclip instance and company as the gateway you are calling; cross-instance tokens can never match the hash lookup.
- Check the token is the named gateway token (pcgw_ prefixed) and not an agent_api_key or other bearer credential.
- If failures repeat, wait for the auth-failure throttle window to reset before retrying, or the request will be rejected with 429 gateway_auth_throttled.
Example fix
// before: stale token cached in MCP client config
"headers": { "Authorization": "Bearer pcgw_old_deleted_token" }
// after: freshly minted gateway token from board gateway settings
"headers": { "Authorization": "Bearer pcgw_<newly-generated-token>" } Defensive patterns
Strategy: try-catch
Validate before calling
// before calling the gateway, check the token shape and non-emptiness
const token = process.env.PAPERCLIP_GATEWAY_TOKEN?.trim();
if (!token || !token.startsWith("pcgw_")) {
throw new Error("PAPERCLIP_GATEWAY_TOKEN is missing or not a named gateway token");
} Type guard
function isNamedGatewayToken(v: unknown): v is string {
return typeof v === "string" && v.trim().startsWith("pcgw_") && v.trim().length > 10;
} Try / catch
try {
await mcpGatewayCall(token);
} catch (err) {
if (err?.status === 401 && err?.code === "gateway_token_invalid") {
// token unknown to this instance: re-mint from board settings and reload config
token = await issueNewGatewayToken();
} else if (err?.status === 429 && err?.code === "gateway_auth_throttled") {
await sleep(err.details?.retryAfterMs ?? 60000);
} else throw err;
} Prevention
- Store the gateway token in env/secret manager keyed per Paperclip instance to avoid cross-instance token reuse.
- Rotate and update client configs in one step whenever tokens are reissued.
- Never substitute an agent API key for a gateway bearer token.
- Watch for repeated 401s and back off — repeated failures trigger the 429 auth-failure throttle.
When it happens
Trigger: Calling any MCP gateway protocol method (e.g. initialize, tools/call) with an Authorization: Bearer token whose hash does not match any row in tool_mcp_gateway_tokens — a token that was never issued for this instance, a token from a different environment (staging vs prod), or a token with a typo/whitespace-only difference that fails the hash lookup.
Common situations: Copying a pcgw_ token from a different Paperclip instance or company; an old token deleted from the board while a client (agent adapter, mcp client config) still caches it; rotating gateway tokens without updating the client's MCP server config; passing a regular agent API key instead of a gateway bearer token.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- invalid_cloud_runtime_identity
- Agent identity is required
- Cloud control assertion has already been used
- Cloud runtime identity assertion is expired or has an…
- Cloud runtime identity destination is invalid
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/b2e5c1b0c4d10a31.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/tool-gateway.ts:6671
async function recordNamedGatewayAuthFailure(input: {
gatewayId?: string | null;
gatewayPublicId?: string | null;
bearerToken: string;
reasonCode: string;
clientMetadata: ReturnType<typeof safeClientMetadata>;
}): Promise<never> {
const token = input.bearerToken.trim();
const tokenId = namedGatewayTokenId(token);
const gatewayKey = input.gatewayId
? `id:${input.gatewayId}`
: `public:${input.gatewayPublicId ?? "unknown"}`;
const tokenKey = tokenId
? `id:${tokenId}`
: `hash:${hashGatewayToken(token).slice(0, 24)}`;
const gateway = await findGatewayForProtocolLocator(input);
if (!gateway) {
throw new ToolGatewayHttpError(
401,
"Gateway bearer token is expired or invalid",
input.reasonCode,
);
}
const gatewayState = await consumeProtocolRateLimit({
companyId: gateway.companyId,
counterKey: `mcp_gateway_auth_failure:gateway:${gatewayKey}`,
config: protocolLimits.authFailures,
});
const tokenState = await consumeProtocolRateLimit({
companyId: gateway.companyId,
counterKey: `mcp_gateway_auth_failure:token:${gatewayKey}:${tokenKey}`,
config: protocolLimits.authFailures,
});
const limited = gatewayState.limited || tokenState.limited;
if (limited) {
const limiterKeyClass = gatewayState.limitedView on GitHub (pinned to 3f1d897a7c)