* feat(web): wire the listChanged indicator across all screens (#1402) The per-screen list-changed indicator was hardcoded false in InspectorView for Tools, Apps, Prompts, and Resources, so it could never light up. Source it from the managed-state layer instead. Each ManagedXState now tracks a listChanged flag: set true when its */list_changed notification arrives (the existing auto-refresh leaves it set), exposed via getListChanged()/a listChangedChange event, and cleared by clearListChanged() or on disconnect. The useManagedX hooks surface the flag and clear it on a user-initiated refresh (the indicator's Refresh button = acknowledge). App threads the three flags to InspectorView, which maps them to the screens — Apps shares the tools flag since apps are a filtered view of tools. Tests cover set/clear/disconnect at the state-manager layer, the hook subscription + refresh-clears behavior, and the InspectorView routing (including Apps sharing the tools flag and no cross-wiring between screens). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(core): close clear-after-await race in managed-list refresh (#1402 review) The useManagedX refresh() callbacks cleared the listChanged flag AFTER awaiting the fetch. If a */list_changed arrived mid-fetch, the state re-set the flag (and auto-refreshed), then the trailing clear wiped that genuinely-new signal. Clear BEFORE the await instead: the user's refresh acknowledges only the change in hand, and a mid-fetch change re-sets the flag and survives. Adds a race regression test on the tools hook. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): drop duplicate Refresh on the Apps screen (#1402) AppControls uniquely had a standalone toolbar Refresh button AND the ListChangedIndicator on a separate row, so once the indicator could actually appear (this PR), the Apps screen showed two Refresh buttons. Match the other three controls (Tools/Prompts/Resources): the indicator is the single header-right refresh affordance. Updates AppControls and AppsScreen tests to assert exactly one Refresh (and none when the list hasn't changed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(core): stop auto-invoking */list on list_changed; user pulls via Refresh (#1402) With auto-refresh on, the list was already current by the time the list-changed indicator appeared, making its Refresh button moot. Now the managed Tools/Prompts/Resources state managers only flag the change on */list_changed (they no longer auto-refresh); the list stays as-is until the user clicks Refresh, which fetches the new list and clears the indicator. The connect-time initial load is unchanged. Resource templates (no own indicator, part of the Resources screen) also stop auto-refreshing; onRefreshResources now refreshes both resources and templates, since a single resources/list_changed covers both. Tasks are left auto-refreshing — they have no list-changed indicator and need live status updates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(core): correct ManagedResourceTemplatesState header comments (#1402 review) The class no longer subscribes to resourceTemplatesListChanged; update the stale file/class docstrings that still claimed it syncs on that notification to reflect connect + statusChange only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): add per-server 'Auto Refresh on List Changed Notifications' option (#1402) New Options section in ServerSettingsForm with a single checkbox. When checked, the managed Tools/Prompts/Resources (and resource templates) state auto-refreshes on */list_changed notifications; when unchecked (default) the notification only lights the list-changed indicator and the user pulls via Refresh. Plumbing: new optional autoRefreshOnListChanged on InspectorServerSettings and StoredMCPServer, round-tripped through serverList (lifted with a false default, persisted only when true, added to the exhaustive field-key map); surfaced from InspectorClient via a new getServerSettings() on the protocol; the state managers read it live at notification time to decide auto-refresh vs flag-only. Resource templates re-subscribe to their list_changed but only auto-refresh when the option is on (they have no indicator of their own). Tests cover the round-trip (true persists, false omitted), the form section (render/checked/toggle), and each state manager's on/off branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(server): persist autoRefreshOnListChanged through validateSettings (#1402) The PUT /api/servers write path runs settings through validateSettings, which rebuilds the value from explicitly-named fields and silently drops unknown keys — so autoRefreshOnListChanged never reached disk (the serverList round-trip test passed because it calls inspectorSettingsToStoredFields directly, bypassing this path). Validate it as an optional boolean and include it in the built value; false is omitted on disk by inspectorSettingsToStoredFields as before. Adds integration tests over the real PUT path: true persists, false is omitted, and a non-boolean is rejected (400). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): default the Server Settings modal to the Options section (#1402) Open 'Options' instead of 'Custom Headers' when the Server Settings modal first opens, so the new Auto Refresh toggle is the first thing surfaced. Updates the modal default-expanded section, the modal test's initial-state assertions, the three header tests (now expand 'Custom Headers' first since it's no longer open by default), and the story defaults. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: drop accidentally-committed Inspector history dump; gitignore the class 83076441 swept an exported Inspector message-history JSON (manual-testing artifact at the repo root) into the tree via git add -A. Untrack it and extend the existing repo-root debug-artifact .gitignore block (alongside /*.png) to cover /inspector-history-*.json so these can't be re-added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
130 lines
4.7 KiB
TypeScript
130 lines
4.7 KiB
TypeScript
/**
|
|
* InspectorClientProtocol: the minimal surface that state managers (core/mcp/state/)
|
|
* and React hooks (core/react/) consume.
|
|
*
|
|
* v1.5 has a single concrete `InspectorClient` class (~2k LOC) bundling the transport,
|
|
* SDK client, OAuth flow, pending-request queues, and fetch tracking. Porting the full
|
|
* class requires first porting the auth subsystem (core/auth/, ~12 files) — that's
|
|
* deferred to a follow-up issue.
|
|
*
|
|
* This interface captures just what the state+hooks layer needs to function,
|
|
* so the layer is decoupled from how (or whether) the runtime is wired up.
|
|
* The real `InspectorClient` will implement this interface in the follow-up port.
|
|
*/
|
|
|
|
import type {
|
|
ConnectionStatus,
|
|
InspectorServerSettings,
|
|
ResourceReadInvocation,
|
|
ResourceTemplateReadInvocation,
|
|
PromptGetInvocation,
|
|
ToolCallInvocation,
|
|
} from "./types.js";
|
|
import type {
|
|
ClientCapabilities,
|
|
Implementation,
|
|
LoggingLevel,
|
|
Prompt,
|
|
Resource,
|
|
ResourceTemplate,
|
|
ServerCapabilities,
|
|
Task,
|
|
Tool,
|
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
import type { JsonValue } from "../json/jsonUtils.js";
|
|
import type { InspectorClientEventTarget } from "./inspectorClientEventTarget.js";
|
|
import type { SamplingCreateMessage } from "./samplingCreateMessage.js";
|
|
import type { ElicitationCreateMessage } from "./elicitationCreateMessage.js";
|
|
|
|
/**
|
|
* Opaque type representing the AppRendererClient surface used by @mcp-ui.
|
|
* v1.5 aliases this to the SDK `Client` type; v2 leaves it opaque until the
|
|
* real InspectorClient (or a focused App-renderer port) lands.
|
|
*/
|
|
export type AppRendererClient = unknown;
|
|
|
|
/**
|
|
* The contract every state manager and hook depends on. Anything that holds
|
|
* a reference to "an Inspector client" — including tests and fixtures —
|
|
* should depend on this protocol rather than the concrete class.
|
|
*/
|
|
export interface InspectorClientProtocol extends InspectorClientEventTarget {
|
|
// Connection state accessors
|
|
getStatus(): ConnectionStatus;
|
|
getCapabilities(): ServerCapabilities | undefined;
|
|
getClientCapabilities(): ClientCapabilities;
|
|
getServerInfo(): Implementation | undefined;
|
|
getInstructions(): string | undefined;
|
|
getProtocolVersion(): string | undefined;
|
|
getServerSettings(): InspectorServerSettings | undefined;
|
|
getAppRendererClient(): AppRendererClient | null;
|
|
|
|
// Connection control
|
|
connect(): Promise<void>;
|
|
disconnect(safeDisconnectTimeout?: number): Promise<void>;
|
|
|
|
// Paginated list methods used by managed/paged state stores
|
|
listTools(
|
|
cursor?: string,
|
|
metadata?: Record<string, string>,
|
|
): Promise<{ tools: Tool[]; nextCursor?: string }>;
|
|
listPrompts(
|
|
cursor?: string,
|
|
metadata?: Record<string, string>,
|
|
): Promise<{ prompts: Prompt[]; nextCursor?: string }>;
|
|
listResources(
|
|
cursor?: string,
|
|
metadata?: Record<string, string>,
|
|
): Promise<{ resources: Resource[]; nextCursor?: string }>;
|
|
listResourceTemplates(
|
|
cursor?: string,
|
|
metadata?: Record<string, string>,
|
|
): Promise<{ resourceTemplates: ResourceTemplate[]; nextCursor?: string }>;
|
|
listRequestorTasks(
|
|
cursor?: string,
|
|
): Promise<{ tasks: Task[]; nextCursor?: string }>;
|
|
|
|
// Invocation methods used by paged result panels
|
|
callTool(
|
|
tool: Tool,
|
|
args: Record<string, JsonValue>,
|
|
generalMetadata?: Record<string, string>,
|
|
toolSpecificMetadata?: Record<string, string>,
|
|
): Promise<ToolCallInvocation>;
|
|
readResource(
|
|
uri: string,
|
|
metadata?: Record<string, string>,
|
|
): Promise<ResourceReadInvocation>;
|
|
readResourceFromTemplate(
|
|
uriTemplate: string,
|
|
params: Record<string, string>,
|
|
metadata?: Record<string, string>,
|
|
): Promise<ResourceTemplateReadInvocation>;
|
|
getPrompt(
|
|
name: string,
|
|
params?: Record<string, string>,
|
|
metadata?: Record<string, string>,
|
|
): Promise<PromptGetInvocation>;
|
|
|
|
// Misc surface required by hooks/state
|
|
setLoggingLevel(level: LoggingLevel): Promise<void>;
|
|
getSessionId(): string | undefined;
|
|
|
|
// Server-initiated request queues. Each entry exposes `respond()` / `reject()`
|
|
// (and `remove()`), which resolve the handler Promise that left the originating
|
|
// call (e.g. a tool execution) in flight. The `pendingSamplesChange` /
|
|
// `pendingElicitationsChange` events fire whenever these arrays mutate.
|
|
getPendingSamples(): SamplingCreateMessage[];
|
|
getPendingElicitations(): ElicitationCreateMessage[];
|
|
|
|
// Completions (resource templates / prompt arguments)
|
|
getCompletions(
|
|
ref:
|
|
| { type: "ref/resource"; uri: string }
|
|
| { type: "ref/prompt"; name: string },
|
|
argumentName: string,
|
|
argumentValue: string,
|
|
context?: Record<string, string>,
|
|
metadata?: Record<string, string>,
|
|
): Promise<{ values: string[]; total?: number; hasMore?: boolean }>;
|
|
}
|