import type { ServerNotification, ServerRequest, } from "@modelcontextprotocol/sdk/types.js"; import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js"; import { AsyncLocalStorage } from "node:async_hooks"; import { z } from "zod"; import type { BillingCustomerContext } from "@/server/billing/subscription"; import { buildDashboardUrl } from "@/server/mcp/urls"; type McpAuth = { userId: string; userEmail: string; organizationId: string; scopes: string[]; clientId: string | null; audience: string; subject: string; }; export const MCP_AUTH_CONTEXT_PROP = "openSeoAuth"; export const MCP_ROUTE = "/mcp"; const mcpToolAuthContextSchema = z.object({ userId: z.string().min(1), userEmail: z.string().min(1), organizationId: z.string().min(1), clientId: z.string().nullable(), scopes: z.array(z.string()), audience: z.string().min(1), subject: z.string().min(1), baseUrl: z.string().url(), }); type McpToolAuthContext = z.infer; export type ToolExtra = RequestHandlerExtra; export const workersOAuthMcpPropsSchema = z.object({ [MCP_AUTH_CONTEXT_PROP]: mcpToolAuthContextSchema, }); const mcpToolAuthContextStorage = new AsyncLocalStorage(); export function createWorkersOAuthMcpProps( context: McpToolAuthContext, ): Record { return { [MCP_AUTH_CONTEXT_PROP]: context, }; } export function withWorkersOAuthMcpScopes( props: unknown, scopes: string[], ): Record | undefined { const result = workersOAuthMcpPropsSchema.safeParse(props); if (!result.success) return undefined; return createWorkersOAuthMcpProps({ ...result.data[MCP_AUTH_CONTEXT_PROP], scopes, }); } export function runWithMcpToolAuthContext( context: McpToolAuthContext, callback: () => T, ) { return mcpToolAuthContextStorage.run(context, callback); } export function requireMcpToolAuthContext( extra: ToolExtra, ): McpToolAuthContext { const rawContext = mcpToolAuthContextStorage.getStore() ?? extra.authInfo?.extra?.[MCP_AUTH_CONTEXT_PROP]; const result = mcpToolAuthContextSchema.safeParse(rawContext); if (!result.success) { throw new Error(`MCP auth context missing: ${result.error.message}`); } return result.data; } export function getAuth(extra: ToolExtra): McpAuth { const { baseUrl: _baseUrl, ...auth } = requireMcpToolAuthContext(extra); return auth; } export function getBaseUrl(extra: ToolExtra): string { return requireMcpToolAuthContext(extra).baseUrl; } export function buildBillingCustomer( auth: McpAuth, projectId: string, ): BillingCustomerContext { return { userId: auth.userId, userEmail: auth.userEmail, organizationId: auth.organizationId, projectId, }; } export function buildProjectMeta( context: { auth: Pick; baseUrl: string }, projectId: string, path?: string, params?: Record, ) { return { organizationId: context.auth.organizationId, projectId, url: path ? buildDashboardUrl(context.baseUrl, path, params) : undefined, }; }