Extensions: Connection Info both eras + io.modelcontextprotocol/ui advertisement (Phase 3 of #1633) (#1744)
* Extensions: Connection Info both eras + io.modelcontextprotocol/ui advertisement (Phase 3 of #1633) Completes the Extensions surfacing and advertises the MCP Apps UI extension. core/mcp/extensions.ts: - Add UI_EXTENSION_KEY ("io.modelcontextprotocol/ui") and MCP_APP_MIME_TYPE ("text/html;profile=mcp-app") — mirrors of ext-apps' EXTENSION_ID / RESOURCE_MIME_TYPE (hardcoded: the constants live on the package's /server subpath or an extensionless re-export that doesn't resolve under NodeNext). - Registry entries gain an optional `advertisement` value; the builder stamps it (default {}). Add the `ui` entry, default-advertised with `{ mimeTypes: ["text/html;profile=mcp-app"] }` — the Inspector always renders App tools. No ext-apps cast needed: the extensions value type is `object`. ConnectionInfoContent: - Source server extensions from the negotiated `capabilities.extensions` (getServerCapabilities, era-transparent) instead of the modern-only discoverResult, so a LEGACY server advertising extensions renders them too. - Add a two-column "Server Extensions" / "Advertised Extensions" section (the Inspector's own advertised map), mirroring the server/client capability columns. Drop the now-redundant Extensions row from the modern Discovery section (Supported versions stays). Tests: extensions.test.ts covers the ui entry + advertisement value and the per-extension enable/disable; ConnectionInfoContent.test.tsx covers legacy server extensions (no discovery), the advertised-extensions column, and the em-dash empty states. npm run ci green (the one InspectorView localStorage failure is a known flake, passes on re-run). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: address Phase 3 review (mime drift-guard, story, exact em-dashes) - extensions-mimetype.test.ts (integration/node): assert MCP_APP_MIME_TYPE equals ext-apps' real RESOURCE_MIME_TYPE (imported) and that the client advertises it — a genuine drift guard, replacing the tautological literal check in the unit test (which now just asserts the type). - ConnectionInfoContent Modern story: carry the tasks extension on initializeResult.capabilities too, so the Server Extensions row demonstrates it (the row now reads negotiated server capabilities, not discoverResult). - Tighten the em-dash assertions from ">= N" to exact counts (3/3/2) now that the two extension sections em-dash by default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: drift-guard UI_EXTENSION_KEY against ext-apps EXTENSION_ID (review) Address the Phase 3 re-review nit: the extension id was the other half of the same drift risk (a server keys its Apps lookup on it). Import ext-apps' EXTENSION_ID from the /server subpath in the node integration test (fine there; the browser build is what avoids /server) and assert UI_EXTENSION_KEY === it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: clone the registry advertisement so builds never alias it (review) Address the Phase 3 re-review's optional defensive nit: buildClientExtensions returned the same object reference as the ADVERTISABLE_EXTENSIONS entry for the ui advertisement, so an in-place mutation of a stamped value (e.g. extensions[ui].mimeTypes) could corrupt the registry for later connections. structuredClone the advertisement on stamp; add a test asserting a mutated build doesn't leak into the next one. Nothing mutates it today — purely defensive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: add #1740 Connection Info proof screenshots End-to-end smoke test in the web client against the legacy advertised-extensions-http.json server: the Connection Info modal shows the new era-transparent Server Extensions / Advertised Extensions section, with Advertised Extensions listing io.modelcontextprotocol/tasks + io.modelcontextprotocol/ui (the new ui advertisement), and only ui after toggling Tasks off and reconnecting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fe295396af
commit
e2c75da5ce
1 changed files with 275 additions and 24 deletions
|
|
@ -10,6 +10,25 @@ import { TASKS_EXTENSION_KEY } from "./modernTaskSchemas.js";
|
|||
export const EMA_EXTENSION_KEY =
|
||||
"io.modelcontextprotocol/enterprise-managed-authorization";
|
||||
|
||||
/**
|
||||
* Extension identifier for the MCP Apps UI extension (SEP-ext-apps). Mirrors
|
||||
* `EXTENSION_ID` from `@modelcontextprotocol/ext-apps`. Hardcoded rather than
|
||||
* imported: that constant lives on the package's `/server` subpath, which would
|
||||
* pull server-only code into the browser bundle. The Inspector always renders
|
||||
* MCP Apps, so this is advertised by default (#1740).
|
||||
*/
|
||||
export const UI_EXTENSION_KEY = "io.modelcontextprotocol/ui";
|
||||
|
||||
/**
|
||||
* The MCP Apps UI resource MIME type the Inspector renders. Mirrors
|
||||
* `RESOURCE_MIME_TYPE` from `@modelcontextprotocol/ext-apps`; a server checks
|
||||
* for it in the client's advertised `io.modelcontextprotocol/ui` `mimeTypes` to
|
||||
* decide whether to serve an App. Hardcoded (stable spec string) because
|
||||
* ext-apps re-exports it through an extensionless path that doesn't resolve
|
||||
* cleanly under NodeNext — see the same note in `core/mcp/apps.ts`.
|
||||
*/
|
||||
export const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
|
||||
|
||||
/**
|
||||
* The value the client stamps for each advertised extension. The wire shape is
|
||||
* `{ [extensionId]: object }` (per `ClientCapabilities.extensions`); an empty
|
||||
|
|
@ -35,6 +54,12 @@ export interface AdvertisableExtension {
|
|||
* no explicit preference (the toggle's default position).
|
||||
*/
|
||||
defaultAdvertised: boolean;
|
||||
/**
|
||||
* The object value stamped into `capabilities.extensions[key]` when
|
||||
* advertised. Defaults to `{}` (declared, no sub-options); an extension that
|
||||
* carries settings — e.g. the UI extension's `mimeTypes` — sets its own shape.
|
||||
*/
|
||||
advertisement?: ExtensionAdvertisement;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -53,6 +78,15 @@ export const ADVERTISABLE_EXTENSIONS: readonly AdvertisableExtension[] = [
|
|||
// (server-directed task creation). Harmless on legacy (extensions ignored).
|
||||
defaultAdvertised: true,
|
||||
},
|
||||
{
|
||||
key: UI_EXTENSION_KEY,
|
||||
label: "MCP Apps UI (io.modelcontextprotocol/ui)",
|
||||
// The MCP Apps UI extension. The Inspector always renders App tools, so it
|
||||
// advertises this by default with the App resource MIME type it supports —
|
||||
// a conforming server checks the `mimeTypes` before serving a UI resource.
|
||||
defaultAdvertised: true,
|
||||
advertisement: { mimeTypes: [MCP_APP_MIME_TYPE] },
|
||||
},
|
||||
];
|
||||
|
||||
export interface BuildClientExtensionsInput {
|
||||
|
|
@ -83,7 +117,13 @@ export function buildClientExtensions(
|
|||
for (const ext of ADVERTISABLE_EXTENSIONS) {
|
||||
const advertised = input.advertised?.[ext.key] ?? ext.defaultAdvertised;
|
||||
if (advertised) {
|
||||
map[ext.key] = {};
|
||||
// Clone the registry advertisement so the returned map never aliases the
|
||||
// shared `ADVERTISABLE_EXTENSIONS` entry — a later in-place mutation of a
|
||||
// stamped value (e.g. `extensions[ui].mimeTypes`) can't corrupt the
|
||||
// registry for subsequent connections. `{}` entries are fresh literals.
|
||||
map[ext.key] = ext.advertisement
|
||||
? structuredClone(ext.advertisement)
|
||||
: {};
|
||||
}
|
||||
}
|
||||
if (input.enterpriseManaged) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue