Skip to content

Auth

Auth in mcify is between the agent and your MCP server. Don’t confuse it with the auth between your server and the upstream API it wraps — those are two different layers.

[ Agent ] ← bearer token → [ mcify server ] ← upstream API key → [ Khipu / Bsale / ... ]

Your auth config governs the left arrow only.

import { bearer, defineConfig } from '@mcify/core';
defineConfig({
auth: bearer({ env: 'MCIFY_AUTH_TOKEN' }),
...
});

The agent sends Authorization: Bearer <token>. The runtime compares with process.env.MCIFY_AUTH_TOKEN using a constant-time check.

Generate the token: openssl rand -hex 32. Store it where you store other secrets (Workers wrangler secret, Fly flyctl secrets, Railway env vars, Kubernetes Secret).

API key

import { apiKey } from '@mcify/core';
auth: apiKey({ headerName: 'x-api-key', env: 'MCIFY_API_KEY' }),

Same shape as bearer, different header. Useful when the consuming agent already speaks x-api-key to its other backends.

OAuth

For multi-tenant setups where each user has their own credentials:

import { oauth } from '@mcify/core';
auth: oauth({
provider: 'workos',
audience: 'mcify-server',
// Provider-specific options.
}),

The runtime validates JWTs against the provider’s JWKS. The decoded claims land in ctx.auth.claims so your handlers can do per-user authorization.

Custom verify

If your token shape doesn’t fit bearer / apiKey, pass a verify function:

auth: bearer({
verify: async (token, ctx) => {
const session = await mySessionStore.lookup(token);
if (!session) return null; // → 401
return { token, claims: { userId: session.userId, scopes: session.scopes } };
},
}),

The runtime calls verify once per request, caches the result for the duration of that request, and exposes the return value as ctx.auth.

None

For local dev or fully-public servers:

import { auth } from '@mcify/core';
auth: auth.none(),

Don’t ship this to production unless your server only exposes idempotent reads of public data. Even then, rateLimit middleware on every tool is mandatory.

Per-tool auth

The server-level auth is the gate. To require an additional check per tool, use requireAuth middleware with a predicate:

defineTool({
middlewares: [
requireAuth({
check: (auth) => auth.claims.scopes?.includes('payments:write'),
message: 'requires the payments:write scope',
}),
],
...
});

requireAuth returns 403 (not 401) when the request authenticated but lacks the right scope. The agent gets a useful error.