Skip to content

Commit 405e3c0

Browse files
committed
Connect card: pin the MCP install URL to the URL's org, not the cookie's
A multi-org user loading /<orgB> while their session cookie still points at orgA saw orgA's slug flash into the copyable `npx add-mcp .../<slug>/mcp` box before /account/me resolved. On first paint `auth.organization` comes from the SSR auth-hint, which is scoped to the COOKIE's org — but the page is scoped to the URL's org. Seed OrganizationProvider's slug (which feeds the connect card via useOrganizationSlug) from the URL's `{-$orgSlug}` param, falling back to the session slug on a bare URL (which OrgSlugGate canonicalizes onto it anyway). The URL slug is the actual request scope and is correct on the very first paint, so the wrong slug never appears. OrgSlugGate keeps using the session slug for canonicalization; only the install-URL context changes. useOrganizationSlug is consumed solely by the connect card, so there's no other blast radius.
1 parent ce39ffc commit 405e3c0

1 file changed

Lines changed: 10 additions & 1 deletion

File tree

apps/cloud/src/routes/__root.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,15 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) {
275275
// which is the same origin, so the key never changes and nothing remounts.
276276
const connection = ssrOrigin ? ({ kind: "http", origin: ssrOrigin } as const) : undefined;
277277
const activeSlug = auth.organization.slug;
278+
// The org context's slug feeds the connect card's `/<slug>/mcp` install URL.
279+
// Prefer the URL's slug over the session's: on first paint `auth.organization`
280+
// comes from the SSR auth-hint (the COOKIE's org), so a multi-org user viewing
281+
// /<orgB> while their cookie still points at orgA would briefly render orgA's
282+
// slug in the copyable URL before /account/me (URL-scoped) corrects it. The
283+
// URL slug is the actual request scope and is correct on the very first paint,
284+
// so sourcing it from there removes that flash. Falls back to the session slug
285+
// on a bare URL (which OrgSlugGate is about to canonicalize onto it anyway).
286+
const scopeSlug = urlOrgSlug ?? activeSlug;
278287

279288
return (
280289
<AutumnProvider pathPrefix="/api/billing">
@@ -284,7 +293,7 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) {
284293
<ExecutorPluginsProvider plugins={clientPlugins}>
285294
<OrganizationProvider
286295
organizationId={auth.organization.id}
287-
organizationSlug={activeSlug}
296+
organizationSlug={scopeSlug}
288297
>
289298
{/* The org header scopes every request to the URL's org, so
290299
reaching here means the caller is a member of `activeSlug`

0 commit comments

Comments
 (0)