import { useAuth } from "@clerk/react"; import { useAtomValue } from "@effect/atom-react"; import { offersLinkedEnvironments } from "@t3tools/client-runtime/link-request"; import { AuthAccessWriteScope, AuthMcpClientAccess, AuthRelayReadScope, type EnvironmentConnectReachability, type EnvironmentId, managedCapabilityDenial, managedRuntimeModeDenial, mcpClientScopes, sessionGrantsScope, } from "@t3tools/contracts"; import { useNavigate } from "@tanstack/react-router"; import { Check, Copy } from "lucide"; import * as Option from "effect/Option"; import { Atom } from "effect/reactivity"; import * as Result from "effect/Result"; import { type ReactNode, useEffect, useEffectEvent, useState } from "react"; import { checkReachability, readLinkState } from "~/cloud/linkEnvironmentAtoms"; import { isManagedTunnelActive } from "~/cloud/t3ConnectState"; import { hasCloudPublicConfig } from "~/cloud/publicConfig"; import { readCachedCloudLinkState } from "~/cloud/cloudLinkState"; import { useCloudLinkController } from "~/cloud/useCloudLinkController"; import { isDesktopLocalConnectionTarget } from "~/connection/desktopLocal"; import { isElectron } from "~/env"; import { useT3ConnectTurnOn } from "~/cloud/useT3ConnectTurnOn"; import { useCopyToClipboard, writeTextToClipboard } from "~/hooks/useCopyToClipboard"; import { useNowSecond } from "~/hooks/useNowSecond"; import { readLocalApi } from "~/localApi"; import { appAtomRegistry } from "~/rpc/atomRegistry"; import { type EnvironmentPresentation, useEnvironmentIdentities, usePrimaryEnvironmentId, } from "~/state/environments"; import { useManagedPolicy } from "~/state/managedPolicy"; import { mintMcpApprovalCodeOn } from "~/state/peerPairingCode"; import { peerLinkEnvironment } from "~/state/peerLinks"; import { useEnvironmentQuery } from "~/state/query"; import { relayEnvironmentDiscovery } from "~/state/relay"; import { useEnvironmentScope, useEnvironmentSessionState } from "~/state/session"; import { useAtomCommand } from "~/state/use-atom-command"; import { useT3ConnectAuthPrompt } from "../clerk/useT3ConnectAuthPrompt"; import { CommandBlock } from "../CommandBlock"; import { MorphIcon } from "../MorphIcon"; import { AccessOption, accessConfig } from "../auth/ConnectAgentSurface"; import { useAccessEnvironment } from "../settings/useAccessEnvironment"; import { Button } from "../ui/button"; import { Dialog } from "../ui/dialog"; import { RadioGroup } from "../ui/radio-group"; import { Spinner } from "../ui/spinner"; import { toastManager } from "../ui/toast"; import { WizardFooter, WizardHeader, WizardPanel, WizardPopup, WizardSteps } from "../ui/wizard"; import { afterFailedCheck, resolveMcpGate } from "./mcpGate"; import { MCP_APPS, type McpApp, mcpHandoffAction, OTHER_MCP_APP_STEPS } from "./mcpApps"; const requestAtom = Atom.make<{ readonly environmentId: EnvironmentId | null } | null>(null).pipe( Atom.keepAlive, Atom.withLabel("mcp:use-in-another-app"), ); /** Opens the wizard for `environmentId`, or for the one Sharing & access picks when null. */ export function openUseInAnotherApp(environmentId: EnvironmentId | null): void { appAtomRegistry.set(requestAtom, { environmentId }); } const close = () => appAtomRegistry.set(requestAtom, null); /** Hosted once at the root, so Cmd-K and Settings can open it from anywhere. */ export function UseInAnotherAppDialogHost() { const request = useAtomValue(requestAtom); return request === null ? null : ; } function UseInAnotherAppDialog({ requested }: { readonly requested: EnvironmentId | null }) { const { environment, label } = useAccessEnvironment(requested); const title = `Use T3 Code in another app (MCP) · ${label ?? "T3 Code"}`; return ( (open ? undefined : close())}> {environment !== null && hasCloudPublicConfig() ? ( ) : ( <> )} ); } const STEPS = ["App", "Access", "Connect"] as const; const HOSTED_APPROVAL_STEPS = ["App", "Connect"] as const; type Step = 0 | 1 | 2; type Choice = McpApp | "other"; function Wizard({ environment, title, }: { readonly environment: EnvironmentPresentation; readonly title: string; }) { const gate = useGate(environment); const levels = useGrantableLevels(environment); const [step, setStep] = useState(0); const [choice, setChoice] = useState(null); const [picked, setPicked] = useState(null); const access = picked ?? DEFAULT_LEVELS.find((level) => levels.get(level) === undefined) ?? null; const open = gate.gate.kind === "open"; // The level is picked on the sign-in page, so the wizard neither asks for it nor needs a code. const hostedApproval = environment.serverConfig?.environment.capabilities.mcpHostedApproval === true; const steps = hostedApproval ? HOSTED_APPROVAL_STEPS : STEPS; const shownStep = hostedApproval && step === 2 ? 1 : step; const next = () => setStep(step === 0 ? (hostedApproval ? 2 : 1) : 2); const appName = choice === null || choice === "other" ? "the app" : choice.name; return ( <> {open ? ( index > shownStep} onStepChange={(index) => setStep(hostedApproval && index === 1 ? 2 : (index as Step))} /> ) : null} {gate.gate.kind !== "open" ? ( ) : step === 0 ? ( ) : step === 1 ? (

What can {appName} do?

setPicked(value as AuthMcpClientAccess)} > {AuthMcpClientAccess.literals.map((level) => ( ))}
) : choice !== null && (hostedApproval || access !== null) ? ( ) : null}
{open ? ( : undefined} > {step === 1 ? ( ) : null} {step === 2 ? ( ) : ( )} ) : null} ); } /** The level the Access step starts at: Auto, else the next one down that can be granted. */ const DEFAULT_LEVELS: readonly AuthMcpClientAccess[] = [ "auto", "auto-accept-edits", "approval-required", "read-only", ]; /** Why each level cannot be granted on this environment; absent when it can. */ function useGrantableLevels(environment: EnvironmentPresentation) { const environmentId = environment.environmentId; const session = useEnvironmentSessionState(environmentId).data; const canMint = useEnvironmentScope(environmentId, AuthAccessWriteScope); const policy = useManagedPolicy(environmentId); // The server refuses codes for any level when its policy keeps remote clients out. const remoteDenial = managedCapabilityDenial(policy, "remoteClients"); return new Map( AuthMcpClientAccess.literals.flatMap((level) => { const reason = remoteDenial ?? (!canMint ? `This device needs the Manage access permission on ${environment.label}.` : session === null || !mcpClientScopes(level).every((scope) => sessionGrantsScope(session, scope)) ? `This device cannot grant this on ${environment.label}.` : level === "read-only" ? undefined : managedRuntimeModeDenial(policy, level)); return reason === undefined ? [] : [[level, reason] as const]; }), ); } function useGate(environment: EnvironmentPresentation) { const environmentId = environment.environmentId; const { isSignedIn } = useAuth({ treatPendingAsSignedOut: false }); const controller = useCloudLinkController(environmentId); const canReadRelay = useEnvironmentScope(environmentId, AuthRelayReadScope); const thisMachine = environmentId === usePrimaryEnvironmentId() || isDesktopLocalConnectionTarget(environment.entry.target); const discovery = useAtomValue(relayEnvironmentDiscovery.stateValueAtom); const refreshDiscovery = useAtomCommand(relayEnvironmentDiscovery.refresh, { reportFailure: false, }); const runCheck = useAtomCommand(checkReachability, { reportFailure: false }); const readState = useAtomCommand(readLinkState, { reportFailure: false }); // The gate opens on a check it ran, or on a newer one the link state reports. A failed run // counts only server checks newer than `since` (see afterFailedCheck); null waits for a retry. const [check, setCheck] = useState< | "idle" | "running" | { readonly result: EnvironmentConnectReachability | null; readonly since: string | null } >("idle"); const [connecting, setConnecting] = useState(false); const linkState = controller.linkState.data; const reported = linkState?.reachability; const serverConfig = environment.serverConfig; const connected = environment.connection.phase === "connected"; const relayEntry = discovery.environments.get(environmentId); const gate = resolveMcpGate({ signedIn: isSignedIn, connected, approvalCodes: serverConfig === null ? undefined : serverConfig.environment.capabilities.mcpApprovalCodes === true, canReadRelay, linkState, linkStateError: controller.linkState.error, thisMachine, relay: !discovery.refreshing && (discovery.offline || Option.isSome(discovery.error)) ? { status: "failed" } : discovery.listed ? { status: "listed", endpoint: relayEntry?.environment.endpoint, availability: relayEntry?.availability, } : { status: "loading" }, reachability: typeof check === "string" ? "checking" : reported !== undefined && check.since !== null && reported.checkedAt > check.since ? reported : check.result, }); const runReachabilityCheck = async () => { setCheck("running"); // A saved machine's address comes from T3 Connect's list, which can be stale. if (!thisMachine) void refreshDiscovery(); const prior = readCachedCloudLinkState({ environmentId }); const result = await runCheck({ target: { environmentId } }); controller.linkState.refresh(); if (result._tag === "Success") { setCheck({ result: result.value, since: result.value.checkedAt }); return; } const latest = await readState({ target: { environmentId } }); setCheck(afterFailedCheck(prior, latest._tag === "Success" ? latest.value : null)); }; const turnOn = async () => { setConnecting(true); const ok = await controller.reconcileCloudState({ managedTunnel: true, publish: controller.publishAgentActivity, }); if (!ok) setConnecting(false); }; // A cached link state can name a tunnel on a disconnected machine; checking it would fail and // spend the one automatic check. const canCheck = connected && linkState !== null && isManagedTunnelActive(linkState, undefined); const checkOnce = useEffectEvent(() => { if (check === "idle") void runReachabilityCheck(); }); // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- asks the server to check its tunnel once one exists useEffect(() => { if (canCheck) queueMicrotask(checkOnce); }, [canCheck]); // T3 Connect lists machines only once something asks, and a saved machine's gate reads that list. const discoverIfUnasked = useEffectEvent(() => { if (!thisMachine && !discovery.listed && !discovery.refreshing) void refreshDiscovery(); }); // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- asks T3 Connect for its machine list once the wizard opens signed in useEffect(() => { if (isSignedIn) discoverIfUnasked(); }, [isSignedIn]); const waiting = gate.kind === "offline" || (connecting && gate.kind === "turn-on"); const refreshLinkState = useEffectEvent(() => controller.linkState.refresh()); // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- polls the server's link state while the tunnel starts; the server has no push for it useEffect(() => { if (!waiting) return; const interval = window.setInterval(() => refreshLinkState(), 5_000); return () => window.clearInterval(interval); }, [waiting]); // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- stops showing Connecting once a started tunnel has had time to come up useEffect(() => { if (!connecting) return; const timeout = window.setTimeout(() => setConnecting(false), 90_000); return () => window.clearTimeout(timeout); }, [connecting]); return { gate, connecting: connecting && gate.kind !== "open", check: runReachabilityCheck, // A disconnected machine cannot be checked; its list entry says when it is back. refreshList: () => void refreshDiscovery(), turnOn, operationError: controller.operationError, tunnelLimit: controller.tunnelLimit, }; } type Gate = ReturnType; function GateScreen({ environment, gate: { gate, connecting, check, refreshList, turnOn, operationError, tunnelLimit }, }: { readonly environment: EnvironmentPresentation; readonly gate: Gate; }) { const name = environment.label; const turnOnHere = useT3ConnectTurnOn(environment.environmentId); const { openAuthPrompt } = useT3ConnectAuthPrompt(); const retry = (onClick: () => void) => ( ); const checkAgain = retry(() => void check()); switch (gate.kind) { case "checking": return (

Checking T3 Connect…

); case "sign-in": return ( {/* Clerk's sign-in is its own modal, so this dialog closes first. */} ); case "disconnected": return gate.relay === null ? ( ) : ( {retry(refreshList)} ); case "update": return ; case "no-relay-read": return ( ); case "error": return ( {checkAgain} ); case "turn-on": case "offline": { if (gate.kind === "offline" && !connecting) { const reachability = gate.reachability; return ( {checkAgain} ); } return ( {turnOnHere.kind === "here" ? (
{operationError ? (

{operationError}

) : null} {tunnelLimit !== null ? ( <>

Your T3 Connect account is out of tunnels.

) : null}
) : turnOnHere.kind === "policy" ? (

{turnOnHere.reason}

) : (

Restart T3 Code on {name}.

{checkAgain}
)}
); } case "open": return null; } } function GateMessage({ title, lines = [], children, }: { readonly title: string; readonly lines?: readonly string[]; readonly children?: ReactNode; }) { return (

{title}

{lines.length > 0 ? (
{lines.map((line) => (

{line}

))}
) : null} {children}
); } function AppStep({ choice, onChoose, }: { readonly choice: Choice | null; readonly onChoose: (choice: Choice) => void; }) { return (

Which app?

{MCP_APPS.filter((app) => !app.hidden).map((app) => ( ))}
); } function ConnectStep({ environment, mcpUrl, choice, access, }: { readonly environment: EnvironmentPresentation; readonly mcpUrl: string; readonly choice: Choice; /** Null when the sign-in page asks for the level, so no code is needed. */ readonly access: AuthMcpClientAccess | null; }) { const [anotherWay, setAnotherWay] = useState(choice === "other"); const app = choice === "other" ? null : choice; const code = access === null ? (

When {app?.name ?? "the app"} opens the sign-in page, choose what it may do and approve with your T3 Connect account.

) : ( ); if (anotherWay || app === null) { const message = app?.handoff.kind === "copy-message" ? app.handoff.message(mcpUrl) : null; return (
{message ? : null}
    {(app?.steps ?? OTHER_MCP_APP_STEPS).map((step) => (
  1. {step}
  2. ))}
{app === null ? (

Apps that require their own client ID cannot connect.

) : app.docs ? ( openLink(app.docs ?? "")}>{app.name} help ) : null} {code} {app === null ? null : ( setAnotherWay(false)}> {app.handoff.kind === "commands" ? "Back to commands" : "Back to one click"} )}
); } return (
{code} setAnotherWay(true)}>Set up another way
); } function TextButton({ onClick, children, }: { readonly onClick: () => void; readonly children: ReactNode; }) { return ( ); } function openLink(url: string) { void readLocalApi() ?.shell.openExternal(url) .catch((error: unknown) => toastManager.add({ type: "error", title: "Could not open the link", description: error instanceof Error ? error.message : url, }), ); } function PrimaryAction({ app, mcpUrl }: { readonly app: McpApp; readonly mcpUrl: string }) { const action = mcpHandoffAction(app, mcpUrl); const note = app.note ?

{app.note}

: null; if (action === null) { return (
{app.handoff.kind === "commands" ? app.handoff .commands(mcpUrl) .map((command) => ) : null}

Run these where {app.name} runs.

{note}
); } const { copy, link, appLink } = action; // The desktop app reports a link nothing opens and falls back; a browser cannot tell, and // opens an app link in place (a new tab for it would stay blank), so its toast keeps the web. const openApp = () => { if (appLink === null) return openLink(link); if (!isElectron) return window.location.assign(appLink); void readLocalApi() ?.shell.openExternal(appLink) .catch(() => openLink(link)); }; const handOff = async () => { if (copy !== null) { // The app opens only once the copy lands, so it never gets an older clipboard. const copied = await writeTextToClipboard(copy.text, copy.title).catch(() => false); if (!copied) { toastManager.add({ type: "error", title: "Could not copy", description: "Use Set up another way.", }); return; } toastManager.add({ type: "success", title: copy.title, // A browser can block an open that follows the copy; this keeps a way through. ...(isElectron ? {} : { actionProps: { children: appLink === null ? `Open ${app.name}` : `Open ${app.name} on the web`, onClick: () => openLink(link), }, }), }); } openApp(); }; return (
{note}
); } /** "Get code" mints on click; the code is never copied or sent anywhere by itself. */ function ApprovalCode({ environmentId, name, access, }: { readonly environmentId: EnvironmentId; readonly name: string; readonly access: AuthMcpClientAccess; }) { const [code, setCode] = useState<{ readonly credential: string; readonly expiresAt: number; } | null>(null); const [pending, setPending] = useState(false); const [error, setError] = useState(null); const now = useNowSecond(); const live = code !== null && now < code.expiresAt; const mint = async () => { setPending(true); setError(null); const result = await mintMcpApprovalCodeOn(environmentId, access); setPending(false); if (Result.isSuccess(result)) { setCode({ credential: result.success.credential, expiresAt: result.success.expiresAt.epochMilliseconds, }); } else { setError(`Could not get a code from ${name}. Try again.`); } }; const left = code === null ? 0 : Math.max(0, Math.floor((code.expiresAt - now) / 1000)); return (
Code to approve {accessConfig[access].label} {live ? null : ( )}
{code !== null ? ( live ? ( <>

Expires {Math.floor(left / 60)}:{String(left % 60).padStart(2, "0")} · one use

) : (

The code expired.

) ) : null} {error ?

{error}

: null}
); } function CopyRow({ label, value }: { readonly label: string; readonly value: string }) { const { copyToClipboard, isCopied } = useCopyToClipboard({ timeout: 1500, target: label }); return (
{label} {value}
); } const levelRank = (level: AuthMcpClientAccess) => AuthMcpClientAccess.literals.indexOf(level); /** Machines this environment links to, at the level a call forwarded there gets. */ function LinkedLine({ environment, access, }: { readonly environment: EnvironmentPresentation; /** Null when the level is picked later; each link then shows its own. */ readonly access: AuthMcpClientAccess | null; }) { const offered = offersLinkedEnvironments(environment.serverConfig); const links = useEnvironmentQuery( offered ? peerLinkEnvironment.list({ environmentId: environment.environmentId, input: {} }) : null, ).data?.links; const identities = useEnvironmentIdentities(); if (!offered || links === undefined || links.length === 0) return null; const named = links.map((link) => { const name = identities.find((identity) => identity.environmentId === link.environmentId)?.label ?? link.label; // A forwarded call keeps the lower of the app's level and the link's own. const level = access === null || levelRank(link.access) < levelRank(access) ? link.access : access; return `${name} · ${link.status === "expired" ? "Expired" : accessConfig[level].label}`; }); return

Linked: {named.join(", ")}

; } /** Opens Sharing & access on this environment, where its clients and T3 Connect tunnels live. */ function ManageAccessButton({ environment, label = "Manage access", }: { readonly environment: EnvironmentPresentation; readonly label?: string; }) { const navigate = useNavigate(); return ( ); }