From d774c8061303a41a857807149a5d80eaa47d1197 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sun, 10 May 2026 22:08:01 -0700 Subject: [PATCH 1/2] Split cloud onboarding into create-org and setup-mcp routes The cloud app now routes onboarding through /create-org (org creation plus pending invitations) and /setup-mcp (MCP server URL and install command). AuthGate redirects an authenticated user without an org to /create-org and renders these routes without the app shell so they read as a focused two-step flow. After creating an org the user is taken to /setup-mcp; "Continue to app" lands at /. --- apps/cloud/src/routeTree.gen.ts | 42 ++++++++++ apps/cloud/src/routes/__root.tsx | 30 ++++++- apps/cloud/src/routes/create-org.tsx | 6 ++ apps/cloud/src/routes/setup-mcp.tsx | 6 ++ .../pages/{onboarding.tsx => create-org.tsx} | 11 ++- apps/cloud/src/web/pages/setup-mcp.tsx | 80 +++++++++++++++++++ 6 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 apps/cloud/src/routes/create-org.tsx create mode 100644 apps/cloud/src/routes/setup-mcp.tsx rename apps/cloud/src/web/pages/{onboarding.tsx => create-org.tsx} (96%) create mode 100644 apps/cloud/src/web/pages/setup-mcp.tsx diff --git a/apps/cloud/src/routeTree.gen.ts b/apps/cloud/src/routeTree.gen.ts index bc0ab8b70..ecffa48b2 100644 --- a/apps/cloud/src/routeTree.gen.ts +++ b/apps/cloud/src/routeTree.gen.ts @@ -10,9 +10,11 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as ToolsRouteImport } from './routes/tools' +import { Route as SetupMcpRouteImport } from './routes/setup-mcp' import { Route as SecretsRouteImport } from './routes/secrets' import { Route as PoliciesRouteImport } from './routes/policies' import { Route as OrgRouteImport } from './routes/org' +import { Route as CreateOrgRouteImport } from './routes/create-org' import { Route as ConnectionsRouteImport } from './routes/connections' import { Route as BillingRouteImport } from './routes/billing' import { Route as ApiKeysRouteImport } from './routes/api-keys' @@ -26,6 +28,11 @@ const ToolsRoute = ToolsRouteImport.update({ path: '/tools', getParentRoute: () => rootRouteImport, } as any) +const SetupMcpRoute = SetupMcpRouteImport.update({ + id: '/setup-mcp', + path: '/setup-mcp', + getParentRoute: () => rootRouteImport, +} as any) const SecretsRoute = SecretsRouteImport.update({ id: '/secrets', path: '/secrets', @@ -41,6 +48,11 @@ const OrgRoute = OrgRouteImport.update({ path: '/org', getParentRoute: () => rootRouteImport, } as any) +const CreateOrgRoute = CreateOrgRouteImport.update({ + id: '/create-org', + path: '/create-org', + getParentRoute: () => rootRouteImport, +} as any) const ConnectionsRoute = ConnectionsRouteImport.update({ id: '/connections', path: '/connections', @@ -82,9 +94,11 @@ export interface FileRoutesByFullPath { '/api-keys': typeof ApiKeysRoute '/billing': typeof BillingRoute '/connections': typeof ConnectionsRoute + '/create-org': typeof CreateOrgRoute '/org': typeof OrgRoute '/policies': typeof PoliciesRoute '/secrets': typeof SecretsRoute + '/setup-mcp': typeof SetupMcpRoute '/tools': typeof ToolsRoute '/billing/plans': typeof BillingPlansRoute '/sources/$namespace': typeof SourcesNamespaceRoute @@ -95,9 +109,11 @@ export interface FileRoutesByTo { '/api-keys': typeof ApiKeysRoute '/billing': typeof BillingRoute '/connections': typeof ConnectionsRoute + '/create-org': typeof CreateOrgRoute '/org': typeof OrgRoute '/policies': typeof PoliciesRoute '/secrets': typeof SecretsRoute + '/setup-mcp': typeof SetupMcpRoute '/tools': typeof ToolsRoute '/billing/plans': typeof BillingPlansRoute '/sources/$namespace': typeof SourcesNamespaceRoute @@ -109,9 +125,11 @@ export interface FileRoutesById { '/api-keys': typeof ApiKeysRoute '/billing': typeof BillingRoute '/connections': typeof ConnectionsRoute + '/create-org': typeof CreateOrgRoute '/org': typeof OrgRoute '/policies': typeof PoliciesRoute '/secrets': typeof SecretsRoute + '/setup-mcp': typeof SetupMcpRoute '/tools': typeof ToolsRoute '/billing_/plans': typeof BillingPlansRoute '/sources/$namespace': typeof SourcesNamespaceRoute @@ -124,9 +142,11 @@ export interface FileRouteTypes { | '/api-keys' | '/billing' | '/connections' + | '/create-org' | '/org' | '/policies' | '/secrets' + | '/setup-mcp' | '/tools' | '/billing/plans' | '/sources/$namespace' @@ -137,9 +157,11 @@ export interface FileRouteTypes { | '/api-keys' | '/billing' | '/connections' + | '/create-org' | '/org' | '/policies' | '/secrets' + | '/setup-mcp' | '/tools' | '/billing/plans' | '/sources/$namespace' @@ -150,9 +172,11 @@ export interface FileRouteTypes { | '/api-keys' | '/billing' | '/connections' + | '/create-org' | '/org' | '/policies' | '/secrets' + | '/setup-mcp' | '/tools' | '/billing_/plans' | '/sources/$namespace' @@ -164,9 +188,11 @@ export interface RootRouteChildren { ApiKeysRoute: typeof ApiKeysRoute BillingRoute: typeof BillingRoute ConnectionsRoute: typeof ConnectionsRoute + CreateOrgRoute: typeof CreateOrgRoute OrgRoute: typeof OrgRoute PoliciesRoute: typeof PoliciesRoute SecretsRoute: typeof SecretsRoute + SetupMcpRoute: typeof SetupMcpRoute ToolsRoute: typeof ToolsRoute BillingPlansRoute: typeof BillingPlansRoute SourcesNamespaceRoute: typeof SourcesNamespaceRoute @@ -182,6 +208,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ToolsRouteImport parentRoute: typeof rootRouteImport } + '/setup-mcp': { + id: '/setup-mcp' + path: '/setup-mcp' + fullPath: '/setup-mcp' + preLoaderRoute: typeof SetupMcpRouteImport + parentRoute: typeof rootRouteImport + } '/secrets': { id: '/secrets' path: '/secrets' @@ -203,6 +236,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof OrgRouteImport parentRoute: typeof rootRouteImport } + '/create-org': { + id: '/create-org' + path: '/create-org' + fullPath: '/create-org' + preLoaderRoute: typeof CreateOrgRouteImport + parentRoute: typeof rootRouteImport + } '/connections': { id: '/connections' path: '/connections' @@ -260,9 +300,11 @@ const rootRouteChildren: RootRouteChildren = { ApiKeysRoute: ApiKeysRoute, BillingRoute: BillingRoute, ConnectionsRoute: ConnectionsRoute, + CreateOrgRoute: CreateOrgRoute, OrgRoute: OrgRoute, PoliciesRoute: PoliciesRoute, SecretsRoute: SecretsRoute, + SetupMcpRoute: SetupMcpRoute, ToolsRoute: ToolsRoute, BillingPlansRoute: BillingPlansRoute, SourcesNamespaceRoute: SourcesNamespaceRoute, diff --git a/apps/cloud/src/routes/__root.tsx b/apps/cloud/src/routes/__root.tsx index 334fbe9ef..787206f06 100644 --- a/apps/cloud/src/routes/__root.tsx +++ b/apps/cloud/src/routes/__root.tsx @@ -1,6 +1,13 @@ import React from "react"; import * as Sentry from "@sentry/react"; -import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router"; +import { + HeadContent, + Outlet, + Scripts, + createRootRoute, + useLocation, + useNavigate, +} from "@tanstack/react-router"; import { AutumnProvider } from "autumn-js/react"; import posthog from "posthog-js"; import { PostHogProvider } from "posthog-js/react"; @@ -13,10 +20,11 @@ import { plugins as clientPlugins } from "virtual:executor/plugins-client"; import { AuthProvider, useAuth } from "../web/auth"; import { SupportOptions } from "../web/components/support-options"; import { LoginPage } from "../web/pages/login"; -import { OnboardingPage } from "../web/pages/onboarding"; import { Shell } from "../web/shell"; import appCss from "@executor-js/react/globals.css?url"; +const ONBOARDING_PATHS = new Set(["/create-org", "/setup-mcp"]); + if (typeof window !== "undefined" && import.meta.env.VITE_PUBLIC_SENTRY_DSN) { Sentry.init({ dsn: import.meta.env.VITE_PUBLIC_SENTRY_DSN, @@ -198,6 +206,18 @@ function ShellErrorFallback() { function AuthGate() { const auth = useAuth(); + const location = useLocation(); + const navigate = useNavigate(); + const isOnboardingRoute = ONBOARDING_PATHS.has(location.pathname); + + const needsOrgRedirect = + auth.status === "authenticated" && auth.organization == null && !isOnboardingRoute; + + React.useEffect(() => { + if (needsOrgRedirect) { + void navigate({ to: "/create-org", replace: true }); + } + }, [needsOrgRedirect, navigate]); if (auth.status === "loading") { return ; @@ -207,8 +227,12 @@ function AuthGate() { return ; } + if (isOnboardingRoute) { + return ; + } + if (auth.organization == null) { - return ; + return ; } return ( diff --git a/apps/cloud/src/routes/create-org.tsx b/apps/cloud/src/routes/create-org.tsx new file mode 100644 index 000000000..e56083a03 --- /dev/null +++ b/apps/cloud/src/routes/create-org.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { CreateOrgPage } from "../web/pages/create-org"; + +export const Route = createFileRoute("/create-org")({ + component: CreateOrgPage, +}); diff --git a/apps/cloud/src/routes/setup-mcp.tsx b/apps/cloud/src/routes/setup-mcp.tsx new file mode 100644 index 000000000..3af902d9f --- /dev/null +++ b/apps/cloud/src/routes/setup-mcp.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { SetupMcpPage } from "../web/pages/setup-mcp"; + +export const Route = createFileRoute("/setup-mcp")({ + component: SetupMcpPage, +}); diff --git a/apps/cloud/src/web/pages/onboarding.tsx b/apps/cloud/src/web/pages/create-org.tsx similarity index 96% rename from apps/cloud/src/web/pages/onboarding.tsx rename to apps/cloud/src/web/pages/create-org.tsx index 7074d2361..92c741ba6 100644 --- a/apps/cloud/src/web/pages/onboarding.tsx +++ b/apps/cloud/src/web/pages/create-org.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import { useAtomValue, useAtomSet } from "@effect/atom-react"; +import { useNavigate } from "@tanstack/react-router"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import * as Exit from "effect/Exit"; import { authWriteKeys } from "@executor-js/react/api/reactivity-keys"; @@ -39,8 +40,9 @@ const formatRelativeTime = (iso: string): string => { return `${Math.floor(days / 365)}y ago`; }; -export const OnboardingPage = () => { +export const CreateOrgPage = () => { const auth = useAuth(); + const navigate = useNavigate(); const invitationsResult = useAtomValue(pendingInvitationsAtom); const doAccept = useAtomSet(acceptInvitation, { mode: "promiseExit" }); @@ -75,7 +77,9 @@ export const OnboardingPage = () => { const form = useCreateOrganizationForm({ defaultName: suggestedName, - onSuccess: () => {}, + onSuccess: () => { + void navigate({ to: "/setup-mcp" }); + }, }); const isLoading = @@ -93,6 +97,9 @@ export const OnboardingPage = () => {
+

+ Step 1 of 2 +

{isLoading ? "Loading" diff --git a/apps/cloud/src/web/pages/setup-mcp.tsx b/apps/cloud/src/web/pages/setup-mcp.tsx new file mode 100644 index 000000000..20694e6c0 --- /dev/null +++ b/apps/cloud/src/web/pages/setup-mcp.tsx @@ -0,0 +1,80 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "@tanstack/react-router"; +import { Button } from "@executor-js/react/components/button"; +import { CodeBlock } from "@executor-js/react/components/code-block"; +import { CopyButton } from "@executor-js/react/components/copy-button"; + +const buildInstallCommand = (endpoint: string): string => + `npx add-mcp ${endpoint} --transport http --name executor`; + +export const SetupMcpPage = () => { + const navigate = useNavigate(); + const [origin, setOrigin] = useState(null); + + useEffect(() => { + setOrigin(window.location.origin); + }, []); + + const endpoint = origin ? `${origin}/mcp` : ""; + const command = endpoint ? buildInstallCommand(endpoint) : ""; + + return ( +
+
+
+

+ Step 2 of 2 +

+

Connect your MCP client

+

+ Executor exposes your sources, secrets, and tools to any MCP-compatible agent. Copy the + URL into your client, or run the install command. +

+
+ +
+

+ MCP server URL +

+
+ + {endpoint || "…"} + + {endpoint && } +
+

+ Paste this into your MCP client config. +

+
+ +
+
+ or +
+
+ +
+

+ Install command +

+ +

Adds the server to a supported agent.

+
+ +
+ {/* oxlint-disable-next-line react/forbid-elements */} + + +
+
+
+ ); +}; From 5f8a7b41e960df076ffa3197a8d1c26618108530 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sun, 10 May 2026 22:10:27 -0700 Subject: [PATCH 2/2] Format setup-mcp page --- apps/cloud/src/web/pages/setup-mcp.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/cloud/src/web/pages/setup-mcp.tsx b/apps/cloud/src/web/pages/setup-mcp.tsx index 20694e6c0..376e0407a 100644 --- a/apps/cloud/src/web/pages/setup-mcp.tsx +++ b/apps/cloud/src/web/pages/setup-mcp.tsx @@ -42,9 +42,7 @@ export const SetupMcpPage = () => { {endpoint && }
-

- Paste this into your MCP client config. -

+

Paste this into your MCP client config.