209 lines · 8.6 KB · TypeScript
RawDownload
// ---------------------------------------------------------------------------// Where an MCP session's organization identity comes from.//// Three sources, in the order they are preferred:////   props    — the org record the worker resolved while authorizing THIS//              request, carried in the session props. Free, and always the//              freshest answer available.//   stored   — the meta this session Durable Object already persisted for the//              same organization on an earlier init. Free, and correct for a//              session that already exists.//   database — an actual read of the `organizations` row.//// Only the third can fail, and it is the one that used to run unconditionally.// Every cold DO init opened a brand-new Postgres connection through Hyperdrive// purely to re-read a row the worker had loaded microseconds earlier on the// same request and then discarded; when that connection could not be// established the read hung for the full connect budget and the failure was// turned into a defect, killing `initialize` on a database that was, at that// same moment, answering the worker's own queries in milliseconds.//// So: prefer what the request already knows, and when the database genuinely is// the only source, give it a bounded retry and a CLASSIFIED failure — the same// transient-vs-definitive split the WorkOS membership check already uses in// `auth-provider.ts` — instead of an unclassified defect.// --------------------------------------------------------------------------- import { Data, Effect, Predicate, Result, Schedule } from "effect"; import type { McpSessionInit, SessionMeta } from "@executor-js/cloudflare/mcp/agent-durable-object"; import { UserStoreService } from "../auth/context";import { WorkOSClient } from "../auth/workos";import {  isDefinitiveWorkOSDenial,  isTransientUserStoreReason,  type UserStoreError,} from "../auth/errors";import { resolveOrganization } from "../auth/organization"; export type SessionMetaSource = "props" | "stored" | "database"; export const SESSION_META_SOURCE_ATTRIBUTE = "mcp.session.meta_source"; /** * The wire marker for "the organization directory was unreachable, try again". * * A Durable Object's `init` can only reject its Promise, so this travels to the * worker as an ordinary error message across the DO boundary — the same * mechanism the condemned-session `"destroyed"` abort already uses. The worker * matches on it to answer a retryable 503 instead of letting an unclassified * 500 (and the agents SDK's own DO-operation retry on top of it) turn a * transient blip into a half-minute client hang. */export const MCP_SESSION_META_UNAVAILABLE = "mcp_session_meta_unavailable"; export class McpSessionMetaUnavailableError extends Data.TaggedError(  "McpSessionMetaUnavailableError",)<{  readonly reason: string;  readonly attempts: number;}> {  override get message(): string {    return `${MCP_SESSION_META_UNAVAILABLE}: organization directory unavailable (${this.reason}) after ${this.attempts} attempts`;  }} export class OrganizationNotFoundError extends Data.TaggedError("OrganizationNotFoundError")<{  readonly organizationId: string;}> {} /** Does this failure, seen at the worker, mean "the session DO could not reach *  the organization directory"? Matches on the message because that is what *  survives the Durable Object RPC boundary. */export const isMcpSessionMetaUnavailable = (error: unknown): boolean =>  // oxlint-disable-next-line executor/no-unknown-error-message -- adapter boundary: a Durable Object rejection reaches the worker as a plain Error whose message IS the signal (same mechanism as the "destroyed" abort)  Predicate.isError(error) && error.message.includes(MCP_SESSION_META_UNAVAILABLE); /** * Retries for the database path. Deliberately small: the point is to ride out a * single bad connection attempt, not to sit on a client's `initialize` while a * database stays down. Exhausting them answers the client quickly and * retryably, which is strictly better than hanging. */export const SESSION_META_DB_RETRIES = 2; const RETRY_SCHEDULE = Schedule.both(  Schedule.exponential("200 millis"),  Schedule.recurs(SESSION_META_DB_RETRIES),); const isUserStoreError = Predicate.isTagged("UserStoreError") as (  error: unknown,) => error is UserStoreError; /** * Is this org-lookup failure worth another attempt? A connection that never * opened is; a query the server answered with an error, or a WorkOS denial, is * not — retrying those only burns the client's init budget. */export const isRetryableOrganizationLookupFailure = (failure: unknown): boolean => {  if (isUserStoreError(failure)) return isTransientUserStoreReason(failure.reason);  if (isDefinitiveWorkOSDenial(failure)) return false;  // A WorkOS blip (429/5xx/timeout/network) — the same class the MCP auth path  // already treats as retryable.  return Predicate.isTagged(failure, "WorkOSError");}; const failureReason = (failure: unknown): string =>  isUserStoreError(failure) ? failure.reason : "upstream"; const metaFromIdentity = (  token: McpSessionInit,  organization: { readonly name: string; readonly slug?: string },): SessionMeta => ({  organizationId: token.organizationId,  organizationName: organization.name,  ...(organization.slug === undefined ? {} : { organizationSlug: organization.slug }),  userId: token.userId,  resource: token.resource,  elicitationMode: token.elicitationMode,  artifactsEnabled: token.artifactsEnabled,  searchToolsEnabled: token.searchToolsEnabled,}); /** * Read the organization row, retrying only failures a retry can clear, and * surfacing an exhausted retry as a typed, retryable failure rather than a * defect. */const organizationFromDatabase = (  organizationId: string,): Effect.Effect<  { readonly id: string; readonly name: string; readonly slug?: string },  McpSessionMetaUnavailableError | OrganizationNotFoundError,  UserStoreService | WorkOSClient> =>  Effect.gen(function* () {    let attempts = 0;    // `Effect.retry` retries every failure it sees, so definitive failures are    // lifted OUT of the error channel before the schedule ever runs and only    // the retryable ones are left in it.    const attempt = Effect.suspend(() => {      attempts += 1;      return resolveOrganization(organizationId).pipe(        Effect.result,        Effect.flatMap((outcome) =>          Result.isFailure(outcome) && isRetryableOrganizationLookupFailure(outcome.failure)            ? Effect.fail(outcome.failure)            : Effect.succeed(outcome),        ),      );    });     // Two nested Results: the outer one is "the retries ran out", the inner one    // is "the lookup failed definitively on the first look". Both mean the same    // thing to the caller.    const retried = yield* attempt.pipe(Effect.retry(RETRY_SCHEDULE), Effect.result);    const outcome = Result.isFailure(retried) ? Result.fail(retried.failure) : retried.success;     if (Result.isFailure(outcome)) {      return yield* new McpSessionMetaUnavailableError({        reason: failureReason(outcome.failure),        attempts,      });    }    const organization = outcome.success;    if (!organization) return yield* new OrganizationNotFoundError({ organizationId });    return organization;  }).pipe(    Effect.withSpan("mcp.session.resolve_organization", {      attributes: { "mcp.auth.organization_id": organizationId },    }),  ); /** * Build the session meta for an init, preferring the org identity the request * already carries over any read of the organization directory. * * `storedMeta` is this DO's own persisted meta for the SAME organization (the * base Durable Object only offers a matching one), or `null`. */export const resolveSessionMetaForToken = (  token: McpSessionInit,  storedMeta: SessionMeta | null,): Effect.Effect<  SessionMeta,  McpSessionMetaUnavailableError | OrganizationNotFoundError,  UserStoreService | WorkOSClient> =>  Effect.gen(function* () {    const fromProps = token.organizationName;    if (fromProps) {      yield* Effect.annotateCurrentSpan(SESSION_META_SOURCE_ATTRIBUTE, "props");      return metaFromIdentity(token, { name: fromProps, slug: token.organizationSlug });    }     if (storedMeta?.organizationName) {      yield* Effect.annotateCurrentSpan(SESSION_META_SOURCE_ATTRIBUTE, "stored");      return metaFromIdentity(token, {        name: storedMeta.organizationName,        slug: storedMeta.organizationSlug,      });    }     yield* Effect.annotateCurrentSpan(SESSION_META_SOURCE_ATTRIBUTE, "database");    const organization = yield* organizationFromDatabase(token.organizationId);    return metaFromIdentity(token, organization);  });