import { useState, useEffect, useCallback } from "react"; import type { InspectorClientProtocol } from "../mcp/inspectorClientProtocol.js"; import type { AppRendererClient } from "../mcp/inspectorClientProtocol.js"; import type { TypedEvent } from "../mcp/inspectorClientEventTarget.js"; import type { ConnectionStatus } from "../mcp/types.js"; import type { ServerCapabilities, Implementation, } from "@modelcontextprotocol/sdk/types.js"; export interface UseInspectorClientResult { status: ConnectionStatus; capabilities?: ServerCapabilities; serverInfo?: Implementation; instructions?: string; appRendererClient: AppRendererClient | null; connect: () => Promise; disconnect: () => Promise; } /** * React hook that subscribes to InspectorClient events and provides reactive * connection state. Log lists (message / stderr / fetch) live in dedicated * state managers consumed via useMessageLog / useStderrLog / useFetchRequestLog. * * Note: `appRendererClient` is read lazily from the client on every render * and is NOT subscribed. It changes once at connect time and is not expected * to change again during a session, so callers will see the current value * on any rerender triggered by status / capabilities / serverInfo / instructions. * If a future use case requires autonomous updates when the renderer attaches, * add an `appRendererClientChange` event to `InspectorClientEventMap` and * subscribe here. */ export function useInspectorClient( inspectorClient: InspectorClientProtocol | null, ): UseInspectorClientResult { const [status, setStatus] = useState( inspectorClient?.getStatus() ?? "disconnected", ); const [capabilities, setCapabilities] = useState< ServerCapabilities | undefined >(inspectorClient?.getCapabilities()); const [serverInfo, setServerInfo] = useState( inspectorClient?.getServerInfo(), ); const [instructions, setInstructions] = useState( inspectorClient?.getInstructions(), ); useEffect(() => { if (!inspectorClient) { setStatus("disconnected"); setCapabilities(undefined); setServerInfo(undefined); setInstructions(undefined); return; } setStatus(inspectorClient.getStatus()); setCapabilities(inspectorClient.getCapabilities()); setServerInfo(inspectorClient.getServerInfo()); setInstructions(inspectorClient.getInstructions()); const onStatusChange = (event: TypedEvent<"statusChange">) => { setStatus(event.detail); }; const onCapabilitiesChange = (event: TypedEvent<"capabilitiesChange">) => { setCapabilities(event.detail); }; const onServerInfoChange = (event: TypedEvent<"serverInfoChange">) => { setServerInfo(event.detail); }; const onInstructionsChange = (event: TypedEvent<"instructionsChange">) => { setInstructions(event.detail); }; inspectorClient.addEventListener("statusChange", onStatusChange); inspectorClient.addEventListener( "capabilitiesChange", onCapabilitiesChange, ); inspectorClient.addEventListener("serverInfoChange", onServerInfoChange); inspectorClient.addEventListener( "instructionsChange", onInstructionsChange, ); return () => { inspectorClient.removeEventListener("statusChange", onStatusChange); inspectorClient.removeEventListener( "capabilitiesChange", onCapabilitiesChange, ); inspectorClient.removeEventListener( "serverInfoChange", onServerInfoChange, ); inspectorClient.removeEventListener( "instructionsChange", onInstructionsChange, ); }; }, [inspectorClient]); const connect = useCallback(async () => { if (!inspectorClient) return; await inspectorClient.connect(); }, [inspectorClient]); const disconnect = useCallback(async () => { if (!inspectorClient) return; await inspectorClient.disconnect(); }, [inspectorClient]); return { status, capabilities, serverInfo, instructions, appRendererClient: inspectorClient?.getAppRendererClient() ?? null, connect, disconnect, }; }