feat(site/src/pages/AgentsPage): show MCP settings and hide insights in sidebar (#24428)

This commit is contained in:
Danielle Maywood
2026-04-21 17:53:17 +01:00
committed by GitHub
parent 148e56b5d9
commit 7f4127bc61
3 changed files with 877 additions and 552 deletions
@@ -81,8 +81,14 @@ export const EmptyState: Story = {
await expect(
await body.findByText(/No MCP servers configured yet/i),
).toBeInTheDocument();
await expect(
body.getByRole("button", { name: /Add Server/i }),
// Both the section header and the empty state render a distinct
// Add button, and the empty-state one is the primary CTA.
expect(
body.getByRole("button", { name: "Add server" }),
).toBeInTheDocument();
expect(
body.getByRole("button", { name: "Add your first server" }),
).toBeInTheDocument();
},
};
@@ -137,6 +143,9 @@ export const ServerList: Story = {
).toBeInTheDocument();
expect(body.getByRole("button", { name: /Linear/ })).toBeInTheDocument();
expect(body.getByRole("button", { name: /GitHub/ })).toBeInTheDocument();
// Disabled servers surface a warning badge next to the name.
expect(body.getByText(/^disabled$/i)).toBeInTheDocument();
},
};
@@ -150,14 +159,26 @@ export const CreateServer: Story = {
// Click Add Server.
await userEvent.click(
await body.findByRole("button", { name: /Add Server/i }),
await body.findByRole("button", { name: /Add your first server/i }),
);
// Fill in the display name via the inline header input.
const nameInput = await body.findByLabelText(/Display Name/i);
await userEvent.type(nameInput, "Sentry");
// Slug should auto-populate.
// Required fields (slug, server URL) are always visible. Optional
// sections start collapsed and the Enabled switch is edit-only.
// PencilIcon sits next to the editable name.
expect(body.getByLabelText(/^Slug/i)).toBeInTheDocument();
expect(body.getByLabelText(/Server URL/i)).toBeInTheDocument();
expect(body.queryByLabelText(/Description/i)).not.toBeInTheDocument();
expect(
body.queryByRole("switch", { name: /Enabled/i }),
).not.toBeInTheDocument();
const nameRow = nameInput.closest("div")?.parentElement;
expect(nameRow?.querySelector("svg.lucide-pencil")).toBeTruthy();
// Slug should auto-populate from the display name.
await expect(body.getByLabelText(/^Slug/i)).toHaveValue("sentry");
await userEvent.type(
@@ -192,7 +213,7 @@ export const CreateServerOAuth2: Story = {
const body = within(canvasElement.ownerDocument.body);
await userEvent.click(
await body.findByRole("button", { name: /Add Server/i }),
await body.findByRole("button", { name: /Add your first server/i }),
);
await userEvent.type(await body.findByLabelText(/Display Name/i), "GitHub");
@@ -201,6 +222,9 @@ export const CreateServerOAuth2: Story = {
"https://api.githubcopilot.com/mcp/",
);
await userEvent.click(
await body.findByRole("button", { name: /Authentication/i }),
);
// Select OAuth2 from the Radix Select dropdown.
await userEvent.click(body.getByLabelText(/Authentication/i));
await userEvent.click(await body.findByRole("option", { name: /OAuth2/i }));
@@ -241,7 +265,7 @@ export const CreateServerAPIKey: Story = {
const body = within(canvasElement.ownerDocument.body);
await userEvent.click(
await body.findByRole("button", { name: /Add Server/i }),
await body.findByRole("button", { name: /Add your first server/i }),
);
await userEvent.type(await body.findByLabelText(/Display Name/i), "Linear");
@@ -250,6 +274,9 @@ export const CreateServerAPIKey: Story = {
"https://mcp.linear.app/v1",
);
await userEvent.click(
await body.findByRole("button", { name: /Authentication/i }),
);
// Select API Key from the Radix Select dropdown.
await userEvent.click(body.getByLabelText(/Authentication/i));
await userEvent.click(
@@ -306,11 +333,16 @@ export const EditServer: Story = {
// The inline name input should be pre-populated.
const nameInput = await body.findByLabelText(/Display Name/i);
expect(nameInput).toHaveValue("Sentry");
// Slug and Server URL are always visible.
expect(body.getByLabelText(/^Slug/i)).toHaveValue("sentry");
expect(body.getByLabelText(/Server URL/i)).toHaveValue(
"https://mcp.sentry.io/sse",
);
// Expand Details to reach the description field.
await userEvent.click(body.getByRole("button", { name: /Details/i }));
// Update the description.
const descField = body.getByLabelText(/Description/i);
await userEvent.clear(descField);
@@ -355,6 +387,11 @@ export const EditServerWithOAuth2Secret: Story = {
await userEvent.click(await body.findByRole("button", { name: /GitHub/ }));
// Authentication section is collapsed by default.
await userEvent.click(
await body.findByRole("button", { name: /Authentication/i }),
);
// The OAuth2 fields should be visible.
const secretField = await body.findByLabelText(/Client Secret/i);
expect(secretField).toHaveValue("••••••••••••••••");
@@ -385,6 +422,11 @@ export const EditServerWithCustomHeaders: Story = {
await body.findByRole("button", { name: /Custom API/ }),
);
// Authentication section is collapsed by default.
await userEvent.click(
await body.findByRole("button", { name: /Authentication/i }),
);
// Should show message about existing headers.
await expect(
await body.findByText(/has custom headers configured/i),
@@ -477,6 +519,31 @@ export const DeleteServerCancelled: Story = {
};
/** Confirm delete in dialog calls the API. */
export const DirectEditWhileLoading: Story = {
parameters: {
reactRouter: reactRouterParameters({
location: {
path: "/agents/settings/mcp-servers",
searchParams: { server: "mcp-sentry" },
},
routing: { path: "/agents/settings/mcp-servers" },
}),
},
args: {
serversData: undefined,
isLoadingServers: true,
},
play: async ({ canvasElement }) => {
const body = within(canvasElement.ownerDocument.body);
await expect(await body.findByText(/^Loading$/i)).toBeInTheDocument();
expect(body.queryByLabelText(/Display Name/i)).not.toBeInTheDocument();
expect(
body.queryByRole("button", { name: /Save changes/i }),
).not.toBeInTheDocument();
},
};
export const DeleteServerConfirmed: Story = {
args: {
serversData: [
@@ -519,7 +586,7 @@ export const BackToList: Story = {
const body = within(canvasElement.ownerDocument.body);
await userEvent.click(
await body.findByRole("button", { name: /Add Server/i }),
await body.findByRole("button", { name: /^Add server$/i }),
);
// Click Back.
@@ -543,7 +610,7 @@ export const CreateServerWithToolGovernance: Story = {
const body = within(canvasElement.ownerDocument.body);
await userEvent.click(
await body.findByRole("button", { name: /Add Server/i }),
await body.findByRole("button", { name: /Add your first server/i }),
);
await userEvent.type(
@@ -555,6 +622,7 @@ export const CreateServerWithToolGovernance: Story = {
"https://mcp.example.com/v1",
);
await userEvent.click(body.getByRole("button", { name: /Behavior/i }));
await userEvent.type(
body.getByLabelText(/Tool Allow List/i),
"search, read_file",
@@ -587,7 +655,7 @@ export const CustomHeadersAuthType: Story = {
const body = within(canvasElement.ownerDocument.body);
await userEvent.click(
await body.findByRole("button", { name: /Add Server/i }),
await body.findByRole("button", { name: /Add your first server/i }),
);
await userEvent.type(
@@ -599,6 +667,9 @@ export const CustomHeadersAuthType: Story = {
"https://mcp.example.com/v1",
);
await userEvent.click(
await body.findByRole("button", { name: /Authentication/i }),
);
// Select Custom Headers auth type.
await userEvent.click(body.getByLabelText(/Authentication/i));
await userEvent.click(
File diff suppressed because it is too large Load Diff
@@ -39,13 +39,13 @@ import {
PauseIcon,
PinIcon,
PinOffIcon,
ServerIcon,
SettingsIcon,
ShieldIcon,
SquarePenIcon,
Trash2Icon,
UserIcon,
WalletIcon,
WandSparklesIcon,
} from "lucide-react";
import {
createContext,
@@ -1360,18 +1360,18 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
adminOnly
/>
<SettingsNavItem
icon={WalletIcon}
label="Spend"
active={sidebarView.section === "spend"}
to="/agents/settings/spend"
icon={ServerIcon}
label="MCP Servers"
active={sidebarView.section === "mcp-servers"}
to="/agents/settings/mcp-servers"
state={location.state}
adminOnly
/>
<SettingsNavItem
icon={WandSparklesIcon}
label="Insights"
active={sidebarView.section === "insights"}
to="/agents/settings/insights"
icon={WalletIcon}
label="Spend"
active={sidebarView.section === "spend"}
to="/agents/settings/spend"
state={location.state}
adminOnly
/>