import type { Readable, Writable } from "node:stream"; import { getValidAccessToken } from "../oauth/getValidAccessToken"; import { type TokenStore } from "../oauth/tokenStore"; import { type SuperviseOptions } from "./supervise"; import { type PushToken } from "./pushToken"; /** Starts a repeating callback and returns a canceller. Injectable so tests drive ticks by hand. */ export type Scheduler = (intervalMs: number, tick: () => void) => () => void; /** Options for {@link runSession}. */ export interface RunSessionOptions { /** Executable of the wrapped server command (everything after `--`). */ command: string; /** Arguments for the wrapped command. */ commandArgs: string[]; /** Loopback port the server's refresh listener is on. */ port: number; /** * Whether the port was chosen by us rather than by the user. An auto-picked * port can lose a race it is not the user's fault to lose, so a first push * that cannot reach it degrades the session instead of failing it. */ portWasAutoSelected?: boolean; /** Shared secret authenticating pushes. Generated if omitted. */ secret?: string; /** Cadence of the refresh-and-push loop, ms. */ refreshIntervalMs?: number; /** Override token minting (tests). */ getToken?: typeof getValidAccessToken; /** Override the token store (tests). */ tokenStore?: TokenStore; /** Override the push (tests). */ push?: PushToken; /** Override the child supervisor (tests). */ supervise?: (options: SuperviseOptions) => Promise; /** Override the refresh scheduler (tests). */ scheduler?: Scheduler; /** Base environment for the wrapped child. Defaults to `process.env`. */ env?: NodeJS.ProcessEnv; /** Forwarded to the supervisor as the child's stdin. Defaults to `process.stdin`. */ stdin?: Readable; /** Forwarded to the supervisor as the child's stdout (the MCP channel). Defaults to `process.stdout`. */ stdout?: Writable; /** Diagnostics stream. Defaults to `process.stderr`. NEVER stdout (that is the MCP channel). */ stderr?: Writable; /** Forwarded to the supervisor's signal registrar (tests). Defaults to `process.on`. */ onSignal?: (signal: NodeJS.Signals, handler: () => void) => void; } /** * Supervise the wrapped server command for a session: mint an initial access * token, inject it plus the refresh secret and port into the child's * environment, and keep the running server's token fresh by refreshing and * pushing before expiry. Resolves with the child's exit code. * * Fails fast with a `NOT_AUTHENTICATED` {@link CLIError} if there are no stored * credentials to mint from, and with `REFRESH_UNREACHABLE` if the very first * push cannot reach the server (almost always a misconfigured port/secret) — * exiting so the MCP client sees the failure rather than letting the session * silently die at expiry. Once a push has succeeded, a later refresh failure is * logged (the server keeps its last good token) rather than tearing the session * down, with one louder diagnostic after {@link CONSECUTIVE_FAILURE_WARNING_THRESHOLD} * consecutive failures. */ export default function runSession(options: RunSessionOptions): Promise;