import { type EnvironmentId, McpCapabilityUnavailableError, PreviewAutomationUnavailableError, type ProviderInstanceId, type ThreadId, } from "@t3tools/contracts"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; export type McpCapability = "preview" | "device" | "pull-requests"; export interface McpInvocationScope { readonly environmentId: EnvironmentId; readonly threadId: ThreadId; readonly providerSessionId: string; readonly providerInstanceId: ProviderInstanceId; readonly capabilities: ReadonlySet; readonly issuedAt: number; } export class McpInvocationContext extends Context.Service< McpInvocationContext, McpInvocationScope >()("t3/mcp/McpInvocationContext") {} /** The error a missing capability surfaces as; preview keeps its own so the broker can route it. */ export type McpCapabilityError = C extends "preview" ? PreviewAutomationUnavailableError : McpCapabilityUnavailableError; const missingCapability = ( invocation: McpInvocationScope, capability: McpCapability, ): PreviewAutomationUnavailableError | McpCapabilityUnavailableError => { const fields = { environmentId: invocation.environmentId, threadId: invocation.threadId, providerSessionId: invocation.providerSessionId, providerInstanceId: invocation.providerInstanceId, }; return capability === "preview" ? new PreviewAutomationUnavailableError({ capability, ...fields }) : new McpCapabilityUnavailableError({ capability, ...fields }); }; export const requireMcpCapability = ( capability: C, ): Effect.Effect, McpInvocationContext> => Effect.flatMap(McpInvocationContext, (invocation) => invocation.capabilities.has(capability) ? Effect.succeed(invocation) : // The conditional type narrows what the literal argument decided at runtime. Effect.fail(missingCapability(invocation, capability) as McpCapabilityError), ).pipe(Effect.withSpan("mcp.requireCapability"));