Extensions UI: own Advertised Extensions section + 'Client Advertised Extensions' heading (#1748)

* Settings: move Advertised Extensions to its own accordion section (#1747)

The Advertised Extensions control (added in #1739) lived inside the Options
accordion panel, wedged between "Fetch Lists One Page at a Time" and "Network
Log Size". Promote it to its own top-level "Advertised Extensions" accordion
section (after Options), so the distinct concept — which extensions the
Inspector advertises to this server in its client capabilities — reads as its
own collapsible section.

- ServerSettingsForm: add "extensions" to ServerSettingsSection; move the
  checkbox group into a new Accordion.Item value="extensions". Its description
  moves up into the panel; no behavior change (still folds into
  settings.advertisedExtensions via onAdvertisedExtensionChange).
- ServerSettingsModal: add "extensions" to allSectionsFor so expand/collapse-all
  covers it.
- Tests: the toggle now lives under the new (collapsed-by-default) section, so
  the form tests render it expanded and the modal tests open it first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Connection Info: rename "Advertised Extensions" heading to "Client Advertised Extensions"

Disambiguates the client-side column from the adjacent "Server Extensions"
column and from the (separate) Server Settings "Advertised Extensions" section.
Label-only change; the value still renders clientCapabilities.extensions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* docs: refresh Connection Info screenshot for the renamed heading

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* docs: refresh #1747 proof screenshots (fresh capture)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Settings: add AdvertisedExtensions Storybook story (review)

Address the #1748 review's optional note: the new Advertised Extensions
accordion section had no story expanding it, so it wasn't documented in the
visual catalog (the default story expands only ['options']). Add an
AdvertisedExtensions story with expandedSections: ['extensions'] and a
per-server override, showing the default-on and toggled-off checkbox states.

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:
Cliff Hall 2026-07-23 12:10:13 -04:00 • committed by GitHub
commit c51d6aabd2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 66 additions and 36 deletions

View file

@ -216,7 +216,9 @@ describe("ConnectionInfoContent", () => {
protocolEra="legacy"
/>,
);
expect(screen.getByText("Advertised Extensions")).toBeInTheDocument();
expect(
screen.getByText("Client Advertised Extensions"),
).toBeInTheDocument();
expect(
screen.getByText(
"io.modelcontextprotocol/tasks, io.modelcontextprotocol/ui",
@ -234,7 +236,9 @@ describe("ConnectionInfoContent", () => {
/>,
);
expect(screen.getByText("Server Extensions")).toBeInTheDocument();
expect(screen.getByText("Advertised Extensions")).toBeInTheDocument();
expect(
screen.getByText("Client Advertised Extensions"),
).toBeInTheDocument();
// Exactly two em dashes: the two extension sections (the server version is
// present in the fixture, so it does not em-dash).
expect(screen.getAllByText("—")).toHaveLength(2);

View file

@ -260,7 +260,7 @@ export function ConnectionInfoContent({
<ValueText>{formatExtensions(capabilities.extensions)}</ValueText>
</Stack>
<Stack gap="xs">
<SectionHeading>Advertised Extensions</SectionHeading>
<SectionHeading>Client Advertised Extensions</SectionHeading>
<ValueText>
{formatExtensions(clientCapabilities.extensions)}
</ValueText>

View file

@ -233,6 +233,19 @@ export const WithHeaders: Story = {
},
};
export const AdvertisedExtensions: Story = {
args: {
// Expand the standalone Advertised Extensions section so its checkboxes are
// documented visually. A per-server override disables one extension, showing
// both the default-on and toggled-off states side by side.
expandedSections: ["extensions"],
settings: {
...defaultSettings,
advertisedExtensions: { "io.modelcontextprotocol/tasks": false },
},
},
};
export const WithOAuth: Story = {
args: {
settings: {

View file

@ -458,9 +458,13 @@ describe("ServerSettingsForm", () => {
<ServerSettingsForm
{...baseHandlers}
settings={emptySettings}
expandedSections={["options"]}
expandedSections={["extensions"]}
/>,
);
// Advertised Extensions is now its own accordion section (#1747).
expect(
screen.getByRole("button", { name: "Advertised Extensions" }),
).toBeInTheDocument();
const tasks = screen.getByRole("checkbox", {
name: /Tasks \(io\.modelcontextprotocol\/tasks\)/,
});
@ -476,7 +480,7 @@ describe("ServerSettingsForm", () => {
...emptySettings,
advertisedExtensions: { "io.modelcontextprotocol/tasks": false },
}}
expandedSections={["options"]}
expandedSections={["extensions"]}
/>,
);
expect(
@ -494,7 +498,7 @@ describe("ServerSettingsForm", () => {
{...baseHandlers}
onAdvertisedExtensionChange={onAdvertisedExtensionChange}
settings={emptySettings}
expandedSections={["options"]}
expandedSections={["extensions"]}
/>,
);
// Default is checked; clicking flips it to unadvertised.

View file

@ -44,6 +44,7 @@ function isExtensionAdvertised(
export type ServerSettingsSection =
| "options"
| "extensions"
| "environment"
| "headers"
| "metadata"
@ -401,34 +402,6 @@ export function ServerSettingsForm({
checked={settings.paginatedLists ?? false}
onChange={(e) => onPaginatedListsChange(e.currentTarget.checked)}
/>
<Stack gap="xs">
<Text size="sm" fw={500}>
Advertised Extensions
</Text>
<Text size="xs" c="var(--inspector-text-secondary)">
Which extensions the Inspector declares to this server in its
client capabilities. A server may register different tools
depending on what the client advertises — toggle these to debug
that. Takes effect on the next connect.
</Text>
{ADVERTISABLE_EXTENSIONS.map((ext) => (
<Checkbox
key={ext.key}
label={ext.label}
checked={isExtensionAdvertised(
settings,
ext.key,
ext.defaultAdvertised,
)}
onChange={(e) =>
onAdvertisedExtensionChange(
ext.key,
e.currentTarget.checked,
)
}
/>
))}
</Stack>
<NumberInput
label="Network Log Size"
description="Maximum number of HTTP requests kept in the Network log for this server. Older entries rotate out past this limit; a response body that arrives after its entry rotated out is dropped. Use 0 for unlimited (not recommended). Applies immediately to the active connection."
@ -456,6 +429,34 @@ export function ServerSettingsForm({
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value="extensions">
<Accordion.Control>Advertised Extensions</Accordion.Control>
<Accordion.Panel>
<Stack gap="xs">
<Text size="xs" c="var(--inspector-text-secondary)">
Which extensions the Inspector declares to this server in its
client capabilities. A server may register different tools
depending on what the client advertises — toggle these to debug
that. Takes effect on the next connect.
</Text>
{ADVERTISABLE_EXTENSIONS.map((ext) => (
<Checkbox
key={ext.key}
label={ext.label}
checked={isExtensionAdvertised(
settings,
ext.key,
ext.defaultAdvertised,
)}
onChange={(e) =>
onAdvertisedExtensionChange(ext.key, e.currentTarget.checked)
}
/>
))}
</Stack>
</Accordion.Panel>
</Accordion.Item>
{isStdio ? (
<Accordion.Item value="environment">
<Accordion.Control>Environment Variables</Accordion.Control>

View file

@ -175,8 +175,11 @@ describe("ServerSettingsModal", () => {
onSettingsChange={onSettingsChange}
/>,
);
// The Options section is expanded by default; Tasks is checked (registry
// default), so clicking it toggles the override off.
// Advertised Extensions is its own accordion section (#1747), collapsed by
// default — open it, then toggle Tasks (checked by the registry default) off.
await user.click(
screen.getByRole("button", { name: "Advertised Extensions" }),
);
await user.click(
screen.getByRole("checkbox", {
name: /Tasks \(io\.modelcontextprotocol\/tasks\)/,
@ -207,6 +210,10 @@ describe("ServerSettingsModal", () => {
onSettingsChange={onSettingsChange}
/>,
);
// Open the Advertised Extensions section (#1747), then re-check Tasks.
await user.click(
screen.getByRole("button", { name: "Advertised Extensions" }),
);
await user.click(
screen.getByRole("checkbox", {
name: /Tasks \(io\.modelcontextprotocol\/tasks\)/,

View file

@ -26,6 +26,7 @@ function allSectionsFor(
): ServerSettingsSection[] {
return [
"options",
"extensions",
...(isStdio ? (["environment"] as const) : []),
"headers",
"metadata",

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB