Every site tagged `ext-apps#702` carried a workaround for ext-apps
peering on the v1 SDK while the Inspector runs v2. With 2.0.0 the
package's `AppBridge` extends the v2 `Protocol`, takes the v2 `Client`
and `Implementation` directly, and registers its list-changed forwarding
on that client with `setNotificationHandler("notifications/…", handler)`,
so:
- core/mcp/apps.ts: `getToolUiResourceUri(tool)` takes the v2 `Tool`
without a cast; `UiMetaShape` is replaced by ext-apps' own
`McpUiToolMeta` / `McpUiResourceMeta`, which resolve under NodeNext
since ext-apps#705. `AppInfo.csp` / `.permissions` / `.visibility`
take the named types too.
- core/mcp/inspectorClient.ts: `getAppRendererClient()` returns the SDK
client itself; the Proxy that translated the 1.x schema-first
`setNotificationHandler(Schema, handler)` into the method-string form,
its `notificationMethodFromSchema` helper and its cached field are
gone. `AppRendererClient` is now `Client` rather than `unknown`.
- createAppBridgeFactory.ts: `new AppBridge(client, HOST_INFO, …)` with
no `as unknown as`; `resolveSourceUri` passes the tool straight through.
- AppRenderer.tsx: `sendToolResult(result)` — its params ARE the v2
`CallToolResult` now.
- requestAppElicitation.ts: the double cast on the `elicitation/create`
send goes too — v2 `Protocol.request` takes the standard `Request`
shape, which `ElicitRequest` is. The helper itself stays: ext-apps#733
is not in 2.0.0.
- core/mcp/extensions.ts: `MCP_APP_MIME_TYPE` re-exports ext-apps'
`RESOURCE_MIME_TYPE` from `/app-bridge`; `UI_EXTENSION_KEY` stays
hardcoded because `EXTENSION_ID` is still `/server`-only in 2.0.0.
Tests: the Proxy-translation test is removed with the Proxy; the
`getAppRendererClient` tests assert the real `Client` instance and that
disconnect withdraws it; the `useInspectorClient` sentinel is a real
(unconnected) `Client`. Comments that cited ext-apps 1.7.5 now cite 2.0.0
where the fact still holds (#733 still unreleased).
Closes #1745
Refs #2309
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp
Signed-off-by: cliffhall <cliff@futurescale.com>
218 lines
8.7 KiB
TypeScript
218 lines
8.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,
|
|
ResourceSubscriptionStreamState,
|
|
ExcludedTool,
|
|
RequestMetadata,
|
|
} from "./types.js";
|
|
import type {
|
|
CacheMode,
|
|
Client,
|
|
ClientCapabilities,
|
|
DiscoverResult,
|
|
Implementation,
|
|
LoggingLevel,
|
|
Prompt,
|
|
ProtocolEra,
|
|
Resource,
|
|
ResourceTemplateType as ResourceTemplate,
|
|
ServerCapabilities,
|
|
Task,
|
|
Tool,
|
|
} from "@modelcontextprotocol/client";
|
|
import type { JsonValue } from "../json/jsonUtils.js";
|
|
import type { MalformedListItem } from "./listSalvage.js";
|
|
import type { InspectorClientEventTarget } from "./inspectorClientEventTarget.js";
|
|
import type { SkillEntry } from "./skillsSchemas.js";
|
|
import type { SkillsExtensionSupport } from "./skills.js";
|
|
import type { DirectoryReadResult } from "./skillsSchemas.js";
|
|
import type { SamplingCreateMessage } from "./samplingCreateMessage.js";
|
|
import type { ElicitationCreateMessage } from "./elicitationCreateMessage.js";
|
|
|
|
/**
|
|
* The SDK `Client` the MCP Apps host bridge is built over. ext-apps'
|
|
* `AppBridge` takes it directly and forwards a running app's
|
|
* tools/resources/prompts calls (and list-changed notifications) through it.
|
|
* Since ext-apps 2.0.0 peers on SDK v2 this is the real client with nothing in
|
|
* between — the v1-peer translation proxy it used to name is gone (#1745).
|
|
*/
|
|
export type AppRendererClient = Client;
|
|
|
|
/**
|
|
* 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;
|
|
getProtocolEra(): ProtocolEra | undefined;
|
|
/** Tools excluded from `tools/list` for invalid `x-mcp-header` annotations
|
|
* (SEP-2243); empty on legacy/stdio connections (#1632). */
|
|
getExcludedTools(): ExcludedTool[];
|
|
/** Entries dropped from a list result as malformed, across every list method;
|
|
* empty against a conforming server (#1909). */
|
|
getMalformedListItems(): MalformedListItem[];
|
|
getResourceSubscriptionStreamState(): ResourceSubscriptionStreamState;
|
|
getDiscoverResult(): DiscoverResult | undefined;
|
|
getServerSettings(): InspectorServerSettings | undefined;
|
|
setServerSettings(settings: InspectorServerSettings): void;
|
|
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?: RequestMetadata,
|
|
): Promise<{ tools: Tool[]; nextCursor?: string }>;
|
|
listPrompts(
|
|
cursor?: string,
|
|
metadata?: RequestMetadata,
|
|
): Promise<{ prompts: Prompt[]; nextCursor?: string }>;
|
|
listResources(
|
|
cursor?: string,
|
|
metadata?: RequestMetadata,
|
|
): Promise<{ resources: Resource[]; nextCursor?: string }>;
|
|
listResourceTemplates(
|
|
cursor?: string,
|
|
metadata?: RequestMetadata,
|
|
): Promise<{ resourceTemplates: ResourceTemplate[]; nextCursor?: string }>;
|
|
listRequestorTasks(
|
|
cursor?: string,
|
|
): Promise<{ tasks: Task[]; nextCursor?: string }>;
|
|
/** Poll one requestor task's current status (era-aware: modern `DetailedTask`
|
|
* via `tasks/get`, or the legacy flattened task). Dispatches
|
|
* `requestorTaskUpdated`. Used by the modern task store's refresh (no
|
|
* `tasks/list`). */
|
|
getRequestorTask(taskId: string): Promise<Task>;
|
|
/** True when a modern (2026-07-28) connection negotiated the
|
|
* `io.modelcontextprotocol/tasks` extension (SEP-2663). Gates the Tasks tab
|
|
* and the modern task store's poll-based refresh. */
|
|
isTasksExtensionNegotiated(): boolean;
|
|
|
|
/** The Skills extension (SEP-2640) the server declared, or `undefined`.
|
|
* Gates the Skills tab and the managed skills store (#2234). */
|
|
getSkillsExtension(): SkillsExtensionSupport | undefined;
|
|
/** One page of `skills/list`; the managed skills store walks the cursor. */
|
|
listSkills(
|
|
cursor?: string,
|
|
metadata?: RequestMetadata,
|
|
): Promise<{ skills: SkillEntry[]; nextCursor?: string }>;
|
|
/** One skill entry by URI (`skills/get`). */
|
|
getSkill(uri: string, metadata?: RequestMetadata): Promise<SkillEntry>;
|
|
/**
|
|
* One page of `resources/directory/read` — the direct children of a directory
|
|
* resource (#2248). Optional on this interface, unlike the two methods above:
|
|
* declaring the extension commits a server to `skills/list` and `skills/get`,
|
|
* while this one is separately gated on `directoryRead`, so a caller has to
|
|
* check for it anyway and the many test doubles that satisfy this interface
|
|
* should not all have to grow a method most of them never reach.
|
|
*/
|
|
readResourceDirectory?(
|
|
uri: string,
|
|
cursor?: string,
|
|
metadata?: RequestMetadata,
|
|
): Promise<DirectoryReadResult>;
|
|
|
|
/**
|
|
* Mark the response that most recently answered `method` as rejected by the
|
|
* client, so its Protocol entry shows the reason instead of rendering as a
|
|
* clean success (#1953). Optional so existing test doubles satisfy the
|
|
* interface without implementing it; see `InspectorClient` for how the id is
|
|
* correlated and why that correlation is exact.
|
|
*/
|
|
markResponseRejected?(method: string, reason: string): void;
|
|
|
|
// Aggregate (all-page) list methods used by the managed state stores on
|
|
// refresh. Unlike the single-page methods above, these route through the
|
|
// SDK's cache-aware high-level verbs so `cacheMode` ('use' | 'refresh' |
|
|
// 'bypass') is honored — a manual refresh forces a cache-bypassing round trip
|
|
// on modern servers that send `ttlMs` hints. Options are optional; omitting
|
|
// `cacheMode` uses the SDK default ('use').
|
|
listAllTools(options?: {
|
|
cacheMode?: CacheMode;
|
|
metadata?: RequestMetadata;
|
|
}): Promise<{ tools: Tool[] }>;
|
|
listAllPrompts(options?: {
|
|
cacheMode?: CacheMode;
|
|
metadata?: RequestMetadata;
|
|
}): Promise<{ prompts: Prompt[] }>;
|
|
listAllResources(options?: {
|
|
cacheMode?: CacheMode;
|
|
metadata?: RequestMetadata;
|
|
}): Promise<{ resources: Resource[] }>;
|
|
listAllResourceTemplates(options?: {
|
|
cacheMode?: CacheMode;
|
|
metadata?: RequestMetadata;
|
|
}): Promise<{ resourceTemplates: ResourceTemplate[] }>;
|
|
|
|
// Invocation methods used by paged result panels
|
|
callTool(
|
|
tool: Tool,
|
|
args: Record<string, JsonValue>,
|
|
generalMetadata?: RequestMetadata,
|
|
toolSpecificMetadata?: RequestMetadata,
|
|
): Promise<ToolCallInvocation>;
|
|
readResource(
|
|
uri: string,
|
|
metadata?: RequestMetadata,
|
|
): Promise<ResourceReadInvocation>;
|
|
readResourceFromTemplate(
|
|
uriTemplate: string,
|
|
params: Record<string, string>,
|
|
metadata?: RequestMetadata,
|
|
): Promise<ResourceTemplateReadInvocation>;
|
|
getPrompt(
|
|
name: string,
|
|
params?: Record<string, string>,
|
|
metadata?: RequestMetadata,
|
|
): 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?: RequestMetadata,
|
|
): Promise<{ values: string[]; total?: number; hasMore?: boolean }>;
|
|
}
|