diff --git a/clients/web/src/App.tsx b/clients/web/src/App.tsx
index 5e005100..48295bcf 100644
--- a/clients/web/src/App.tsx
+++ b/clients/web/src/App.tsx
@@ -102,6 +102,7 @@ import { useInspectorVersion } from "@inspector/core/react/useInspectorVersion.j
import { usePendingClientRequests } from "@inspector/core/react/usePendingClientRequests.js";
import { InspectorView } from "./components/views/InspectorView/InspectorView";
import { VersionBadge } from "./components/elements/VersionBadge/VersionBadge";
+import { CopyrightBadge } from "./components/elements/CopyrightBadge/CopyrightBadge";
import type {
ToolCallState,
ToolsUiState,
@@ -3824,6 +3825,7 @@ function App() {
onRefreshApps={onRefreshTools}
/>
+
= {
+ title: "Elements/CopyrightBadge",
+ component: CopyrightBadge,
+};
+
+export default meta;
+type Story = StoryObj;
+
+// The grey copyright notice pinned to the lower-left corner of the viewport.
+export const Default: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ await expect(canvas.getByText(COPYRIGHT_NOTICE)).toBeInTheDocument();
+ },
+};
diff --git a/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.test.tsx b/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.test.tsx
new file mode 100644
index 00000000..271d5ba8
--- /dev/null
+++ b/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.test.tsx
@@ -0,0 +1,17 @@
+import { describe, it, expect } from "vitest";
+import { renderWithMantine, screen } from "../../../test/renderWithMantine";
+import { CopyrightBadge, COPYRIGHT_NOTICE } from "./CopyrightBadge";
+
+describe("CopyrightBadge", () => {
+ it("renders the project copyright notice", () => {
+ renderWithMantine();
+ expect(screen.getByText(COPYRIGHT_NOTICE)).toBeInTheDocument();
+ });
+
+ it("names the Model Context Protocol and LF Projects", () => {
+ renderWithMantine();
+ expect(
+ screen.getByText(/Model Context Protocol.*Series of LF Projects, LLC\./),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.tsx b/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.tsx
new file mode 100644
index 00000000..49f1fb7c
--- /dev/null
+++ b/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.tsx
@@ -0,0 +1,18 @@
+import { Text } from "@mantine/core";
+
+/** The project's copyright notice. */
+export const COPYRIGHT_NOTICE =
+ "Copyright © Model Context Protocol a Series of LF Projects, LLC.";
+
+// The `copyrightBadge` variant (src/theme/Text.ts) pins it to the lower-right
+// corner in grey, on the same row as the version badge, and non-interactive.
+const CopyrightText = Text.withProps({ variant: "copyrightBadge" });
+
+/**
+ * The project copyright notice, fixed to the lower-right corner of the screen
+ * (#1639) — the grey, non-interactive twin of the lower-left version badge,
+ * sharing its row.
+ */
+export function CopyrightBadge() {
+ return {COPYRIGHT_NOTICE};
+}
diff --git a/clients/web/src/components/elements/VersionBadge/VersionBadge.tsx b/clients/web/src/components/elements/VersionBadge/VersionBadge.tsx
index db7468f5..fbb834d7 100644
--- a/clients/web/src/components/elements/VersionBadge/VersionBadge.tsx
+++ b/clients/web/src/components/elements/VersionBadge/VersionBadge.tsx
@@ -5,12 +5,12 @@ export interface VersionBadgeProps {
version?: string;
}
-// The `versionBadge` variant (src/theme/Text.ts) pins it to the lower-right
+// The `versionBadge` variant (src/theme/Text.ts) pins it to the lower-left
// corner in grey and makes it non-interactive.
const VersionText = Text.withProps({ variant: "versionBadge" });
/**
- * A small, unobtrusive build-version label fixed to the lower-right corner of
+ * A small, unobtrusive build-version label fixed to the lower-left corner of
* the screen (#1639). Sourced from the root `package.json` via the backend's
* `GET /api/config`; renders nothing until the version is known (or on a legacy
* backend that omits it).
diff --git a/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx b/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx
index 28c8fbcf..97e9f3ee 100644
--- a/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx
+++ b/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx
@@ -1,4 +1,4 @@
-import { ActionIcon, Group, SegmentedControl, TextInput } from "@mantine/core";
+import { Button, Group, SegmentedControl, TextInput } from "@mantine/core";
import { TbLayoutSidebarRightCollapse } from "react-icons/tb";
import { ClearButton } from "../../elements/ClearButton/ClearButton";
@@ -69,15 +69,14 @@ export function MonitoringControls({
) : null
}
/>
-
-
+
);
}
diff --git a/clients/web/src/components/groups/ServerListControls/ServerListControls.test.tsx b/clients/web/src/components/groups/ServerListControls/ServerListControls.test.tsx
index d2d2e5ab..3cfde766 100644
--- a/clients/web/src/components/groups/ServerListControls/ServerListControls.test.tsx
+++ b/clients/web/src/components/groups/ServerListControls/ServerListControls.test.tsx
@@ -39,8 +39,9 @@ describe("ServerListControls", () => {
onToggleList={onToggleList}
/>,
);
- const buttons = screen.getAllByRole("button");
- await user.click(buttons[0]);
+ await user.click(
+ screen.getByRole("button", { name: /Expand all|Collapse all/ }),
+ );
expect(onToggleList).toHaveBeenCalledTimes(1);
});
diff --git a/clients/web/src/components/groups/ServerListControls/ServerListControls.tsx b/clients/web/src/components/groups/ServerListControls/ServerListControls.tsx
index a76c2f3c..ed6f0351 100644
--- a/clients/web/src/components/groups/ServerListControls/ServerListControls.tsx
+++ b/clients/web/src/components/groups/ServerListControls/ServerListControls.tsx
@@ -27,9 +27,6 @@ export function ServerListControls({
}: ServerListControlsProps) {
return (
- {serverCount > 0 && (
-
- )}
@@ -40,6 +37,9 @@ export function ServerListControls({
onImportServerJson={onImportServerJson}
/>
)}
+ {serverCount > 0 && (
+
+ )}
);
}
diff --git a/clients/web/src/theme/Text.ts b/clients/web/src/theme/Text.ts
index 1a635108..7a9ae782 100644
--- a/clients/web/src/theme/Text.ts
+++ b/clients/web/src/theme/Text.ts
@@ -37,15 +37,29 @@ export const ThemeText = Text.extend({
},
};
}
- // A small, unobtrusive build-version label pinned to the lower-right corner
- // of the screen (#1639): grey, non-interactive (clicks pass through), and
- // out of the tab/selection flow so it never interferes with the UI beneath.
- if (props.variant === "versionBadge") {
+ // Two small, unobtrusive labels pinned to the bottom corners of the screen
+ // on the same row (#1639): the build version (bottom-left) and the
+ // copyright notice (bottom-right). Each occupies the full-height `xl` bottom
+ // margin band (`height: xl` + flex centering) so the text sits vertically
+ // centered in it, and insets by `xl` horizontally to line up with the
+ // content's left/right margins. Both are grey, non-interactive (clicks pass
+ // through), and out of the tab/selection flow.
+ if (
+ props.variant === "versionBadge" ||
+ props.variant === "copyrightBadge"
+ ) {
+ const horizontal =
+ props.variant === "versionBadge"
+ ? { left: "var(--mantine-spacing-xl)" }
+ : { right: "var(--mantine-spacing-xl)" };
return {
root: {
position: "fixed",
- bottom: "0.35rem",
- right: "0.6rem",
+ bottom: 0,
+ height: "var(--mantine-spacing-xl)",
+ display: "flex",
+ alignItems: "center",
+ ...horizontal,
zIndex: 100,
color: "var(--inspector-text-secondary)",
fontSize: "var(--mantine-font-size-xs)",