94 lines · 3.7 KB · TypeScript
RawDownload
import { useCallback } from "react";import { useAtomSet, useAtomValue } from "@effect/atom-react";import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { useScope } from "@executor-js/react/api/scope-context";import { sourceWriteKeys } from "@executor-js/react/api/reactivity-keys";import { connectionsAtom } from "@executor-js/react/api/atoms";import {  OAuthSignInButton,  oauthCallbackUrl,  oauthConnectionId,  useOAuthPopupFlow,  type OAuthCompletionPayload,} from "@executor-js/react/plugins/oauth-sign-in";import { slugifyNamespace } from "@executor-js/react/plugins/source-identity"; import { mcpSourceAtom, updateMcpSource } from "./atoms";import type { McpStoredSourceSchemaType } from "../sdk/stored-source"; // ---------------------------------------------------------------------------// McpSignInButton — top-bar action on the source detail page.//// Reads the source's stored endpoint + oauth2 pointer, re-runs the DCR /// authorization-code flow against a stable `mcp-oauth2-${namespace}`// connection id, and on success rewrites the source's auth pointer to// the freshly minted connection. Works whether or not the previous// Connection still exists — source-owned config is the source of truth.// --------------------------------------------------------------------------- export default function McpSignInButton(props: { sourceId: string }) {  const scopeId = useScope();  const sourceResult = useAtomValue(    mcpSourceAtom(scopeId, props.sourceId),  ) as AsyncResult.AsyncResult<McpStoredSourceSchemaType | null, unknown>;  const connectionsResult = useAtomValue(connectionsAtom(scopeId));  const doUpdate = useAtomSet(updateMcpSource, { mode: "promise" });  const oauth = useOAuthPopupFlow({    popupName: "mcp-oauth",  });   const source =    AsyncResult.isSuccess(sourceResult) && sourceResult.value ? sourceResult.value : null;  const remote = source && source.config.transport === "remote" ? source.config : null;  const oauth2 = remote && remote.auth.kind === "oauth2" ? remote.auth : null;  const connections = AsyncResult.isSuccess(connectionsResult)    ? (connectionsResult.value as readonly { readonly id: string }[])    : null;  const isConnected =    oauth2 !== null &&    connections !== null &&    connections.some((c) => c.id === oauth2.connectionId);   const handleSignIn = useCallback(async () => {    if (!remote || !oauth2 || !source) return;    const namespaceSlug = slugifyNamespace(source.namespace) || "mcp";    await oauth.start({      payload: {        endpoint: remote.endpoint,        ...(remote.headers ? { headers: remote.headers } : {}),        ...(remote.queryParams ? { queryParams: remote.queryParams } : {}),        redirectUrl: oauthCallbackUrl(),        connectionId: oauthConnectionId({          pluginId: "mcp",          namespace: namespaceSlug,        }),        strategy: { kind: "dynamic-dcr" },        pluginId: "mcp",        identityLabel: `${source.name.trim() || source.namespace || "MCP"} OAuth`,      },      onSuccess: async (result: OAuthCompletionPayload) => {        await doUpdate({          params: { scopeId, namespace: props.sourceId },          payload: {            auth: { kind: "oauth2", connectionId: result.connectionId },          },          reactivityKeys: sourceWriteKeys,        });      },    });  }, [remote, oauth2, source, scopeId, props.sourceId, doUpdate, oauth]);   if (!oauth2) return null;   return (    <OAuthSignInButton      busy={oauth.busy}      error={oauth.error}      isConnected={isConnected}      onSignIn={() => void handleSignIn()}      reconnectingLabel="Reconnecting…"      signingInLabel="Signing in…"    />  );}