Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions site/src/modules/mcpServers/MCPServerIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { cn } from "cn";
import { ServerIcon } from "lucide-react";
import type { FC } from "react";
import { ExternalImage } from "#/components/ExternalImage/ExternalImage";

const variantClasses = {
tile: "rounded border border-solid border-border",
circle: "rounded-full",
};

// Decorative: every consumer renders the server name as text or labels
// the containing control, so the icon itself carries no alt text.
export const MCPServerIcon: FC<{
iconUrl: string;
className?: string;
variant?: keyof typeof variantClasses;
}> = ({ iconUrl, className, variant = "tile" }) => {
return (
<div
className={cn(
"flex shrink-0 items-center justify-center bg-surface-secondary",
variantClasses[variant],
className,
)}
>
{iconUrl ? (
<ExternalImage src={iconUrl} alt="" className="size-3/5" />
) : (
<ServerIcon className="size-3/5 text-content-secondary" />
)}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "#/components/Tooltip/Tooltip";
import { MCPServerIcon } from "./MCPServerIcon";
import { MCPServerIcon } from "#/modules/mcpServers/MCPServerIcon";

const MCPServerFormBackLink: FC<{ to: string }> = ({ to }) => {
return (
Expand Down Expand Up @@ -82,9 +82,7 @@ export const MCPServerFormHeader: FC<MCPServerFormHeaderProps> = ({
)}
</div>
<div className="flex items-center gap-4 pt-6 min-w-0">
{isEditing && (
<MCPServerIcon iconUrl={iconUrl} name={title} className="size-12" />
)}
{isEditing && <MCPServerIcon iconUrl={iconUrl} className="size-12" />}
<SettingsHeaderTitle>
<span
className={cn(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type * as TypesGen from "#/api/typesGenerated";
import { Badge } from "#/components/Badge/Badge";
import { TableCell, TableRow } from "#/components/Table/Table";
import { useClickableTableRow } from "#/hooks/useClickableTableRow";
import { MCPServerIcon } from "./MCPServerIcon";
import { MCPServerIcon } from "#/modules/mcpServers/MCPServerIcon";
import { AUTH_TYPE_LABELS, AVAILABILITY_LABELS } from "./mcpServerFormLogic";

interface MCPServerRowProps {
Expand All @@ -25,7 +25,6 @@ export const MCPServerRow: FC<MCPServerRowProps> = ({ server, onClick }) => {
<div className="flex min-w-0 items-center gap-3">
<MCPServerIcon
iconUrl={server.icon_url}
name={server.display_name}
className={cn("size-10", !enabled && "opacity-50")}
/>
<span
Expand Down
119 changes: 73 additions & 46 deletions site/src/pages/AgentsPage/components/AgentChatInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { MonitorDotIcon } from "lucide-react";
import { useEffect, useRef } from "react";
import { type ComponentProps, useEffect, useRef } from "react";
import { expect, fn, spyOn, userEvent, waitFor, within } from "storybook/test";
import { API } from "#/api/api";
import { preferenceSettingsKey } from "#/api/queries/users";
Expand Down Expand Up @@ -687,7 +687,7 @@ const startMCPOAuthFlow = async (canvasElement: HTMLElement) => {

// ── MCP stories ────────────────────────────────────────────────

/** Input with multiple MCP servers selected — shows icon stack in toolbar. */
/** Multiple selected servers share one grouped pill with an icon stack. */
export const WithMCPServers: Story = {
args: {
...mcpDefaults,
Expand All @@ -696,6 +696,24 @@ export const WithMCPServers: Story = {
},
};

export const MCPGroupPopoverOpen: Story = {
args: WithMCPServers.args,
play: async ({ canvasElement }) => {
await userEvent.click(
within(canvasElement).getByRole("button", { name: "3 MCP servers" }),
);
await within(document.body).findByRole("dialog");
},
};

export const MCPGroupDisabled: Story = {
args: {
...WithMCPServers.args,
isDisabled: true,
},
play: MCPGroupPopoverOpen.play,
};

/** MCP server needing OAuth — shows Auth button instead of toggle. */
export const WithMCPNeedingAuth: Story = {
args: {
Expand Down Expand Up @@ -1277,26 +1295,57 @@ const pagerdutyMCP = buildMCPServer({
enabled: true,
});

// Wide badges that cannot fit force into +N overflow.
const confluenceWideMCP = buildMCPServer({
id: "mcp-confluence-wide",
display_name: "Confluence Cloud Enterprise Wiki",
slug: "confluence-wide",
availability: "default_on",
auth_type: "none",
enabled: true,
});
const mockOverflowAttachedWorkspace = {
id: MockWorkspace.id,
name: "an-extremely-long-attached-workspace-name-that-cannot-fit-inline",
route: "/@admin/attached",
statusIcon: <MonitorDotIcon className="size-3" />,
statusLabel: "Workspace running",
} satisfies ComponentProps<typeof AgentChatInput>["attachedWorkspace"];

const datadogWideMCP = buildMCPServer({
id: "mcp-datadog-wide",
display_name: "Datadog Infrastructure Monitoring",
slug: "datadog-wide",
availability: "default_on",
auth_type: "none",
enabled: true,
});
export const MCPGroupMoreThanThree: Story = {
args: {
...mcpDefaults,
mcpServers: [
sentryMCP,
linearMCP,
githubMCPConnected,
notionMCPConnected,
confluenceMCP,
],
selectedMCPServerIds: [
sentryMCP.id,
linearMCP.id,
githubMCPConnected.id,
notionMCPConnected.id,
confluenceMCP.id,
],
},
};

export const MCPGroupInOverflow: Story = {
args: {
...WithMCPServers.args,
attachedWorkspace: mockOverflowAttachedWorkspace,
onWorkspaceChange: fn(),
},
parameters: {
viewport: { defaultViewport: "mobile2" },
pixel: { matrix: { viewports: ["phone"] } },
},
play: async ({ canvasElement }) => {
await userEvent.click(
await within(canvasElement).findByRole("button", { name: /more item/ }),
);
const overflow = await within(document.body).findByRole("dialog");
await userEvent.click(
within(overflow).getByRole("button", { name: "3 MCP servers" }),
);
await within(document.body).findByRole("button", { name: "Remove Linear" });
},
};

/** Many tools with a workspace at 414px — forces overflow and "+N" pill. */
/** A long attached workspace keeps the grouped tools in the "+N" overflow. */
export const OverflowBadges: Story = {
args: {
...mcpDefaults,
Expand All @@ -1316,31 +1365,8 @@ export const OverflowBadges: Story = {
datadogMCP.id,
pagerdutyMCP.id,
],
workspaceOptions: [
{
id: "ws-1",
name: "my-long-workspace-name",
owner_name: "admin",
organization_id: "org-1",
},
],
selectedWorkspaceId: "ws-1",
attachedWorkspace: mockOverflowAttachedWorkspace,
onWorkspaceChange: fn(),
attachedWorkspace: {
id: "ws-1",
name: "my-long-workspace-name",
route: "/@admin/my-long-workspace-name",
statusIcon: <MonitorDotIcon className="size-3" />,
statusLabel: "Workspace running",
},
workspace: {
...MockWorkspace,
id: "ws-1",
name: "my-long-workspace-name",
owner_name: "admin",
},
workspaceAgent: MockWorkspaceAgent,
chatId: "overflow-chat-id",
},
parameters: {
viewport: { defaultViewport: "mobile2" },
Expand Down Expand Up @@ -1552,8 +1578,9 @@ export const ModelExpandsWhileBadgesOverflow: Story = {
displayName: "Claude Sonnet 4.5",
},
],
mcpServers: [confluenceWideMCP, datadogWideMCP],
selectedMCPServerIds: [confluenceWideMCP.id, datadogWideMCP.id],
mcpServers: [confluenceMCP, datadogMCP],
selectedMCPServerIds: [confluenceMCP.id, datadogMCP.id],
attachedWorkspace: mockOverflowAttachedWorkspace,
},
parameters: {
viewport: { defaultViewport: "mobile2" },
Expand Down
Loading
Loading