fix(web): render the connected header when a modern server omits serverInfo (#1773)
A modern-era server/discover makes serverInfo optional (SHOULD, not MUST); a conforming modern server that omits it left initializeResult undefined, collapsing the entire connected header + tab bar so the server was uninspectable. Gate initializeResult on connectionStatus alone with a catalog-name fallback, and thread a serverInfoReported flag so the Connection Info modal reports the omission truthfully instead of presenting the inferred name as server-sent. Closes #1772.
This commit is contained in:
parent
da37692485
commit
820d38c438
1 changed files with 288 additions and 21 deletions
|
|
@ -1473,21 +1473,47 @@ function App() {
|
|||
};
|
||||
}, [inspectorClient]);
|
||||
|
||||
// The Server Info modal needs the active server's transport and (optional)
|
||||
// OAuth details — both are co-located here so the modal opens against the
|
||||
// same connection snapshot the header is reading. Also feeds the
|
||||
// `initializeResult` serverInfo fallback below.
|
||||
const activeServer = useMemo<ServerEntry | undefined>(
|
||||
() => servers.find((s) => s.id === activeServerId),
|
||||
[servers, activeServerId],
|
||||
);
|
||||
|
||||
// Whether the server actually reported `serverInfo`, vs. the catalog-name
|
||||
// fallback synthesized below. Threaded to the Connection Info modal so it can
|
||||
// show "not reported" instead of an inferred name that looks server-sent.
|
||||
const serverInfoReported = serverInfo !== undefined;
|
||||
|
||||
// Build the InitializeResult the connected ViewHeader / Connection Info
|
||||
// modal expect from the hook's split fields. `protocolVersion` is the value
|
||||
// the InspectorClient negotiated during initialize (#1324); it's dispatched
|
||||
// alongside serverInfo, so in practice it's present whenever we're connected.
|
||||
// We deliberately gate only on serverInfo (not protocolVersion): this object
|
||||
// also drives the connected header and Connection Info modal, so a
|
||||
// missing/edge-case version must not hide those. It flows through as the
|
||||
// optional field it is everywhere downstream (the ServerCard label and the
|
||||
// modal value both tolerate an empty string), so "" reads as "unknown".
|
||||
// We gate only on `connectionStatus`, never on serverInfo or protocolVersion:
|
||||
// this object also drives the connected header (and its whole tab bar) and the
|
||||
// Connection Info modal, so a missing field must not hide those.
|
||||
//
|
||||
// A modern-era server's `server/discover` makes `serverInfo` OPTIONAL (SHOULD,
|
||||
// not MUST — it's stamped in `_meta["io.modelcontextprotocol/serverInfo"]`), so
|
||||
// a conforming modern server may omit it and `serverInfo` stays undefined even
|
||||
// while connected. Falling back to the catalog name (rather than returning
|
||||
// `undefined`) keeps the header + tabs rendered for those servers (#1772). It
|
||||
// fires for such a modern server — and, harmlessly, in the batched instant on
|
||||
// any connect (legacy included) between the `connected` status dispatch and the
|
||||
// `serverInfo` dispatch, which land in a single React render. The modal uses
|
||||
// `serverInfoReported` (above), not this name, to stay faithful.
|
||||
const initializeResult = useMemo<InitializeResult | undefined>(() => {
|
||||
if (connectionStatus !== "connected" || !serverInfo) return undefined;
|
||||
if (connectionStatus !== "connected") return undefined;
|
||||
const resolvedServerInfo = serverInfo ?? {
|
||||
name: activeServer?.name ?? "",
|
||||
version: "",
|
||||
};
|
||||
return {
|
||||
protocolVersion: protocolVersion ?? "",
|
||||
capabilities: capabilities ?? {},
|
||||
serverInfo,
|
||||
serverInfo: resolvedServerInfo,
|
||||
...(instructions ? { instructions } : {}),
|
||||
};
|
||||
}, [
|
||||
|
|
@ -1496,16 +1522,9 @@ function App() {
|
|||
serverInfo,
|
||||
instructions,
|
||||
protocolVersion,
|
||||
activeServer,
|
||||
]);
|
||||
|
||||
// The Server Info modal needs the active server's transport and (optional)
|
||||
// OAuth details — both are co-located here so the modal opens against the
|
||||
// same connection snapshot the header is reading.
|
||||
const activeServer = useMemo<ServerEntry | undefined>(
|
||||
() => servers.find((s) => s.id === activeServerId),
|
||||
[servers, activeServerId],
|
||||
);
|
||||
|
||||
// Mirror the active server's name into a ref so a mid-session failure toast
|
||||
// can still name the server: a transport crash dispatches `disconnect`,
|
||||
// which clears `activeServerId` (and thus `activeServer`) before the
|
||||
|
|
@ -4448,6 +4467,7 @@ function App() {
|
|||
opened={connectionInfoModalOpen}
|
||||
onClose={() => setConnectionInfoModalOpen(false)}
|
||||
initializeResult={initializeResult}
|
||||
serverInfoReported={serverInfoReported}
|
||||
clientCapabilities={clientCapabilities}
|
||||
transport={connectionInfoTransport}
|
||||
protocolEra={protocolEra}
|
||||
|
|
|
|||
Loading…
Reference in a new issue