* feat(web): show negotiated MCP protocol version on ServerCard (#1324) Add optional protocolVersion to ConnectionState and render it as a dimmed "MCP <version>" label at the end of the transport-badge/ description row on ServerCard, shown only when connected. The value is plumbed through useInspectorClient as part of #1324. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(core,web): surface negotiated protocolVersion end-to-end (#1324) Plumb the MCP protocol version negotiated during initialize from the InspectorClient through to the ServerCard so the active connected card displays "MCP <version>". The SDK Client doesn't expose protocolVersion; it stamps it onto the transport via setProtocolVersion. MessageTrackingTransport now captures it (and forwards to the base transport when present), so it works for stdio too — whose base transport has no setProtocolVersion. - core: MessageTrackingTransport captures + exposes protocolVersion; InspectorClient stores it, exposes getProtocolVersion(), dispatches a protocolVersionChange event, and clears it on disconnect. - core: useInspectorClient subscribes and returns protocolVersion. - web: App reads it from the hook (drops the hard-coded "2025-06-18") and InspectorView splices it onto the active server's connection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): decouple initializeResult from protocolVersion (#1324 review) Gate the initializeResult memo on serverInfo only, not protocolVersion. initializeResult also drives the connected ViewHeader and Connection Info modal, so requiring the version risked hiding the whole connected surface in the edge case where it's absent. protocolVersion now flows through as the optional field it already is everywhere downstream ('' reads as unknown; the ServerCard label and modal value both tolerate it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): drop redundant connected-check in InspectorView splice (#1324 review) initializeResult is derived from connectionStatus in App's memo, so a truthy initializeResult.protocolVersion already implies a live connection — the extra connectionStatus === 'connected' guard in the splice was redundant. ServerCard keeps its own status guard (dumb- component robustness). No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
121f875a0e
commit
9b558afee1
7 changed files with 273 additions and 12 deletions
|
|
@ -43,6 +43,7 @@ export interface FakeInspectorClientOptions {
|
|||
clientCapabilities?: ClientCapabilities;
|
||||
serverInfo?: Implementation;
|
||||
instructions?: string;
|
||||
protocolVersion?: string;
|
||||
}
|
||||
|
||||
export class FakeInspectorClient
|
||||
|
|
@ -54,6 +55,7 @@ export class FakeInspectorClient
|
|||
private clientCapabilities: ClientCapabilities;
|
||||
private serverInfo: Implementation | undefined;
|
||||
private instructions: string | undefined;
|
||||
private protocolVersion: string | undefined;
|
||||
private appRendererClient: AppRendererClient | null = null;
|
||||
private sessionId: string | undefined;
|
||||
private pendingSamples: SamplingCreateMessage[] = [];
|
||||
|
|
@ -147,6 +149,7 @@ export class FakeInspectorClient
|
|||
this.clientCapabilities = options.clientCapabilities ?? {};
|
||||
this.serverInfo = options.serverInfo;
|
||||
this.instructions = options.instructions;
|
||||
this.protocolVersion = options.protocolVersion;
|
||||
}
|
||||
|
||||
getStatus(): ConnectionStatus {
|
||||
|
|
@ -169,6 +172,10 @@ export class FakeInspectorClient
|
|||
return this.instructions;
|
||||
}
|
||||
|
||||
getProtocolVersion(): string | undefined {
|
||||
return this.protocolVersion;
|
||||
}
|
||||
|
||||
getAppRendererClient(): AppRendererClient | null {
|
||||
return this.appRendererClient;
|
||||
}
|
||||
|
|
@ -235,6 +242,11 @@ export class FakeInspectorClient
|
|||
this.dispatchTypedEvent("instructionsChange", instructions);
|
||||
}
|
||||
|
||||
setProtocolVersion(protocolVersion: string | undefined): void {
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.dispatchTypedEvent("protocolVersionChange", protocolVersion);
|
||||
}
|
||||
|
||||
setAppRendererClient(client: AppRendererClient | null): void {
|
||||
this.appRendererClient = client;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
private capabilities?: ServerCapabilities;
|
||||
private serverInfo?: Implementation;
|
||||
private instructions?: string;
|
||||
private protocolVersion?: string;
|
||||
// The capabilities this Inspector client advertises to the server during the
|
||||
// initialize handshake. Built once in setupClient() and snapshotted here so
|
||||
// UI surfaces (Server Info modal) can display them without poking at the
|
||||
|
|
@ -1050,11 +1051,13 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
this.capabilities = undefined;
|
||||
this.serverInfo = undefined;
|
||||
this.instructions = undefined;
|
||||
this.protocolVersion = undefined;
|
||||
this.dispatchTypedEvent("pendingSamplesChange", this.pendingSamples);
|
||||
this.dispatchTypedEvent("pendingElicitationsChange", this.pendingElicitations);
|
||||
this.dispatchTypedEvent("capabilitiesChange", this.capabilities);
|
||||
this.dispatchTypedEvent("serverInfoChange", this.serverInfo);
|
||||
this.dispatchTypedEvent("instructionsChange", this.instructions);
|
||||
this.dispatchTypedEvent("protocolVersionChange", this.protocolVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1295,6 +1298,14 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
return this.instructions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MCP protocol version negotiated with the server during the
|
||||
* initialize handshake (e.g. "2025-06-18"). Undefined when not connected.
|
||||
*/
|
||||
getProtocolVersion(): string | undefined {
|
||||
return this.protocolVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the logging level for the MCP server
|
||||
* @param level Logging level to set
|
||||
|
|
@ -2193,6 +2204,14 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
if (this.instructions !== undefined) {
|
||||
this.dispatchTypedEvent("instructionsChange", this.instructions);
|
||||
}
|
||||
|
||||
// The negotiated protocol version isn't exposed by the SDK Client; it's
|
||||
// stamped onto the transport during initialize. MessageTrackingTransport
|
||||
// captures it (see its setProtocolVersion) so we can surface it here.
|
||||
if (this.transport instanceof MessageTrackingTransport) {
|
||||
this.protocolVersion = this.transport.protocolVersion;
|
||||
this.dispatchTypedEvent("protocolVersionChange", this.protocolVersion);
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors in fetching server info
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ export interface InspectorClientEventMap {
|
|||
capabilitiesChange: ServerCapabilities | undefined;
|
||||
serverInfoChange: Implementation | undefined;
|
||||
instructionsChange: string | undefined;
|
||||
protocolVersionChange: string | undefined;
|
||||
message: MessageEntry;
|
||||
stderrLog: StderrLogEntry;
|
||||
fetchRequest: FetchRequestEntry;
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export interface InspectorClientProtocol extends InspectorClientEventTarget {
|
|||
getClientCapabilities(): ClientCapabilities;
|
||||
getServerInfo(): Implementation | undefined;
|
||||
getInstructions(): string | undefined;
|
||||
getProtocolVersion(): string | undefined;
|
||||
getAppRendererClient(): AppRendererClient | null;
|
||||
|
||||
// Connection control
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export interface MessageTrackingCallbacks {
|
|||
export class MessageTrackingTransport implements Transport {
|
||||
private baseTransport: Transport;
|
||||
private callbacks: MessageTrackingCallbacks;
|
||||
private negotiatedProtocolVersion?: string;
|
||||
|
||||
constructor(
|
||||
baseTransport: Transport,
|
||||
|
|
@ -146,7 +147,19 @@ export class MessageTrackingTransport implements Transport {
|
|||
return this.baseTransport.sessionId;
|
||||
}
|
||||
|
||||
get setProtocolVersion(): ((version: string) => void) | undefined {
|
||||
return this.baseTransport.setProtocolVersion;
|
||||
// Implemented as a concrete method (rather than delegating the base
|
||||
// transport's optional `setProtocolVersion`) so the SDK Client always
|
||||
// invokes it after the initialize handshake — including for stdio, whose
|
||||
// base transport has no `setProtocolVersion`. We capture the negotiated
|
||||
// version for the UI here, then forward to the base transport when it
|
||||
// cares (HTTP transports stamp it into subsequent request headers).
|
||||
setProtocolVersion(version: string): void {
|
||||
this.negotiatedProtocolVersion = version;
|
||||
this.baseTransport.setProtocolVersion?.(version);
|
||||
}
|
||||
|
||||
/** MCP protocol version negotiated during initialize, once connected. */
|
||||
get protocolVersion(): string | undefined {
|
||||
return this.negotiatedProtocolVersion;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,6 +143,13 @@ export interface ConnectionState {
|
|||
status: ConnectionStatus;
|
||||
retryCount?: number;
|
||||
error?: { message: string; details?: string };
|
||||
/**
|
||||
* MCP protocol version negotiated with the server during initialize
|
||||
* (e.g. "2025-06-18"). Only present once connected; surfaced in the
|
||||
* ServerCard transport row. Populated when #1324 plumbs the value
|
||||
* through `useInspectorClient`.
|
||||
*/
|
||||
protocolVersion?: string;
|
||||
}
|
||||
|
||||
export interface ServerEntry {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export interface UseInspectorClientResult {
|
|||
clientCapabilities: ClientCapabilities;
|
||||
serverInfo?: Implementation;
|
||||
instructions?: string;
|
||||
protocolVersion?: string;
|
||||
appRendererClient: AppRendererClient | null;
|
||||
connect: () => Promise<void>;
|
||||
disconnect: () => Promise<void>;
|
||||
|
|
@ -54,6 +55,9 @@ export function useInspectorClient(
|
|||
const [instructions, setInstructions] = useState<string | undefined>(
|
||||
inspectorClient?.getInstructions(),
|
||||
);
|
||||
const [protocolVersion, setProtocolVersion] = useState<string | undefined>(
|
||||
inspectorClient?.getProtocolVersion(),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!inspectorClient) {
|
||||
|
|
@ -61,6 +65,7 @@ export function useInspectorClient(
|
|||
setCapabilities(undefined);
|
||||
setServerInfo(undefined);
|
||||
setInstructions(undefined);
|
||||
setProtocolVersion(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +73,7 @@ export function useInspectorClient(
|
|||
setCapabilities(inspectorClient.getCapabilities());
|
||||
setServerInfo(inspectorClient.getServerInfo());
|
||||
setInstructions(inspectorClient.getInstructions());
|
||||
setProtocolVersion(inspectorClient.getProtocolVersion());
|
||||
|
||||
const onStatusChange = (event: TypedEvent<"statusChange">) => {
|
||||
setStatus(event.detail);
|
||||
|
|
@ -81,6 +87,11 @@ export function useInspectorClient(
|
|||
const onInstructionsChange = (event: TypedEvent<"instructionsChange">) => {
|
||||
setInstructions(event.detail);
|
||||
};
|
||||
const onProtocolVersionChange = (
|
||||
event: TypedEvent<"protocolVersionChange">,
|
||||
) => {
|
||||
setProtocolVersion(event.detail);
|
||||
};
|
||||
|
||||
inspectorClient.addEventListener("statusChange", onStatusChange);
|
||||
inspectorClient.addEventListener(
|
||||
|
|
@ -92,6 +103,10 @@ export function useInspectorClient(
|
|||
"instructionsChange",
|
||||
onInstructionsChange,
|
||||
);
|
||||
inspectorClient.addEventListener(
|
||||
"protocolVersionChange",
|
||||
onProtocolVersionChange,
|
||||
);
|
||||
|
||||
return () => {
|
||||
inspectorClient.removeEventListener("statusChange", onStatusChange);
|
||||
|
|
@ -107,6 +122,10 @@ export function useInspectorClient(
|
|||
"instructionsChange",
|
||||
onInstructionsChange,
|
||||
);
|
||||
inspectorClient.removeEventListener(
|
||||
"protocolVersionChange",
|
||||
onProtocolVersionChange,
|
||||
);
|
||||
};
|
||||
}, [inspectorClient]);
|
||||
|
||||
|
|
@ -132,6 +151,7 @@ export function useInspectorClient(
|
|||
inspectorClient?.getClientCapabilities() ?? EMPTY_CLIENT_CAPABILITIES,
|
||||
serverInfo,
|
||||
instructions,
|
||||
protocolVersion,
|
||||
appRendererClient: inspectorClient?.getAppRendererClient() ?? null,
|
||||
connect,
|
||||
disconnect,
|
||||
|
|
|
|||
Loading…
Reference in a new issue