/** * Type-safe EventTarget for InspectorClient events. * Extends the generic TypedEventTarget with InspectorClientEventMap; TypedEvent * is provided as a type alias for use in listener signatures. * * v2 note: the v1.5 event map referenced SamplingCreateMessage / ElicitationCreateMessage * classes (with embedded promise resolvers) and OAuth events from the auth subsystem. * The auth subsystem and the runtime promise-based pending classes are not yet ported to * v2 — see #1243 scope. Pending requests are typed with the v2 wrapper types * (InspectorPendingSampling / InspectorPendingElicitation); OAuth events are deliberately * omitted here and will be added when the auth subsystem is ported. */ import { TypedEventTarget, type TypedEventGeneric, } from "./typedEventTarget.js"; import type { ConnectionStatus, MessageEntry, StderrLogEntry, FetchRequestEntry, FetchStreamState, PromptGetInvocation, ResourceReadInvocation, ResourceTemplateReadInvocation, ResourceSubscriptionStreamState, ExcludedTool, RequestMetadata, } from "./types.js"; import type { MalformedListItem } from "./listSalvage.js"; import type { ConnectionDiagnostics } from "./connectionDiagnostics.js"; import type { Tool, ServerCapabilities, Implementation, Root, Progress, ProgressToken, Task, CallToolResult, ProtocolError, ProtocolEra, DiscoverResult, } from "@modelcontextprotocol/client"; import type { SamplingCreateMessage } from "./samplingCreateMessage.js"; import type { ElicitationCreateMessage } from "./elicitationCreateMessage.js"; import type { JsonValue } from "../json/jsonUtils.js"; import type { OAuthTokens } from "@modelcontextprotocol/client"; import type { AuthChallenge } from "../auth/challenge.js"; /** Task with createdAt optional so we can emit synthetic tasks (e.g. on result/error) that omit it. */ export type TaskWithOptionalCreatedAt = Omit & { createdAt?: string; }; /** * Maps event names to their detail types for CustomEvents */ export interface InspectorClientEventMap { statusChange: ConnectionStatus; toolsChange: Tool[]; /** * Tools the SDK excluded from `tools/list` for invalid `x-mcp-header` * annotations (SEP-2243), each with its reason. Empty on legacy/stdio * connections (which don't exclude) and before connect (#1632). */ excludedToolsChange: ExcludedTool[]; /** * Entries dropped from a list result because they failed the spec schema for * their primitive, across every list method. Empty against a conforming * server; a non-empty set means the list rendered without them (#1909). */ malformedListItemsChange: MalformedListItem[]; capabilitiesChange: ServerCapabilities | undefined; serverInfoChange: Implementation | undefined; instructionsChange: string | undefined; protocolVersionChange: string | undefined; /** Protocol era negotiated after connect (SEP §7.8); `"legacy"` on a legacy connect, undefined when not connected. */ protocolEraChange: ProtocolEra | undefined; /** `server/discover` result on a probed/pinned connect; undefined on legacy. */ discoverResultChange: DiscoverResult | undefined; message: MessageEntry; /** * A response the client REJECTED after it was logged — the server answered, * but the SDK's era codec refused the result (e.g. a 2026-07-28 `tools/list` * missing `ttlMs`/`cacheScope`). The wire frame is a valid JSON-RPC result, * so the Protocol entry would otherwise render as a clean success; this * carries the reason so it can be marked instead (#1953). `id` is the * JSON-RPC id of the rejected response. */ responseRejected: { id: string | number; reason: string }; stderrLog: StderrLogEntry; fetchRequest: FetchRequestEntry; /** Fired when an in-flight fetch's response body is read asynchronously. */ fetchRequestBodyUpdate: { id: string; responseBody: string }; /** * Fired as a tracked long-lived stream (the standalone `GET` on Streamable * HTTP, the legacy SSE event stream) delivers events and when it ends. The * body of such a stream is never captured, so this is the log's only view * of its lifetime (#2318). */ fetchRequestStreamUpdate: { id: string; stream: FetchStreamState }; /** * Fired whenever the connection diagnostics snapshot changes — a request * goes out, a response comes back, the notification stream opens, delivers * an event or closes, or a new session resets it. Carries the fresh * snapshot; `getConnectionDiagnostics()` returns the same one (#2318). */ connectionDiagnosticsChange: ConnectionDiagnostics; /** * Fired whenever the client transitions `status` to `"error"` from a path * that is NOT an awaited promise — i.e. a mid-session transport failure * (stdio subprocess crash, SSE stream drop, HTTP 5xx) surfaced via the * transport's `onerror`. The `Error` carries the reason (`.message`, * optional `.cause`). Handshake failures are NOT dispatched here: they * reject the awaited `connect()` promise, so the caller already has the * error and a second surface would double-report it. Consumers that don't * subscribe directly can read the last error via `useInspectorClient`'s * `lastError`. */ error: Error; resourceUpdated: { uri: string }; progressNotification: Progress & { progressToken?: ProgressToken }; toolCallResultChange: { toolName: string; params: Record; result: CallToolResult | null; timestamp: Date; success: boolean; error?: string; metadata?: RequestMetadata; /** Non-fatal outputSchema mismatch detected on the skipOutputValidation path. */ outputValidationError?: string; }; resourceContentChange: { uri: string; content: ResourceReadInvocation; timestamp: Date; }; resourceTemplateContentChange: { uriTemplate: string; content: ResourceTemplateReadInvocation; params: Record; timestamp: Date; }; promptContentChange: { name: string; content: PromptGetInvocation; timestamp: Date; }; pendingSamplesChange: SamplingCreateMessage[]; newPendingSample: SamplingCreateMessage; pendingElicitationsChange: ElicitationCreateMessage[]; newPendingElicitation: ElicitationCreateMessage; rootsChange: Root[]; resourceSubscriptionsChange: string[]; /** * Fired when the modern-era `subscriptions/listen` stream that backs resource * subscriptions changes lifecycle state (#1630): opened+acknowledged, dropped * and reconnecting, or ended. Legacy connections never dispatch an `active` * state (they have no persistent stream). */ resourceSubscriptionStreamChange: ResourceSubscriptionStreamState; // Task events /** Fired only from server notification notifications/tasks/status. */ taskStatusChange: { taskId: string; task: Task }; /** Fired from callToolStream for each task update. */ toolCallTaskUpdated: { taskId: string; task: TaskWithOptionalCreatedAt; result?: CallToolResult; error?: ProtocolError; }; /** Fired from getRequestorTask() and callToolStream (client-origin task updates). */ requestorTaskUpdated: { taskId: string; task: TaskWithOptionalCreatedAt; result?: CallToolResult; error?: ProtocolError; }; taskCancelled: { taskId: string }; /** * Fired from callToolStream when a progress notification arrives for a * task-augmented tool call, tagged with the taskId callToolStream owns so * consumers can correlate progress → task (the generic progressNotification * event carries only the caller's progressToken, not the taskId). */ requestorTaskProgress: { taskId: string; progress: Progress }; tasksChange: Task[]; // Signal events (no payload) connect: void; disconnect: void; // List changed notification events toolsListChanged: void; resourcesListChanged: void; resourceTemplatesListChanged: void; promptsListChanged: void; tasksListChanged: void; // Session persistence (dispatched by client; FetchRequestLogState listens and saves) saveSession: { sessionId: string }; // OAuth events (#1302 — fired by the ported oauthManager / InspectorClient) oauthComplete: { tokens: OAuthTokens }; oauthAuthorizationRequired: { url: URL }; oauthError: { error: Error }; /** Ambient (SSE) auth challenge while no command-scoped send is active. */ authChallengeAmbient: { challenge: AuthChallenge }; /** Command-scoped direct-transport auth challenge (no ambient toast). */ authChallengeCommand: { challenge: AuthChallenge }; /** Ambient auth recovery completed (remote auth state pushed). */ authChallengeRecovered: { challenge: AuthChallenge }; /** Interactive auth required; App orchestrates redirect (step-up modal or reauth). */ authChallengeInteractive: { challenge: AuthChallenge; authorizationUrl: URL; }; } /** * Type alias for InspectorClient typed events (for listener signatures). */ export type TypedEvent = TypedEventGeneric; /** * Type-safe EventTarget for InspectorClient events. */ export class InspectorClientEventTarget extends TypedEventTarget {}