/** * The MCP inspector bridge's sessions (DES-MCP-INSPECTOR-001 §3). * * One session = one live transport to one MCP server, opened on the operator's explicit start and * driven by studio's host for an `mcp-server` editor plugin. The SDK's transports are used WITHOUT * the SDK `Client`, so every JSON-RPC message in both directions is seen and logged raw. * * - The bridge owns the handshake: `initialize` (no client capabilities) then * `notifications/initialized`. A server request (sampling, elicitation, roots, anything) is * answered `-32601` and logged. * - Only {@link INSPECT_METHODS} may be sent. * - The log is a ring (1000 entries, 4 MB); each entry's text is capped at 64 KB. * - Secrets: every value the bridge injected (the registry secret, each typed env/header value) is * scrubbed from every entry and every reply, and credential-shaped strings are replaced. Field-name * redaction is NOT applied: it would blank the `session`/`token`-named fields being debugged. * - Lifetime: at most {@link MAX_SESSIONS}; idle (no rpc, no poll) for {@link IDLE_MS} = closed; * `closeAll` on the daemon's close; `killAllSync` on process exit (a stdio child gets SIGKILL). */ import type { Readable } from 'node:stream'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import type { McpInspectInitialize, McpInspectLogResponse, McpInspectRpcResponse, McpInspectSessionView, McpInspectState } from '../core/types.js'; /** The methods a plugin may send. The bridge itself owns `initialize`. */ export declare const INSPECT_METHODS: ReadonlySet; export declare const MAX_SESSIONS = 4; export declare const IDLE_MS: number; export declare const INIT_TIMEOUT_MS = 15000; export declare const CALL_TIMEOUT_MS = 60000; export declare const LOG_MAX_ENTRIES = 1000; export declare const LOG_MAX_BYTES: number; export declare const ENTRY_MAX_CHARS: number; export declare const REPLY_MAX_CHARS: number; export declare const POLL_MAX_MS = 20000; export declare class InspectError extends Error { readonly status: number; readonly code: string; constructor(status: number, code: string, message: string); } /** An opened transport, before `start()`. */ export interface OpenedTransport { transport: Transport; stderr: Readable | null; pid: () => number | null; } export type TargetDescription = McpInspectSessionView['target']; /** * Every form a secret can take in what the bridge emits: the value itself and its JSON-escaped body * (a quote, backslash or newline in a secret changes its serialized form; codex r1). */ export declare function secretForms(secrets: readonly string[]): string[]; /** Scrub exact secrets (every form), then credential shapes, from any JSON value or string. */ export declare function scrub(value: T, forms: readonly string[]): T; /** * How many trailing characters of `text` could be the start of a secret form: the longest tail * that is a proper prefix of one (codex r2). Bounded by the longest form, so a buffer holding it * back never grows past that. */ export declare function heldTail(text: string, forms: readonly string[]): number; export declare class InspectSession { private readonly rawTarget; private readonly opened; private readonly now; private readonly callTimeoutMs; readonly id: string; readonly startedAt: string; state: McpInspectState; error: string | null; initialize: McpInspectInitialize | null; lastSeen: number; private seq; private log; private bytes; private nextId; private pending; /** When each request went out, so a response that lands after its timeout still shows its time. */ private sentAt; private waiters; private readonly forms; /** Stderr not yet logged: at most a tail that could begin a secret (whole secrets are scrubbed in it first). */ private stderrBuf; private closing; /** * The stdio child's pid, held from start until the child is known gone: the SDK forgets its * process the moment close() begins, so `opened.pid()` cannot be asked then (codex r2). */ private childPid; constructor(rawTarget: TargetDescription, opened: OpenedTransport, secrets: readonly string[], now?: () => number, callTimeoutMs?: number); /** The target as anyone outside may see it: a secret the operator also typed into an argument is scrubbed (codex r1). */ get target(): TargetDescription; /** Scrub a message meant for a reply, an error or a daemon log line. */ scrubText(text: string): string; view(): McpInspectSessionView; touch(): void; /** * Connect and run the handshake, all of it (transport start, `initialize`, `initialized`) under * ONE deadline (codex r1: a legacy SSE endpoint that never sends its `endpoint` event hung the * start). Never throws: a failure is the session's `failed` state. */ start(initTimeoutMs?: number): Promise; /** Send one allowed request and wait for its answer. */ rpc(method: string, params: Record | undefined): Promise; private request; private send; private received; /** * Stderr as it arrives (codex r1/r2), by whole line: whole secrets are scrubbed in the buffer * first, so none can straddle what is logged; a tail that could begin a secret is never logged; * and only complete lines go out (a partial one waits for its newline, up to 8 KiB), so the * credential-shape patterns see a line whole however the server's writes were chunked. */ private stderrData; /** * The pipe ended: a partial last line is logged, and a held tail (a secret's start that never * became a whole secret) is redacted, never logged. */ private flushStderr; /** * One log entry. A JSON-RPC message is scrubbed as a VALUE, then serialized (codex r1: scrubbing * the serialized text missed escaped secrets), and so are `method` and `rpcId`. */ private append; /** Entries after `after`, at once if any, else when one lands or `waitMs` passes. */ entries(after: number, waitMs: number): Promise; private fail; private closed; private rejectPending; /** * Close it. A streamable-HTTP session is terminated upstream first (its DELETE, codex r1), then the * transport is closed (a stdio child is terminated by the SDK). Bounded and idempotent. */ close(reason?: string): Promise; /** Process exit: no awaiting, so a stdio child is killed outright. */ killSync(): void; } /** A short, path-free name for a stdio command (`node build/index.js` → `index.js`). */ export declare function commandName(command: string, args: readonly string[]): string; export declare class InspectSessions { private readonly now; private sessions; /** Sessions removed but still closing, with their close: killable at exit, awaited by closeAll (codex r2). */ private closingSessions; private sweeper; constructor(now?: () => number); get size(): number; add(s: InspectSession): void; /** Reserve a slot before the (slow) connect, so concurrent starts cannot exceed the cap. */ hasRoom(): boolean; get(id: string): InspectSession | null; remove(id: string, reason?: string): Promise; sweep(): Promise; closeAll(): Promise; killAllSync(): void; } //# sourceMappingURL=session.d.ts.map