// --------------------------------------------------------------------------- // MCP bearer JWT verify/classify (formerly mcp-auth.ts). // // Kept as its own `cloudflare:workers`-free leaf: the node-pool test // (mcp-auth.node.test.ts) imports these verifiers at runtime, and // `mcp/auth.ts` (which DOES read `cloudflare:workers` env) imports this leaf; // the dependency points one way only. // --------------------------------------------------------------------------- import { Data, Effect, Result, Schema } from "effect"; import { jwtVerify, type JWTVerifyGetKey } from "jose"; import { JWKSInvalid, JWKSTimeout, JWTExpired } from "jose/errors"; export type VerifiedToken = { /** The WorkOS account ID (user ID). */ accountId: string; /** The WorkOS organization ID, if the session has org context. */ organizationId: string | null; }; export class McpJwtVerificationError extends Data.TaggedError("McpJwtVerificationError")<{ readonly cause: unknown; readonly reason: "expired" | "invalid" | "system"; }> {} const JoseErrorCode = Schema.Struct({ code: Schema.String }); const isJoseErrorCodeShape = Schema.is(JoseErrorCode); const getJoseErrorCode = (cause: unknown): string | null => isJoseErrorCodeShape(cause) ? cause.code : null; const isJoseErrorCode = (code: string): boolean => code.startsWith("ERR_J"); const classifyJwtVerificationError = (cause: unknown): McpJwtVerificationError => new McpJwtVerificationError({ cause, reason: (() => { const code = getJoseErrorCode(cause); if (code === JWTExpired.code) return "expired"; if ( code === JWKSTimeout.code || code === JWKSInvalid.code || code === null || !isJoseErrorCode(code) ) { return "system"; } return "invalid"; })(), }); const isExpectedJwtVerificationError = (error: McpJwtVerificationError): boolean => error.reason === "expired" || error.reason === "invalid"; const withJwtVerificationSpan = ( effect: Effect.Effect, ): Effect.Effect => effect.pipe( Effect.result, Effect.flatMap((outcome) => Effect.gen(function* () { if (Result.isSuccess(outcome)) { yield* Effect.annotateCurrentSpan({ "mcp.auth.jwt_verify.outcome": "verified" }); return outcome; } yield* Effect.annotateCurrentSpan({ "mcp.auth.jwt_verify.outcome": outcome.failure.reason, }); return isExpectedJwtVerificationError(outcome.failure) ? outcome : yield* Effect.fail(outcome.failure); }), ), Effect.withSpan("mcp.auth.jwt_verify"), Effect.flatMap((outcome) => Result.isSuccess(outcome) ? Effect.succeed(outcome.success) : Effect.fail(outcome.failure), ), ); export const verifyMcpAccessToken = ( token: string, jwks: JWTVerifyGetKey, options: { readonly issuer: string; readonly audience: string; }, ) => Effect.gen(function* () { const { payload } = yield* Effect.tryPromise({ try: () => jwtVerify(token, jwks, { issuer: options.issuer, audience: options.audience, }), catch: classifyJwtVerificationError, }).pipe(withJwtVerificationSpan); if (!payload.sub) return null; return { accountId: payload.sub, organizationId: (payload.org_id as string | undefined) ?? null, } satisfies VerifiedToken; }); export const verifyWorkOSMcpAccessToken = ( token: string, jwks: JWTVerifyGetKey, options: { readonly issuer: string; readonly audience: string; }, ) => Effect.gen(function* () { const verified = yield* verifyMcpAccessToken(token, jwks, { issuer: options.issuer, audience: options.audience, }); yield* Effect.annotateCurrentSpan({ "mcp.auth.audience_mode": "workos_client", }); return verified; });