/** * Resolves the environment binding for `scai mcp serve` and packages * everything tool handlers need into a single `McpContext`. * * One server instance, ONE bound/default env — but, since the * multi-environment ("Option B") redesign, every environment-scoped * tool call may target ANY configured environment. The bound env is * the default; `resolveEnvBinding` resolves any other env on demand. * * Two-phase resolution so the stdio transport can answer the MCP * `initialize` handshake before any keychain access happens: * * 1. `resolveMcpEnv` — synchronous, reads the config file from disk * and validates the named environment. Throws on missing config * or unknown env so startup fails fast and loudly. * * 2. `createMcpContextProvider` — returns a memoized async getter * that fetches the deploy token from the OS keychain on first * invocation. Tool dispatchers `await` this; concurrent first * calls share the same in-flight promise so the keychain prompt * surfaces once, not per-call. Failures are not cached, so a * subsequent tool call retries after the operator unlocks the * keychain or runs `scai setup login`. * * Per-call retargeting: `resolveEnvBinding(configPath, envName)` * resolves any environment on demand and is memoized per * `configPath::envName` so a retargeted tool call doesn't re-mint a * deploy token on every invocation. The bound `McpContext` is a * superset of an `EnvBinding`, so handlers can treat either uniformly. * * The Authoring API access token is acquired lazily on first use — * recipe and serialization tools handle their own OAuth flow because * the authoring credentials may differ from the deploy credentials. */ import { type ResolvedEnvironment } from "../policy/environment.js"; /** * Everything a tool handler needs to talk to ONE environment. The bound * `McpContext` is structurally a superset of this — a handler that * accepts an `EnvBinding` works equally with the bound context or a * per-call retargeted binding. */ export interface EnvBinding { envName: string; resolved: ResolvedEnvironment; allowWriteEnabled: boolean; /** Deploy access token for this environment. */ deployToken: string; } export interface McpContext extends EnvBinding { /** The bound/default environment's name. Same as `envName`. */ envName: string; configPath: string; resolved: ResolvedEnvironment; allowWriteEnabled: boolean; /** Cached deploy access token (resolved on first tool call). */ deployToken: string; } export interface BindMcpEnvironmentOptions { configPath?: string; environmentName?: string; } export type McpContextProvider = () => Promise; export declare const resolveMcpEnv: (options: BindMcpEnvironmentOptions) => ResolvedEnvironment; export declare const createMcpContextProvider: (resolved: ResolvedEnvironment, configPath: string) => McpContextProvider; export declare const bindMcpEnvironment: (options: BindMcpEnvironmentOptions) => Promise; export declare const resolveEnvBinding: (configPath: string, envName: string) => Promise; /** * Test-only — clears the per-`configPath::envName` binding cache so * memoization state doesn't leak across `describe` blocks. */ export declare const __resetEnvBindingCacheForTests: () => void; /** * Resolve the `EnvBinding` an environment-scoped tool should operate * against. When `environmentName` is omitted (the common case), returns * the bound `McpContext` itself — it IS an `EnvBinding` — so behavior is * byte-for-byte identical to the pre-Option-B single-env server. When * set, resolves (and memoizes) the named environment via * `resolveEnvBinding`. */ export declare const resolveToolBinding: (context: McpContext, environmentName: string | undefined) => Promise;