120 lines · 4.7 KB · TypeScript
RawDownload
import { lazy, useCallback, type ComponentProps, type ComponentType } from "react";import { useAtomSet } from "@effect/atom-react";import * as Exit from "effect/Exit";import type {  IntegrationPlugin,  IntegrationQuickAddInput,  IntegrationQuickAddResult,} from "@executor-js/sdk/client";import { placementFromHeaderPattern } from "@executor-js/react/lib/auth-placements";import { slugifyNamespace } from "@executor-js/react/plugins/integration-identity";import { integrationWriteKeys } from "@executor-js/react/api/reactivity-keys";import { mcpPresets } from "../sdk/presets";import { addMcpServer, probeMcpEndpoint } from "./atoms";import {  mcpAuthMethodInputFromEditorValue,  mcpDetectedAuthSeeds,  mcpWireAuthInput,} from "./auth-method-config"; const importAdd = () => import("./AddMcpIntegration");const importEditSheet = () => import("./EditMcpIntegration");const importAccounts = () => import("./McpAccountsPanel"); const LazyAddMcpIntegration = lazy(importAdd);const LazyEditMcpSheet = lazy(importEditSheet);const LazyMcpAccountsPanel = lazy(importAccounts); type AddProps = ComponentProps<IntegrationPlugin["add"]>; /** One-click add for a registry MCP row: probe, declare the auth methods the *  add page would have seeded (same policy — `mcpDetectedAuthSeeds`), and *  register. A failed probe means the configuration screen's job — it renders *  the failure with retry UX — so quick add reports `{ok: false}` instead of *  guessing. */function useMcpQuickAdd(): (input: IntegrationQuickAddInput) => Promise<IntegrationQuickAddResult> {  const doProbe = useAtomSet(probeMcpEndpoint, { mode: "promiseExit" });  const doAddServer = useAtomSet(addMcpServer, { mode: "promiseExit" });  return useCallback(    async (input) => {      const probeExit = await doProbe({ payload: { endpoint: input.url } });      if (Exit.isFailure(probeExit)) return { ok: false, reason: "probe failed" };      const probe = probeExit.value;      const seeds = mcpDetectedAuthSeeds(probe, {        placement: input.authHeader ? placementFromHeaderPattern(input.authHeader) : null,        kind: input.authKind,      });      const methods = seeds.map((seed) =>        mcpWireAuthInput(mcpAuthMethodInputFromEditorValue(seed.value)),      );      const slug = input.slug ? slugifyNamespace(input.slug) : undefined;      const addExit = await doAddServer({        payload: {          transport: "remote" as const,          name: input.name,          endpoint: input.url,          ...(slug ? { slug } : {}),          ...(probe.instructions ? { description: probe.instructions } : {}),          authenticationTemplate: methods.length > 0 ? methods : [{ kind: "none" as const }],          // The probe reports when only legacy negotiation worked (a server          // that echoes the modern revision but breaks its contract); pin it          // so refreshes and tool calls use the same handshake.          ...(probe.versionNegotiation === "legacy"            ? { versionNegotiation: "legacy" as const }            : {}),        },        reactivityKeys: integrationWriteKeys,      });      if (Exit.isFailure(addExit)) return { ok: false, reason: "add failed" };      return { ok: true, slug: String(addExit.value.slug) };    },    [doProbe, doAddServer],  );} export interface McpIntegrationPluginOptions {  /**   * Enable the stdio transport in the add-integration UI (tab + presets).   *   * Off by default — stdio is a high-risk transport on any server deployment   * (see `dangerouslyAllowStdioMCP` on the server-side plugin). Only enable in   * trusted local contexts where the server has the matching flag set.   */  readonly allowStdio?: boolean;} export const createMcpIntegrationPlugin = (  options?: McpIntegrationPluginOptions,): IntegrationPlugin => {  const allowStdio = options?.allowStdio ?? false;   const AddWithFlag: ComponentType<AddProps> = (props) => (    <LazyAddMcpIntegration {...props} allowStdio={allowStdio} />  );   const base = allowStdio    ? mcpPresets    : mcpPresets.filter(        (p) => !("transport" in p && (p as { transport?: string }).transport === "stdio"),      );  // Built-ins are registry-listed; a deployment's custom presets would not be.  const presets = base.map((p) => ({ ...p, registryListed: true }));   return {    key: "mcp",    label: "MCP",    add: AddWithFlag,    editSheet: LazyEditMcpSheet,    accounts: LazyMcpAccountsPanel,    presets,    preload: () => {      void importAdd();      void importEditSheet();      void importAccounts();    },    useQuickAdd: useMcpQuickAdd,  };}; /** @deprecated Use `createMcpIntegrationPlugin({ allowStdio })` instead. */export const mcpIntegrationPlugin: IntegrationPlugin = createMcpIntegrationPlugin();