diff --git a/ui/litellm-dashboard/src/app/connect/layout.test.tsx b/ui/litellm-dashboard/src/app/connect/layout.test.tsx
new file mode 100644
index 00000000000..795a79d77e3
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/connect/layout.test.tsx
@@ -0,0 +1,65 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { render, screen } from "@testing-library/react";
+import ConnectLayout from "./layout";
+
+const { mockUseAuthorized, state } = vi.hoisted(() => {
+ const state = {
+ accessToken: "token-123" as string | null,
+ isAuthorized: true,
+ isLoading: false,
+ };
+ return {
+ state,
+ mockUseAuthorized: vi.fn(() => ({
+ accessToken: state.accessToken,
+ isAuthorized: state.isAuthorized,
+ isLoading: state.isLoading,
+ })),
+ };
+});
+
+vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ default: mockUseAuthorized }));
+vi.mock("@/components/navbar", () => ({ default: () =>
}));
+vi.mock("@/contexts/ThemeContext", () => ({
+ ThemeProvider: ({ children }: { children: React.ReactNode }) => <>{children}>,
+}));
+
+describe("ConnectLayout", () => {
+ afterEach(() => {
+ state.accessToken = "token-123";
+ state.isAuthorized = true;
+ state.isLoading = false;
+ });
+
+ it("renders the connect surface for an authorized user without any chat-ui flag", () => {
+ render(
+
+
+ ,
+ );
+ expect(screen.getByTestId("navbar")).toBeInTheDocument();
+ expect(screen.getByTestId("page-content")).toBeInTheDocument();
+ });
+
+ it("renders nothing when the user is not authorized", () => {
+ state.isAuthorized = false;
+ render(
+
+
+ ,
+ );
+ expect(screen.queryByTestId("page-content")).not.toBeInTheDocument();
+ expect(screen.queryByTestId("navbar")).not.toBeInTheDocument();
+ });
+
+ it("renders nothing while authorization is still loading", () => {
+ state.isLoading = true;
+ render(
+
+
+ ,
+ );
+ expect(screen.queryByTestId("page-content")).not.toBeInTheDocument();
+ expect(screen.queryByTestId("navbar")).not.toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/connect/layout.tsx b/ui/litellm-dashboard/src/app/connect/layout.tsx
new file mode 100644
index 00000000000..63b1c484094
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/connect/layout.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
+import Navbar from "@/components/navbar";
+import { ThemeProvider } from "@/contexts/ThemeContext";
+
+export default function ConnectLayout({ children }: { children: React.ReactNode }) {
+ const { accessToken, isAuthorized, isLoading } = useAuthorized();
+
+ if (isLoading || !isAuthorized) return null;
+
+ return (
+
+
+
+ );
+}
diff --git a/ui/litellm-dashboard/src/app/connect/page.test.tsx b/ui/litellm-dashboard/src/app/connect/page.test.tsx
new file mode 100644
index 00000000000..07b0e7a305a
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/connect/page.test.tsx
@@ -0,0 +1,55 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { render, screen } from "@testing-library/react";
+import ConnectPage from "./page";
+
+interface PanelProps {
+ accessToken: string;
+ selectedServers: string[];
+ onChange: (servers: string[]) => void;
+}
+
+const { mockReplace, mockPanel, state } = vi.hoisted(() => {
+ const state = {
+ oauthReturn: null as string | null,
+ };
+ return {
+ state,
+ mockReplace: vi.fn(),
+ mockPanel: vi.fn((_props: PanelProps) => ),
+ };
+});
+
+vi.mock("next/navigation", () => ({
+ useRouter: () => ({ replace: mockReplace }),
+ useSearchParams: () => ({ get: (key: string) => (key === "mcpOauthReturn" ? state.oauthReturn : null) }),
+}));
+vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
+ default: () => ({ accessToken: "token-123" }),
+}));
+vi.mock("@/components/chat/MCPAppsPanel", () => ({ default: mockPanel }));
+
+describe("ConnectPage", () => {
+ afterEach(() => {
+ state.oauthReturn = null;
+ mockReplace.mockClear();
+ mockPanel.mockClear();
+ });
+
+ it("renders the MCP connect panel with the user's access token", () => {
+ render();
+ expect(screen.getByTestId("mcp-apps-panel")).toBeInTheDocument();
+ expect(mockPanel.mock.calls[0][0]).toMatchObject({ accessToken: "token-123", selectedServers: [] });
+ });
+
+ it("strips the mcpOauthReturn param from the URL after an OAuth return", () => {
+ state.oauthReturn = "apps";
+ window.history.replaceState({}, "", "/connect?mcpOauthReturn=apps");
+ render();
+ expect(mockReplace).toHaveBeenCalledWith("/connect");
+ });
+
+ it("does not rewrite the URL when there is no OAuth return param", () => {
+ render();
+ expect(mockReplace).not.toHaveBeenCalled();
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/connect/page.tsx b/ui/litellm-dashboard/src/app/connect/page.tsx
new file mode 100644
index 00000000000..84770915e46
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/connect/page.tsx
@@ -0,0 +1,36 @@
+"use client";
+
+import { Suspense, useEffect, useState } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
+import MCPAppsPanel from "@/components/chat/MCPAppsPanel";
+
+function ConnectPageContent() {
+ const { accessToken } = useAuthorized();
+ const [selectedServers, setSelectedServers] = useState([]);
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const oauthReturn = searchParams.get("mcpOauthReturn");
+
+ useEffect(() => {
+ if (oauthReturn) {
+ const url = new URL(window.location.href);
+ url.searchParams.delete("mcpOauthReturn");
+ router.replace(url.pathname + url.search);
+ }
+ }, [oauthReturn, router]);
+
+ return (
+
+
+
+ );
+}
+
+export default function ConnectPage() {
+ return (
+
+
+
+ );
+}