@12-apps/mcp 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADOPTING.md +31 -0
- package/dist/chunk-7QVYU63E.js +7 -0
- package/dist/chunk-7QVYU63E.js.map +1 -0
- package/dist/chunk-FYEVBTDU.js +162 -0
- package/dist/chunk-FYEVBTDU.js.map +1 -0
- package/dist/chunk-HAZOPC6U.js +229 -0
- package/dist/chunk-HAZOPC6U.js.map +1 -0
- package/dist/chunk-UIILEGAC.js +1247 -0
- package/dist/chunk-UIILEGAC.js.map +1 -0
- package/dist/chunk-WJJNKKNS.js +63 -0
- package/dist/chunk-WJJNKKNS.js.map +1 -0
- package/dist/coverage-gate/index.d.ts +129 -0
- package/dist/coverage-gate/index.js +163 -0
- package/dist/coverage-gate/index.js.map +1 -0
- package/dist/create-api-mcp-oauth-CwVXKK-A.d.ts +647 -0
- package/dist/generate/index.d.ts +91 -0
- package/dist/generate/index.js +101 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/generate-Dx3cK8th.d.ts +184 -0
- package/dist/guide-DV5MQbCg.d.ts +135 -0
- package/dist/hono/index.d.ts +28 -0
- package/dist/hono/index.js +25 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/index.d.ts +407 -0
- package/dist/index.js +383 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth/index.d.ts +446 -0
- package/dist/oauth/index.js +279 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/react/index.d.ts +239 -0
- package/dist/react/index.js +1183 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +31 -10
- package/prisma/migrations/20260812150000_add_mcp_oauth_tables/migration.sql +6 -6
- package/src/index.ts +5 -0
- package/src/server/redact.ts +96 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/ai-onboarding.tsx","../../src/react/ai-landing.tsx","../../src/react/ai-capabilities.tsx","../../src/react/ai-icons.tsx","../../src/react/feature-badge.tsx","../../src/react/ai-connection-utils.ts","../../src/react/ai-flow-steps.tsx","../../src/react/host-connect-guide.tsx","../../src/react/mcp-endpoint-url.tsx","../../src/react/ai-status-board.tsx","../../src/react/host-select-step.tsx","../../src/react/ai-steps.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n GuidedSection,\n OnboardingProvider,\n useOnboarding,\n type OnboardingStateSnapshot,\n type OnboardingStore,\n} from \"@12-apps/onboarding\";\n\nimport {\n AI_CAPABILITIES,\n aiHostGuides,\n AI_PERMISSION_MODEL,\n type AiCapability,\n type AiHostGuide,\n} from \"../guide\";\nimport { AiLanding } from \"./ai-landing\";\nimport type { DisconnectHandler } from \"./ai-status-board\";\nimport {\n buildFlowSteps,\n connectedSummary,\n connectedTitle,\n StatusBoard,\n type AiConnection,\n} from \"./ai-steps\";\n\nexport type { AiConnection } from \"./ai-steps\";\n\nconst DEFAULT_FEATURE_KEY = \"ai_integration\";\n\n/** Props for the reusable AI-connect onboarding flow. */\nexport interface AiIntegrationOnboardingProps {\n /** Persistence seam — the app wires this to its own backend (server actions). */\n store: OnboardingStore;\n /** The MCP endpoint URL to paste (derived by the app from its public origin). */\n endpointUrl: string;\n /** The owner's saved progress (null → first run / landing). */\n initialState: OnboardingStateSnapshot | null;\n /** The live MCP connections (one per connected assistant; empty when none). */\n connections: readonly AiConnection[];\n /** Onboarding feature key (persistence namespace). @default \"ai_integration\" */\n featureKey?: string;\n /** Show the dev-only \"reset onboarding\" button. @default false */\n devReset?: boolean;\n /**\n * The platform operating this MCP server, as its OAuth consent button names\n * it. REQUIRED, and it is the reason `hosts` can have a default at all: one\n * ChatGPT step tells the owner which \"Sign in with …\" button to click, and\n * that button carries whoever runs the server. It used to be a hard-coded\n * name — of a single STORE on one deployment, not even the product — so every\n * other adopter pointed its owners at a button that does not exist.\n */\n platformName: string;\n /** Assistants offered in the flow. @default aiHostGuides(platformName) */\n hosts?: readonly AiHostGuide[];\n /** Capability cards on the landing. @default the shared AI_CAPABILITIES */\n capabilities?: readonly AiCapability[];\n /** Permission reassurance copy on the landing. @default AI_PERMISSION_MODEL */\n permissionModel?: string;\n /**\n * Message the owner pastes into the assistant on the Conectar step.\n *\n * REQUIRED, with no default, because the useful version of it names TOOLS —\n * one to register the connection, one to read something real — and this\n * package neither defines nor serves any. It shipped a constant naming two\n * tools from one adopter's surface, so another host handed its owner a prompt\n * that called two things that did not exist, and the confirm step then waited\n * forever for a registration that could never happen. Build it with\n * `aiConnectPrompt({ … })`.\n */\n connectPrompt: string;\n /**\n * Re-check the live connection on the verify step's \"Testar conexão\" button —\n * apps pass a router refresh (e.g. Next's `router.refresh`). @default a full\n * `window.location.reload()`.\n */\n onRetest?: () => void;\n /**\n * Revoke a connected assistant's access, from the completed status board. The\n * host ID is the board's card, NOT the connection — `claude` and\n * `claude-desktop` share one provider, so the app resolves which stored\n * connection it owns (`providerForHostId`) and revokes that. Omitted → the\n * board stays read-only, which is the pre-existing behavior.\n */\n onDisconnect?: DisconnectHandler;\n}\n\n/** Resolved (defaults-applied) props threaded to the in-provider flow body. */\ninterface FlowProps {\n endpointUrl: string;\n connections: readonly AiConnection[];\n hosts: readonly AiHostGuide[];\n capabilities: readonly AiCapability[];\n permissionModel: string;\n connectPrompt: string;\n onRetest: () => void;\n onDisconnect: DisconnectHandler | undefined;\n devReset: boolean;\n}\n\n/**\n * The flow body — inside the OnboardingProvider so it can read the selected host\n * and pick the path. A host with a published `pluginUrl` gets the simplified\n * Escolher → Instalar → Confirmar flow (no URL to copy); otherwise the full\n * Escolher → Copiar URL → Configurar → Conectar → Confirmar flow.\n */\nfunction AiOnboardingFlow(props: FlowProps): React.JSX.Element {\n const {\n endpointUrl,\n connections,\n hosts,\n capabilities,\n permissionModel,\n connectPrompt,\n onRetest,\n onDisconnect,\n devReset,\n } = props;\n const { state } = useOnboarding();\n const selectedHost =\n hosts.find((h) => h.id === state.data.selectedHost) ?? hosts[0]!;\n\n const steps = buildFlowSteps({\n host: selectedHost,\n hosts,\n endpointUrl,\n connectPrompt,\n connections,\n onRetest,\n });\n const connectedHostId = (state.data.connectedHost ??\n state.data.selectedHost) as string | undefined;\n\n return (\n <GuidedSection\n steps={steps}\n title=\"Conecte assistentes de IA à sua loja\"\n startLabel=\"Ver como conectar\"\n renderLanding={(start) => (\n <AiLanding\n onStart={start}\n permissionModel={permissionModel}\n capabilities={capabilities}\n />\n )}\n configuredTitle={connectedTitle(connections, hosts, connectedHostId)}\n configuredSummary={connectedSummary(connections)}\n editLabel=\"Conectar IA\"\n completedContent={(nav) => (\n <StatusBoard\n nav={nav}\n connections={connections}\n hosts={hosts}\n onDisconnect={onDisconnect}\n />\n )}\n devReset={devReset}\n dataTestId=\"ai-onboarding\"\n />\n );\n}\n\n/**\n * URL-free, persisted onboarding for the AI/MCP integration — a five-step wizard\n * (pick an assistant, copy the store URL, configure the connector, connect, then\n * verify) whose position + chosen assistant are saved via `@12-apps/onboarding`, so\n * a refresh resumes exactly where the owner left off. The live MCP connection\n * signal drives the verify step and the completed status board.\n *\n * App-agnostic: the app supplies the persistence `store`, the `endpointUrl`, and\n * the live `connections` (one per connected assistant); content (hosts,\n * capabilities, copy) defaults to the shared guide but can be overridden per app.\n */\nexport function AiIntegrationOnboarding({\n store,\n endpointUrl,\n initialState,\n connections,\n featureKey = DEFAULT_FEATURE_KEY,\n devReset = false,\n platformName,\n hosts = aiHostGuides(platformName),\n capabilities = AI_CAPABILITIES,\n permissionModel = AI_PERMISSION_MODEL,\n connectPrompt,\n onRetest = () => {\n if (typeof window !== \"undefined\") window.location.reload();\n },\n onDisconnect,\n}: AiIntegrationOnboardingProps): React.JSX.Element {\n return (\n <OnboardingProvider\n featureKey={featureKey}\n store={store}\n initialState={initialState}\n >\n <AiOnboardingFlow\n endpointUrl={endpointUrl}\n connections={connections}\n hosts={hosts}\n capabilities={capabilities}\n permissionModel={permissionModel}\n connectPrompt={connectPrompt}\n onRetest={onRetest}\n onDisconnect={onDisconnect}\n devReset={devReset}\n />\n </OnboardingProvider>\n );\n}\n","\"use client\";\n\nimport BoltOutlinedIcon from \"@mui/icons-material/BoltOutlined\";\nimport LanguageOutlinedIcon from \"@mui/icons-material/LanguageOutlined\";\nimport LockOpenOutlinedIcon from \"@mui/icons-material/LockOpenOutlined\";\nimport VerifiedUserOutlinedIcon from \"@mui/icons-material/VerifiedUserOutlined\";\n\nimport { Button } from \"@12-apps/ui/form/Button\";\nimport { Box } from \"@12-apps/ui/mui/Box\";\nimport { Stack } from \"@12-apps/ui/mui/Stack\";\nimport { Text } from \"@12-apps/ui/typography/Text\";\n\nimport { AI_CAPABILITIES, AI_PERMISSION_MODEL, type AiCapability } from \"../guide\";\nimport { AiCapabilities } from \"./ai-capabilities\";\nimport { FeatureBadge, type FeatureBadgeItem } from \"./feature-badge\";\n\nconst FEATURES: FeatureBadgeItem[] = [\n { icon: <LockOpenOutlinedIcon />, label: \"Usa o seu próprio login\", caption: \"Sem chaves ou credenciais extras\" },\n { icon: <BoltOutlinedIcon />, label: \"Sem instalar nada\", caption: \"Conecta em poucos minutos\" },\n { icon: <VerifiedUserOutlinedIcon />, label: \"Só o que você pode\", caption: \"As suas permissões, nada além\" },\n { icon: <LanguageOutlinedIcon />, label: \"Navegador ou app\", caption: \"Claude, ChatGPT, Codex\" },\n];\n\n/** The single-column marketing hero: eyebrow + headline + description + CTA. */\nfunction Hero({ onStart }: { onStart: () => void }): React.JSX.Element {\n return (\n <Stack spacing={2.5} sx={{ maxWidth: 680 }}>\n <Box\n sx={{\n color: \"primary.main\",\n fontSize: 12,\n fontWeight: 700,\n letterSpacing: 1.2,\n textTransform: \"uppercase\",\n }}\n >\n Integração com IA\n </Box>\n <Text variant=\"heading\" size=\"xl\" weight=\"bold\" as=\"h1\">\n Conecte{\" \"}\n <Box component=\"span\" sx={{ color: \"primary.main\" }}>\n assistentes de IA\n </Box>{\" \"}\n à sua loja\n </Text>\n <Text variant=\"body\" color=\"secondary\" as=\"p\">\n Deixe o Claude, o ChatGPT e outros assistentes responderem sobre o seu cardápio, estoque e\n pedidos — e executarem ações por você, direto na conversa. Com segurança e sem instalar\n nada.\n </Text>\n <Box sx={{ pt: 1 }}>\n <Button onClick={onStart} data-testid=\"ai-landing-start\">\n Ver como conectar\n </Button>\n </Box>\n </Stack>\n );\n}\n\n/**\n * Homepage-style marketing landing for the AI integration (shown before the\n * owner starts): a hero, the permission reassurance, a trust/feature strip, and\n * the capability highlights. `onStart` begins the guided flow. `permissionModel`\n * and `capabilities` default to the shared copy; apps can override.\n */\nexport function AiLanding({\n onStart,\n permissionModel = AI_PERMISSION_MODEL,\n capabilities = AI_CAPABILITIES,\n}: {\n onStart: () => void;\n permissionModel?: string;\n capabilities?: readonly AiCapability[];\n}): React.JSX.Element {\n return (\n <Stack spacing={6} data-testid=\"ai-landing\">\n <Hero onStart={onStart} />\n\n <Stack\n direction=\"row\"\n spacing={1.5}\n alignItems=\"flex-start\"\n data-testid=\"ai-permission-callout\"\n sx={{\n p: 2,\n borderRadius: 2,\n bgcolor: \"action.hover\",\n border: 1,\n borderColor: \"divider\",\n }}\n >\n <VerifiedUserOutlinedIcon sx={{ color: \"success.main\", mt: 0.25 }} />\n <Text variant=\"body\" as=\"p\">\n {permissionModel}\n </Text>\n </Stack>\n\n <Box\n sx={{\n p: { xs: 2.5, md: 3 },\n borderRadius: 3,\n bgcolor: \"action.hover\",\n border: 1,\n borderColor: \"divider\",\n }}\n >\n <Box\n sx={{\n display: \"grid\",\n gap: 3,\n gridTemplateColumns: { xs: \"1fr\", sm: \"1fr 1fr\", md: \"repeat(4, 1fr)\" },\n }}\n >\n {FEATURES.map((f) => (\n <FeatureBadge key={f.label} icon={f.icon} label={f.label} caption={f.caption} />\n ))}\n </Box>\n </Box>\n\n <Box>\n <AiCapabilities capabilities={capabilities} />\n </Box>\n </Stack>\n );\n}\n","import { Box } from \"@12-apps/ui/mui/Box\";\nimport { Text } from \"@12-apps/ui/typography/Text\";\n\nimport { AI_CAPABILITIES, type AiCapability } from \"../guide\";\nimport { CapabilityIcon } from \"./ai-icons\";\n\n/** One marketing card: an icon, a headline and an example prompt (as a bubble). */\nfunction CapabilityCard({ capability }: { capability: AiCapability }): React.JSX.Element {\n return (\n <Box\n data-testid={`ai-capability-${capability.id}`}\n sx={{\n p: 2.5,\n borderRadius: 3,\n border: 1,\n borderColor: \"divider\",\n bgcolor: \"background.paper\",\n height: \"100%\",\n // Static transition string: safe to cross the server→client boundary.\n transition: \"border-color 200ms, box-shadow 200ms, transform 200ms\",\n \"&:hover\": { borderColor: \"primary.main\", boxShadow: 3, transform: \"translateY(-3px)\" },\n }}\n >\n <Box\n sx={{\n width: 44,\n height: 44,\n borderRadius: 2,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: \"primary.main\",\n color: \"primary.contrastText\",\n mb: 1.5,\n }}\n >\n <CapabilityIcon id={capability.id} fontSize={26} />\n </Box>\n <Text variant=\"body\" weight=\"bold\" as=\"h3\">\n {capability.title}\n </Text>\n <Box\n data-testid={`ai-capability-prompt-${capability.id}`}\n sx={{\n mt: 1,\n px: 1.5,\n py: 1,\n borderRadius: 2,\n borderBottomLeftRadius: 4,\n bgcolor: \"action.hover\",\n border: 1,\n borderColor: \"divider\",\n }}\n >\n <Text variant=\"body\" size=\"sm\" as=\"p\" color=\"secondary\">\n {capability.detail}\n </Text>\n </Box>\n </Box>\n );\n}\n\n/**\n * Marketing block for the AI integration: a headline + a responsive grid of\n * capability cards. Shown on the onboarding landing (before the owner starts) to\n * sell the feature — \"here's what the assistant does for you\". `capabilities`\n * defaults to the shared set; apps can pass their own.\n */\nexport function AiCapabilities({\n capabilities = AI_CAPABILITIES,\n}: {\n capabilities?: readonly AiCapability[];\n}): React.JSX.Element {\n return (\n <Box data-testid=\"ai-capability-examples\" sx={{ width: \"100%\" }}>\n <Box sx={{ mb: 3 }}>\n <Text variant=\"heading\" size=\"sm\" as=\"h2\">\n O que o assistente faz por você\n </Text>\n <Box sx={{ mt: 0.5 }}>\n <Text variant=\"body\" size=\"sm\" as=\"p\" color=\"secondary\">\n Sem planilhas, sem cliques — é só perguntar na conversa.\n </Text>\n </Box>\n </Box>\n <Box\n sx={{\n display: \"grid\",\n gap: 3,\n gridTemplateColumns: { xs: \"1fr\", sm: \"1fr 1fr\", md: \"repeat(4, 1fr)\" },\n }}\n >\n {capabilities.map((capability) => (\n <CapabilityCard key={capability.id} capability={capability} />\n ))}\n </Box>\n </Box>\n );\n}\n","import AutoAwesomeIcon from \"@mui/icons-material/AutoAwesome\";\nimport CategoryIcon from \"@mui/icons-material/Category\";\nimport HubIcon from \"@mui/icons-material/Hub\";\nimport Inventory2Icon from \"@mui/icons-material/Inventory2\";\nimport QueryStatsIcon from \"@mui/icons-material/QueryStats\";\nimport ReceiptLongIcon from \"@mui/icons-material/ReceiptLong\";\n\nimport { Box } from \"@12-apps/ui/mui/Box\";\n\nimport type { AiHostBrand } from \"../guide\";\n\n/**\n * Brand accents for the host avatars. These are deliberately hardcoded (not\n * theme tokens): they are BRAND marks whose recognizability depends on the\n * vendor's own accent colour, so the \"always semantic colours\" rule doesn't\n * apply here. The glyphs are simple, original stand-ins — not the vendors'\n * proprietary logo artwork.\n */\nconst BRAND: Record<AiHostBrand, { bg: string; Icon: typeof AutoAwesomeIcon; label: string }> = {\n claude: { bg: \"#D97757\", Icon: AutoAwesomeIcon, label: \"Claude (Anthropic)\" },\n openai: { bg: \"#10A37F\", Icon: HubIcon, label: \"OpenAI\" },\n};\n\n/** A round brand chip: the vendor accent + a simple glyph. */\nexport function HostBrandAvatar({\n brand,\n size = 40,\n}: {\n brand: AiHostBrand;\n size?: number;\n}): React.JSX.Element {\n const { bg, Icon, label } = BRAND[brand];\n return (\n <Box\n aria-label={label}\n sx={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n bgcolor: bg,\n color: \"#fff\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n flexShrink: 0,\n }}\n >\n <Icon sx={{ fontSize: size * 0.55 }} />\n </Box>\n );\n}\n\nconst CAPABILITY_ICON: Record<string, typeof AutoAwesomeIcon> = {\n orders: ReceiptLongIcon,\n inventory: Inventory2Icon,\n catalog: CategoryIcon,\n sales: QueryStatsIcon,\n};\n\n/** The icon for a capability card, by capability id (falls back to a sparkle). */\nexport function CapabilityIcon({\n id,\n fontSize = 24,\n}: {\n id: string;\n fontSize?: number;\n}): React.JSX.Element {\n const Icon = CAPABILITY_ICON[id] ?? AutoAwesomeIcon;\n return <Icon sx={{ fontSize }} />;\n}\n","import { Box } from \"@12-apps/ui/mui/Box\";\nimport { Stack } from \"@12-apps/ui/mui/Stack\";\nimport { Text } from \"@12-apps/ui/typography/Text\";\n\n/** One entry of a landing's trust/feature strip. */\nexport interface FeatureBadgeItem {\n icon: React.ReactNode;\n label: string;\n caption: string;\n}\n\n/**\n * One item of an onboarding landing's trust/feature strip: a paper icon tile\n * (primary-coloured glyph) with a two-line label + caption.\n */\nexport function FeatureBadge({ icon, label, caption }: FeatureBadgeItem): React.JSX.Element {\n return (\n <Stack direction=\"row\" spacing={1.5} alignItems=\"flex-start\">\n <Box\n sx={{\n width: 36,\n height: 36,\n borderRadius: 2,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n bgcolor: \"background.paper\",\n color: \"primary.main\",\n border: 1,\n borderColor: \"divider\",\n flexShrink: 0,\n }}\n >\n {icon}\n </Box>\n <Box>\n <Text variant=\"body\" size=\"sm\" weight=\"bold\" as=\"p\">\n {label}\n </Text>\n <Text variant=\"caption\" color=\"secondary\" as=\"p\">\n {caption}\n </Text>\n </Box>\n </Stack>\n );\n}\n","import { providerForHostId, type AiHostGuide, type AiProvider } from \"../guide\";\n\n/**\n * Pure helpers + the connection type shared by the AI-onboarding flow steps\n * (`ai-flow-steps.tsx`) and the orchestration/status board (`ai-steps.tsx`),\n * kept here so neither JSX module has to import the other (no import cycle).\n */\n\n/** The live connection surfaced from the server (empty list when none). */\nexport interface AiConnection {\n clientName: string | null;\n /**\n * The provider this connection is attributed to (derived server-side from the\n * OAuth client's redirect URIs / confirmed by `announceAiConnection`). `null`\n * for a legacy connection recorded before attribution existed — matched\n * best-effort to whichever host the owner is connecting.\n */\n host: AiProvider | null;\n lastActiveAt: Date;\n}\n\n/** Friendly provider names for the connected-state title (\"Claude Conectado\"). */\nconst PROVIDER_LABEL: Record<AiProvider, string> = {\n claude: \"Claude\",\n chatgpt: \"ChatGPT\",\n codex: \"Codex\",\n};\n\n/** Resolve a persisted host id to its guide (falls back to the first host). */\nexport function resolveHost(hosts: readonly AiHostGuide[], hostId: unknown): AiHostGuide {\n const found = hosts.find((h) => h.id === hostId) ?? hosts[0];\n if (!found) throw new Error(\"resolveHost: hosts array must not be empty\");\n return found;\n}\n\n/** Friendly label for a persisted host id (falls back to a generic word). */\nexport function hostLabel(hosts: readonly AiHostGuide[], hostId: unknown): string {\n return hosts.find((h) => h.id === hostId)?.label ?? \"seu assistente\";\n}\n\n/**\n * The live connection for a given host, or null. A connection is matched by its\n * derived provider; a legacy `null`-host connection matches best-effort (so the\n * pre-attribution single-connection behavior still works).\n */\nexport function connectionForHost(\n connections: readonly AiConnection[],\n hostId: unknown,\n): AiConnection | null {\n const provider = typeof hostId === \"string\" ? providerForHostId(hostId) : null;\n if (!provider) return connections.find((c) => c.host === null) ?? connections[0] ?? null;\n return (\n connections.find((c) => c.host === provider) ?? connections.find((c) => c.host === null) ?? null\n );\n}\n\n/** Short pt-BR \"ativo há X\" from a timestamp (agora / min / h / dias). */\nexport function activeAgo(date: Date): string {\n const minutes = Math.max(0, Math.floor((Date.now() - date.getTime()) / 60_000));\n if (minutes < 1) return \"ativo agora\";\n if (minutes < 60) return `ativo há ${minutes} min`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `ativo há ${hours} h`;\n return `ativo há ${Math.floor(hours / 24)} dias`;\n}\n\n/** The most-recently-active connection across all providers (null when none). */\nfunction mostRecent(connections: readonly AiConnection[]): AiConnection | null {\n return connections.reduce<AiConnection | null>(\n (best, c) => (!best || c.lastActiveAt.getTime() > best.lastActiveAt.getTime() ? c : best),\n null,\n );\n}\n\n/** The configured-state TITLE, naming the connected assistant(s) — FUT req (F). */\nexport function connectedTitle(\n connections: readonly AiConnection[],\n hosts: readonly AiHostGuide[],\n connectedHostId: string | undefined,\n): string {\n const providers = [...new Set(connections.map((c) => c.host).filter((h): h is AiProvider => h !== null))];\n if (providers.length === 1) return `${PROVIDER_LABEL[providers[0]!]} Conectado`;\n if (providers.length > 1) {\n return `${providers.map((p) => PROVIDER_LABEL[p]).join(\", \")} conectados`;\n }\n // Legacy connection with no attributed provider — name the completed host.\n if (connections.length > 0) return `${hostLabel(hosts, connectedHostId)} Conectado`;\n return \"IA conectada\";\n}\n\n/** The completed summary line (second line) for the configured-state header. */\nexport function connectedSummary(connections: readonly AiConnection[]): string {\n const recent = mostRecent(connections);\n if (!recent) return \"Integração configurada\";\n return activeAgo(recent.lastActiveAt);\n}\n","\"use client\";\n\nimport CheckCircleIcon from \"@mui/icons-material/CheckCircle\";\nimport OpenInNewIcon from \"@mui/icons-material/OpenInNew\";\nimport { useEffect, useState } from \"react\";\n\nimport { type GuidedNav } from \"@12-apps/onboarding\";\nimport { Progress } from \"@12-apps/ui/data-display/Progress\";\nimport { Button } from \"@12-apps/ui/form/Button\";\nimport { Box } from \"@12-apps/ui/mui/Box\";\nimport { Stack } from \"@12-apps/ui/mui/Stack\";\nimport { Text } from \"@12-apps/ui/typography/Text\";\n\nimport type { AiHostConfigureStage, AiHostGuide } from \"../guide\";\nimport { activeAgo, connectionForHost, hostLabel, type AiConnection } from \"./ai-connection-utils\";\nimport {\n EndpointCopyBlock,\n HostConnectHeader,\n HostDocsLink,\n HostOpenButton,\n HostStepList,\n PromptCopyBlock,\n} from \"./host-connect-guide\";\n\n/** The host's connect steps split across the Configurar / Conectar stages. */\nconst CONFIGURE_STEP_COUNT = 3;\n\n/** How often the Confirmar step re-checks the live connection while waiting. */\nconst POLL_INTERVAL_MS = 3000;\n\n/** A step's continue/back controls (Voltar left, primary next right). */\nfunction StepNav({\n nav,\n onNext,\n nextLabel = \"Próximo\",\n nextDisabled = false,\n nextTestId,\n}: {\n nav: GuidedNav;\n onNext: () => void;\n nextLabel?: string;\n nextDisabled?: boolean;\n nextTestId?: string;\n}): React.JSX.Element {\n return (\n <Stack direction=\"row\" spacing={1} alignItems=\"center\" justifyContent=\"space-between\">\n <Button variant=\"ghost\" onClick={() => nav.back()} data-testid=\"ai-step-back\">\n Voltar\n </Button>\n <Button onClick={onNext} disabled={nextDisabled} data-testid={nextTestId}>\n {nextLabel}\n </Button>\n </Stack>\n );\n}\n\n/** Step 2 — copy the store URL; copying is the action that advances the wizard. */\nexport function CopyUrlStep({ nav, endpointUrl }: { nav: GuidedNav; endpointUrl: string }): React.JSX.Element {\n return (\n <Stack spacing={3} data-testid=\"ai-copy-step\">\n <Box>\n <Text variant=\"heading\" size=\"sm\" as=\"h2\">\n Copie a URL da sua loja\n </Text>\n <Text variant=\"caption\" as=\"p\" color=\"secondary\">\n É o único dado que você cola no assistente — ao copiar, seguimos para o próximo passo.\n </Text>\n </Box>\n <EndpointCopyBlock endpointUrl={endpointUrl} copied={false} onCopy={() => nav.next()} />\n <Box>\n <Button variant=\"ghost\" onClick={() => nav.back()} data-testid=\"ai-step-back\">\n Voltar\n </Button>\n </Box>\n </Stack>\n );\n}\n\n/** Step 3 — open the host's connectors and add + configure the custom connector. */\nexport function ConfigureStep({ nav, host }: { nav: GuidedNav; host: AiHostGuide }): React.JSX.Element {\n // Hosts with no direct link (e.g. Claude Desktop) have nothing to open, so\n // \"Próximo\" is available immediately; otherwise it unlocks on the open click.\n const [opened, setOpened] = useState(!host.link);\n return (\n <Stack spacing={2.5} data-testid=\"ai-configure-step\">\n <HostConnectHeader host={host} />\n <HostOpenButton host={host} onOpen={() => setOpened(true)} />\n <HostStepList steps={host.steps.slice(0, CONFIGURE_STEP_COUNT)} start={1} />\n <HostDocsLink host={host} />\n <StepNav nav={nav} onNext={() => nav.next()} nextDisabled={!opened} nextTestId=\"ai-configure-next\" />\n </Stack>\n );\n}\n\n/**\n * One stage of a host's per-stage configuration (e.g. ChatGPT's \"enable\n * developer mode\" then \"configurar\"). Each stage has its OWN deep link — opening\n * it unlocks \"Próximo\" — and its own instructions.\n */\nexport function ConfigureStageStep({\n nav,\n host,\n stage,\n}: {\n nav: GuidedNav;\n host: AiHostGuide;\n stage: AiHostConfigureStage;\n}): React.JSX.Element {\n const [opened, setOpened] = useState(!stage.link);\n return (\n <Stack spacing={2.5} data-testid={`ai-stage-${stage.id}`}>\n <HostConnectHeader host={host} />\n {stage.link && (\n <Box>\n <Button\n onClick={() => {\n window.open(stage.link!.url, \"_blank\", \"noopener,noreferrer\");\n setOpened(true);\n }}\n data-testid={`ai-stage-link-${stage.id}`}\n >\n {stage.link.label}\n <OpenInNewIcon sx={{ fontSize: 16, ml: 0.5 }} />\n </Button>\n </Box>\n )}\n <HostStepList steps={stage.steps} start={1} />\n <HostDocsLink host={host} />\n <StepNav\n nav={nav}\n onNext={() => nav.next()}\n nextDisabled={!opened}\n nextTestId={`ai-stage-next-${stage.id}`}\n />\n </Stack>\n );\n}\n\n/** Step 4 — connect: log in, authorize and activate the connector. */\nexport function ConnectStep({\n nav,\n host,\n connectPrompt,\n}: {\n nav: GuidedNav;\n host: AiHostGuide;\n connectPrompt: string;\n}): React.JSX.Element {\n return (\n <Stack spacing={2.5} data-testid=\"ai-connect-step\">\n <HostConnectHeader host={host} />\n <HostStepList steps={host.steps.slice(CONFIGURE_STEP_COUNT)} start={CONFIGURE_STEP_COUNT + 1} />\n <PromptCopyBlock\n title=\"Cole esta mensagem no assistente\"\n caption=\"Assim ele se conecta, se identifica (Claude, ChatGPT…) e registramos a conexão.\"\n message={connectPrompt}\n />\n <HostDocsLink host={host} />\n <StepNav nav={nav} onNext={() => nav.next()} nextLabel=\"Continuar\" nextTestId=\"ai-connect-done\" />\n </Stack>\n );\n}\n\n/**\n * Simplified-flow step (host has a published `pluginUrl`): open the one-click\n * plugin (install + authorize), then paste the prompt asking the assistant to\n * connect. No URL to copy, no manual connector. The next step (Confirmar) waits.\n */\nexport function InstallStep({\n nav,\n host,\n connectPrompt,\n}: {\n nav: GuidedNav;\n host: AiHostGuide;\n connectPrompt: string;\n}): React.JSX.Element {\n const [opened, setOpened] = useState(false);\n return (\n <Stack spacing={2.5} data-testid=\"ai-install-step\">\n <HostConnectHeader host={host} />\n <Text variant=\"body\" as=\"p\" color=\"secondary\">\n Abra o plugin da sua loja, clique em Instalar e autorize o acesso — sem copiar URL nem gerar\n credenciais.\n </Text>\n {host.pluginUrl && (\n <Box>\n <Button\n onClick={() => {\n window.open(host.pluginUrl!, \"_blank\", \"noopener,noreferrer\");\n setOpened(true);\n }}\n data-testid=\"ai-install-open\"\n >\n Instalar o plugin da loja\n <OpenInNewIcon sx={{ fontSize: 16, ml: 0.5 }} />\n </Button>\n </Box>\n )}\n <PromptCopyBlock\n title=\"Peça ao assistente para conectar\"\n caption=\"Cole no assistente para ele se conectar, se identificar e confirmar o acesso.\"\n message={connectPrompt}\n />\n <HostDocsLink host={host} />\n <StepNav\n nav={nav}\n onNext={() => nav.next()}\n nextLabel=\"Continuar\"\n nextDisabled={!opened}\n nextTestId=\"ai-install-done\"\n />\n </Stack>\n );\n}\n\n/**\n * Step 5: auto-detect the live connection. While waiting it polls the server\n * (the sign-in / `announceAiConnection` tool call registers the connection\n * server-side), showing a spinner — the owner never clicks \"verify\". Completes\n * when the selected host's connection appears.\n */\nexport function ConfirmStep({\n nav,\n connections,\n hosts,\n onRetest,\n}: {\n nav: GuidedNav;\n connections: readonly AiConnection[];\n hosts: readonly AiHostGuide[];\n onRetest: () => void;\n}): React.JSX.Element {\n const selectedHostId = nav.data.selectedHost as string | undefined;\n const connection = connectionForHost(connections, selectedHostId);\n const label = connection?.clientName ?? hostLabel(hosts, selectedHostId);\n\n // Auto-detect: re-check on an interval until the connection shows up. The\n // interval is cleared as soon as `connection` is non-null (and on unmount).\n useEffect(() => {\n if (connection) return undefined;\n const id = setInterval(() => onRetest(), POLL_INTERVAL_MS);\n return () => clearInterval(id);\n }, [connection, onRetest]);\n\n if (connection) {\n return (\n <Stack spacing={2} data-testid=\"ai-confirm-connected\">\n <Stack direction=\"row\" spacing={1.5} alignItems=\"center\">\n <CheckCircleIcon sx={{ color: \"success.main\" }} />\n <Box>\n <Text variant=\"body\" weight=\"bold\" as=\"p\">\n {label} está conectado à sua loja.\n </Text>\n <Text variant=\"caption\" as=\"p\" color=\"secondary\">\n {activeAgo(connection.lastActiveAt)}\n </Text>\n </Box>\n </Stack>\n <Box>\n <Button onClick={() => nav.complete({ connectedHost: selectedHostId })}>Concluir</Button>\n </Box>\n </Stack>\n );\n }\n\n return (\n <Stack spacing={2.5} data-testid=\"ai-confirm-waiting\">\n <Stack direction=\"row\" spacing={1.5} alignItems=\"center\">\n <Progress variant=\"circular\" circularSize={22} thickness={4} dataTestId=\"ai-confirm-spinner\" />\n <Box>\n <Text variant=\"body\" weight=\"bold\" as=\"p\">\n Esperando conexão\n </Text>\n <Text variant=\"caption\" as=\"p\" color=\"secondary\">\n Assim que você autorizar o acesso no {label}, ela aparece aqui automaticamente.\n </Text>\n </Box>\n </Stack>\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"ghost\" size=\"sm\" onClick={onRetest} data-testid=\"ai-confirm-retest\">\n Testar agora\n </Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => nav.back()}>\n Voltar\n </Button>\n </Stack>\n </Stack>\n );\n}\n","\"use client\";\n\nimport CheckIcon from \"@mui/icons-material/Check\";\nimport ContentCopyOutlinedIcon from \"@mui/icons-material/ContentCopyOutlined\";\nimport OpenInNewIcon from \"@mui/icons-material/OpenInNew\";\nimport { useState } from \"react\";\n\nimport { Button } from \"@12-apps/ui/form/Button\";\nimport { Box } from \"@12-apps/ui/mui/Box\";\nimport { Stack } from \"@12-apps/ui/mui/Stack\";\nimport { Text } from \"@12-apps/ui/typography/Text\";\n\nimport type { AiHostGuide } from \"../guide\";\nimport { HostBrandAvatar } from \"./ai-icons\";\nimport { McpEndpointUrl } from \"./mcp-endpoint-url\";\n\n/**\n * The store's MCP endpoint URL with a blue \"Copiar\" button (the display-only\n * field defers the copy to this button). Controlled: `copied` flips the label to\n * \"Copiado\" and `onCopy` fires after the clipboard write — the wizard uses it to\n * unlock the \"Próximo\" button on the Copiar-URL step.\n */\nexport function EndpointCopyBlock({\n endpointUrl,\n copied,\n onCopy,\n}: {\n endpointUrl: string;\n copied: boolean;\n onCopy: () => void;\n}): React.JSX.Element {\n const handleClick = (): void => {\n if (typeof navigator !== \"undefined\" && navigator.clipboard) {\n void navigator.clipboard.writeText(endpointUrl).catch(() => undefined);\n }\n onCopy();\n };\n\n return (\n <Box\n sx={{\n p: 2,\n borderRadius: 2,\n border: 1,\n borderColor: \"primary.main\",\n bgcolor: \"action.hover\",\n }}\n >\n <Text variant=\"caption\" as=\"p\" weight=\"bold\">\n URL do servidor da sua loja\n </Text>\n <Text variant=\"caption\" as=\"p\" color=\"secondary\">\n Copie esta URL e cole no seu assistente para conectá-lo à loja.\n </Text>\n <Box sx={{ mt: 1 }}>\n <McpEndpointUrl url={endpointUrl} copyable={false} />\n </Box>\n <Box sx={{ mt: 1.5 }}>\n <Button onClick={handleClick} data-testid=\"ai-copy-endpoint\">\n {copied ? (\n <CheckIcon sx={{ fontSize: 18, mr: 0.5 }} />\n ) : (\n <ContentCopyOutlinedIcon sx={{ fontSize: 18, mr: 0.5 }} />\n )}\n {copied ? \"Copiado\" : \"Copiar URL\"}\n </Button>\n </Box>\n </Box>\n );\n}\n\n/**\n * A copyable multi-line message block (preserves line breaks). Used on the\n * Conectar step for the prompt the owner pastes into the assistant so it\n * identifies itself and calls a tool (which registers the connection).\n */\nexport function PromptCopyBlock({\n title,\n caption,\n message,\n}: {\n title: string;\n caption: string;\n message: string;\n}): React.JSX.Element {\n const [copied, setCopied] = useState(false);\n\n const handleClick = (): void => {\n if (typeof navigator !== \"undefined\" && navigator.clipboard) {\n void navigator.clipboard.writeText(message).catch(() => undefined);\n }\n setCopied(true);\n };\n\n return (\n <Box\n sx={{\n p: 2,\n borderRadius: 2,\n border: 1,\n borderColor: \"primary.main\",\n bgcolor: \"action.hover\",\n }}\n >\n <Text variant=\"caption\" as=\"p\" weight=\"bold\">\n {title}\n </Text>\n <Text variant=\"caption\" as=\"p\" color=\"secondary\">\n {caption}\n </Text>\n <Box\n sx={{\n mt: 1,\n p: 1.5,\n borderRadius: 1,\n border: 1,\n borderColor: \"divider\",\n bgcolor: \"background.paper\",\n whiteSpace: \"pre-line\",\n }}\n >\n <Text variant=\"body\" size=\"sm\" as=\"p\">\n {message}\n </Text>\n </Box>\n <Box sx={{ mt: 1.5 }}>\n <Button onClick={handleClick} data-testid=\"ai-copy-prompt\">\n {copied ? (\n <CheckIcon sx={{ fontSize: 18, mr: 0.5 }} />\n ) : (\n <ContentCopyOutlinedIcon sx={{ fontSize: 18, mr: 0.5 }} />\n )}\n {copied ? \"Copiado\" : \"Copiar mensagem\"}\n </Button>\n </Box>\n </Box>\n );\n}\n\n/** The brand mark + \"Conectar no <host>\" header shared by the connect sub-steps. */\nexport function HostConnectHeader({ host }: { host: AiHostGuide }): React.JSX.Element {\n return (\n <Stack direction=\"row\" spacing={1.5} alignItems=\"center\">\n <HostBrandAvatar brand={host.brand} size={36} />\n <Text variant=\"heading\" size=\"sm\" as=\"h3\">\n Conectar no {host.label}\n </Text>\n </Stack>\n );\n}\n\n/**\n * The primary direct-link button to a host's connector settings (nothing when it\n * has none). `onOpen` fires after the link opens so the Configurar step can\n * unlock its \"Próximo\" only once the owner has actually gone to the connectors.\n */\nexport function HostOpenButton({\n host,\n onOpen,\n}: {\n host: AiHostGuide;\n onOpen?: () => void;\n}): React.JSX.Element | null {\n if (!host.link) return null;\n return (\n <Box>\n <Button\n onClick={() => {\n window.open(host.link!.url, \"_blank\", \"noopener,noreferrer\");\n onOpen?.();\n }}\n data-testid={`ai-host-link-${host.id}`}\n >\n {host.link.label}\n <OpenInNewIcon sx={{ fontSize: 16, ml: 0.5 }} />\n </Button>\n </Box>\n );\n}\n\n/** A \"Para mais informações\" reference to the host's official connector docs. */\nexport function HostDocsLink({ host }: { host: AiHostGuide }): React.JSX.Element | null {\n if (!host.docs) return null;\n return (\n <Text variant=\"caption\" as=\"p\" color=\"secondary\">\n Para mais informações, consulte a{\" \"}\n <Box\n component=\"a\"\n href={host.docs.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-testid={`ai-host-docs-${host.id}`}\n sx={{ color: \"primary.main\", textDecoration: \"underline\" }}\n >\n {host.docs.label}\n </Box>\n .\n </Text>\n );\n}\n\n/**\n * A numbered slice of a host's connect steps. `start` keeps the numbering\n * continuous when the steps are split across wizard stages (e.g. the Conectar\n * stage continues at 4 after Configurar showed 1–3).\n */\nexport function HostStepList({\n steps,\n start = 1,\n}: {\n steps: readonly string[];\n start?: number;\n}): React.JSX.Element {\n return (\n <Box component=\"ol\" start={start} sx={{ m: 0, pl: 3 }}>\n {steps.map((step, index) => (\n <Box component=\"li\" key={index} sx={{ mb: 1 }}>\n <Text variant=\"body\" as=\"span\">\n {step}\n </Text>\n </Box>\n ))}\n </Box>\n );\n}\n","\"use client\";\n\nimport { Code } from \"@12-apps/ui/typography/Code\";\n\n/**\n * The store's MCP endpoint URL. Client component because the copy interaction\n * needs the browser clipboard — the `@12-apps/ui` Code component provides the copy\n * affordance (`copyable`). Pass `copyable={false}` for a display-only field when\n * an external control owns the copy action (e.g. the connect flow's blue Copiar\n * button, which also gates the following steps on the copy).\n */\nexport function McpEndpointUrl({\n url,\n copyable = true,\n}: {\n url: string;\n copyable?: boolean;\n}): React.JSX.Element {\n return (\n <Code variant=\"block\" copyable={copyable} data-testid=\"mcp-endpoint-url\">\n {url}\n </Code>\n );\n}\n","\"use client\";\n\nimport AddLinkOutlinedIcon from \"@mui/icons-material/AddLinkOutlined\";\nimport CheckCircleIcon from \"@mui/icons-material/CheckCircle\";\nimport LinkOffOutlinedIcon from \"@mui/icons-material/LinkOffOutlined\";\nimport MenuBookOutlinedIcon from \"@mui/icons-material/MenuBookOutlined\";\n\nimport { ConfirmButton } from \"@12-apps/ui/feedback/ConfirmAction\";\nimport { Button } from \"@12-apps/ui/form/Button\";\nimport { Box } from \"@12-apps/ui/mui/Box\";\nimport { Stack } from \"@12-apps/ui/mui/Stack\";\nimport { Text } from \"@12-apps/ui/typography/Text\";\n\nimport type { AiHostGuide } from \"../guide\";\nimport { HostBrandAvatar } from \"./ai-icons\";\n\n/**\n * Revoke a host's access. Returning a promise holds the confirmation popup in\n * its pending state until the write settles, and a rejection keeps it open\n * carrying the reason — so the owner sees the disconnect finish or fail.\n */\nexport type DisconnectHandler = (hostId: string) => void | Promise<unknown>;\n\n/** One assistant's connection state. */\nexport interface HostStatus {\n host: AiHostGuide;\n connected: boolean;\n /** Freeform activity line for a connected host (e.g. \"ativo há 3 min\"). */\n detail?: string;\n}\n\n/**\n * The right-hand side of a CONNECTED box: the green pill, then the two things\n * the pill alone left the owner unable to do.\n *\n * \"Instruções\" exists because the setup steps were reachable ONLY from a RED\n * card's \"Conectar\" — so on an all-green board there was no way to re-read how\n * any of them was connected, which is exactly when someone re-doing the setup on\n * a second machine needs them. It re-enters the same flow that button does.\n *\n * \"Desconectar\" is only drawn when the app supplies a revoke path: a board with\n * nowhere to send the revoke must not offer a button that does nothing.\n */\nfunction ConnectedControls({\n host,\n onConnect,\n onDisconnect,\n}: {\n host: AiHostGuide;\n onConnect: (hostId: string) => void;\n onDisconnect?: DisconnectHandler;\n}): React.JSX.Element {\n return (\n <Stack\n direction=\"row\"\n spacing={1}\n alignItems=\"center\"\n justifyContent=\"flex-end\"\n // Wraps rather than squeezing: three controls do not fit a half-width\n // card at every breakpoint.\n sx={{ flexShrink: 0, flexWrap: \"wrap\", rowGap: 0.5 }}\n >\n <Box\n sx={{\n px: 1,\n py: 0.25,\n borderRadius: 999,\n bgcolor: \"success.main\",\n color: \"success.contrastText\",\n }}\n >\n <Text variant=\"caption\" weight=\"bold\" as=\"span\">\n Conectado\n </Text>\n </Box>\n <Button\n variant=\"text\"\n size=\"sm\"\n onClick={() => onConnect(host.id)}\n data-testid={`ai-status-instructions-${host.id}`}\n >\n <MenuBookOutlinedIcon sx={{ fontSize: 16, mr: 0.5 }} />\n Instruções\n </Button>\n {onDisconnect ? (\n <ConfirmButton\n variant=\"outline\"\n color=\"danger\"\n size=\"sm\"\n onClick={() => onDisconnect(host.id)}\n confirm={{\n title: \"Desconectar o assistente?\",\n entityName: host.label,\n description:\n \"Ele perde o acesso à sua loja na hora. Para voltar a usar, será preciso conectar de novo.\",\n confirmText: \"Desconectar\",\n dataTestId: `ai-status-disconnect-confirm-${host.id}`,\n }}\n data-testid={`ai-status-disconnect-${host.id}`}\n >\n <LinkOffOutlinedIcon sx={{ fontSize: 16, mr: 0.5 }} />\n Desconectar\n </ConfirmButton>\n ) : null}\n </Stack>\n );\n}\n\n/** A single green (connected) / red (to connect) status box for one assistant. */\nfunction StatusBox({\n status,\n onConnect,\n onDisconnect,\n}: {\n status: HostStatus;\n onConnect: (hostId: string) => void;\n onDisconnect?: DisconnectHandler;\n}): React.JSX.Element {\n const { host, connected, detail } = status;\n return (\n <Box\n data-testid={`ai-status-${host.id}`}\n data-connected={connected}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 1.5,\n p: 2,\n borderRadius: 3,\n border: 1,\n borderColor: connected ? \"success.main\" : \"error.main\",\n bgcolor: \"background.paper\",\n }}\n >\n <HostBrandAvatar brand={host.brand} size={36} />\n <Box sx={{ minWidth: 0, flexGrow: 1 }}>\n <Stack direction=\"row\" spacing={0.75} alignItems=\"center\">\n <Text variant=\"body\" weight=\"bold\" as=\"span\">\n {host.label}\n </Text>\n {connected ? <CheckCircleIcon sx={{ fontSize: 18, color: \"success.main\" }} /> : null}\n </Stack>\n <Text variant=\"caption\" as=\"p\" color=\"secondary\">\n {connected ? (detail ?? \"Conectado\") : \"Ainda não conectado\"}\n </Text>\n </Box>\n {connected ? (\n <ConnectedControls host={host} onConnect={onConnect} onDisconnect={onDisconnect} />\n ) : (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onConnect(host.id)}\n data-testid={`ai-status-connect-${host.id}`}\n >\n <AddLinkOutlinedIcon sx={{ fontSize: 16, mr: 0.5 }} />\n Conectar\n </Button>\n )}\n </Box>\n );\n}\n\n/**\n * The completed-state status board for the AI integration: every assistant as a\n * box — green when connected, red when still to connect (with a \"Conectar\"\n * button that re-enters the guided flow for that host). Each connection is\n * attributed to its provider (derived from the OAuth client + confirmed by\n * `announceAiConnection`), so several assistants can show connected at once.\n *\n * A connected box carries two more controls, because the pill it used to show\n * alone was a dead end in both directions: \"Instruções\" re-enters the host's\n * setup steps (previously reachable only from a RED card, so an all-green board\n * hid them entirely), and \"Desconectar\" — when the app passes `onDisconnect` —\n * revokes access, which nothing in the UI could do at all.\n */\nexport function AiStatusBoard({\n statuses,\n onConnect,\n onDisconnect,\n}: {\n statuses: readonly HostStatus[];\n onConnect: (hostId: string) => void;\n /** Revoke this host's access. Omit to render a read-only board. */\n onDisconnect?: DisconnectHandler;\n}): React.JSX.Element {\n return (\n <Stack spacing={2} data-testid=\"ai-status-board\">\n <Box>\n <Text variant=\"heading\" size=\"sm\" as=\"h2\">\n Assistentes conectados\n </Text>\n <Text variant=\"caption\" as=\"p\" color=\"secondary\">\n Em verde os que já operam a sua loja; em vermelho os que faltam conectar.\n </Text>\n </Box>\n <Box\n sx={{\n display: \"grid\",\n gap: 2,\n gridTemplateColumns: { xs: \"1fr\", sm: \"1fr 1fr\" },\n }}\n >\n {statuses.map((status) => (\n <StatusBox\n key={status.host.id}\n status={status}\n onConnect={onConnect}\n onDisconnect={onDisconnect}\n />\n ))}\n </Box>\n </Stack>\n );\n}\n","\"use client\";\n\nimport CheckCircleIcon from \"@mui/icons-material/CheckCircle\";\n\nimport { Box } from \"@12-apps/ui/mui/Box\";\nimport { Stack } from \"@12-apps/ui/mui/Stack\";\nimport { Text } from \"@12-apps/ui/typography/Text\";\n\nimport type { AiHostGuide } from \"../guide\";\nimport { HostBrandAvatar } from \"./ai-icons\";\n\n/** One selectable assistant card (brand mark + name + kind, checked when active). */\nfunction HostCard({\n host,\n active,\n onSelect,\n}: {\n host: AiHostGuide;\n active: boolean;\n onSelect: () => void;\n}): React.JSX.Element {\n return (\n <Box\n component=\"button\"\n type=\"button\"\n onClick={onSelect}\n aria-pressed={active}\n data-testid={`ai-host-card-${host.id}`}\n sx={{\n position: \"relative\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 1.5,\n p: 2,\n borderRadius: 3,\n cursor: \"pointer\",\n textAlign: \"left\",\n width: \"100%\",\n border: 2,\n borderColor: active ? \"primary.main\" : \"divider\",\n bgcolor: active ? \"action.selected\" : \"background.paper\",\n transition: (theme) => theme.transitions.create([\"border-color\", \"background-color\"]),\n \"&:hover\": { borderColor: \"primary.main\" },\n }}\n >\n <HostBrandAvatar brand={host.brand} size={40} />\n <Box sx={{ minWidth: 0 }}>\n <Text variant=\"body\" weight=\"bold\" as=\"span\">\n {host.label}\n </Text>\n <Text variant=\"caption\" as=\"p\" color=\"secondary\">\n {host.kind}\n </Text>\n </Box>\n {active && (\n <CheckCircleIcon\n sx={{ position: \"absolute\", top: 10, right: 10, fontSize: 20, color: \"primary.main\" }}\n />\n )}\n </Box>\n );\n}\n\n/**\n * Step 1 of the guided AI connect flow: the owner picks a single assistant\n * (Claude.ai, Claude Desktop, ChatGPT, Codex) from a card grid. Picking a card\n * advances straight to the next step — no separate \"Próximo\" button. Selection\n * is controlled (persisted by the onboarding flow) so a refresh resumes with the\n * same assistant highlighted.\n */\nexport function HostSelectStep({\n hosts,\n selectedId,\n onSelect,\n}: {\n hosts: readonly AiHostGuide[];\n selectedId: string | null;\n onSelect: (hostId: string) => void;\n}): React.JSX.Element {\n return (\n <Stack spacing={3} data-testid=\"ai-host-select\">\n <Box>\n <Text variant=\"heading\" size=\"sm\" as=\"h2\">\n Escolha o seu assistente\n </Text>\n <Text variant=\"caption\" as=\"p\" color=\"secondary\">\n Selecione onde você usa a IA — o passo a passo é feito sob medida para ele.\n </Text>\n </Box>\n\n <Box\n sx={{\n display: \"grid\",\n gap: 2,\n gridTemplateColumns: { xs: \"1fr\", sm: \"1fr 1fr\" },\n }}\n >\n {hosts.map((host) => (\n <HostCard\n key={host.id}\n host={host}\n active={host.id === selectedId}\n onSelect={() => onSelect(host.id)}\n />\n ))}\n </Box>\n </Stack>\n );\n}\n","\"use client\";\n\nimport { type GuidedNav, type GuidedStep } from \"@12-apps/onboarding\";\n\nimport { providerForHostId, type AiHostGuide } from \"../guide\";\nimport { activeAgo, resolveHost, type AiConnection } from \"./ai-connection-utils\";\nimport {\n ConfigureStageStep,\n ConfigureStep,\n ConfirmStep,\n ConnectStep,\n CopyUrlStep,\n InstallStep,\n} from \"./ai-flow-steps\";\nimport { AiStatusBoard, type DisconnectHandler, type HostStatus } from \"./ai-status-board\";\nimport { HostSelectStep } from \"./host-select-step\";\n\nexport type { AiConnection } from \"./ai-connection-utils\";\nexport { connectedSummary, connectedTitle } from \"./ai-connection-utils\";\n\n/** The completed status board: green for each connected host, red for the rest. */\nexport function StatusBoard({\n nav,\n connections,\n hosts,\n onDisconnect,\n}: {\n nav: GuidedNav;\n connections: readonly AiConnection[];\n hosts: readonly AiHostGuide[];\n onDisconnect?: DisconnectHandler;\n}): React.JSX.Element {\n const legacyHostId = (nav.data.connectedHost ?? nav.data.selectedHost) as string | undefined;\n\n const statuses: HostStatus[] = hosts.map((host) => {\n const provider = providerForHostId(host.id);\n // Prefer a provider-attributed connection; fall back to a legacy null-host\n // connection only for the host the owner completed the flow with.\n const connection =\n (provider ? connections.find((c) => c.host === provider) : undefined) ??\n (host.id === legacyHostId ? connections.find((c) => c.host === null) : undefined) ??\n null;\n return {\n host,\n connected: connection !== null,\n detail: connection ? activeAgo(connection.lastActiveAt) : undefined,\n };\n });\n\n /** Re-enter this host's setup — the manual copy path, or its one-click install. */\n const goToConnect = (hostId: string): void => {\n nav.goTo(hosts.find((h) => h.id === hostId)?.pluginUrl ? \"install\" : \"copy\", {\n selectedHost: hostId,\n });\n };\n\n /**\n * Disconnecting the LAST assistant leaves the section configured with nothing\n * configured: a collapsed \"IA conectada\" summary hiding an all-red board, with\n * the setup instructions two clicks away. So when the revoke empties the\n * board, walk straight back into that host's instructions — the owner who just\n * cut an assistant off is the likeliest person to reconnect one, and the steps\n * are the only thing left worth showing.\n *\n * Cutting one of SEVERAL leaves the board meaningful, so it stays put.\n * `statuses` is the pre-revoke picture, which is what makes \"was that the last\n * one?\" answerable here without waiting for the app to re-read.\n */\n const disconnect: DisconnectHandler | undefined = onDisconnect\n ? async (hostId) => {\n await onDisconnect(hostId);\n const revoked = providerForHostId(hostId);\n const othersLive = statuses.some(\n (status) => status.connected && providerForHostId(status.host.id) !== revoked,\n );\n if (!othersLive) goToConnect(hostId);\n }\n : undefined;\n\n return (\n <AiStatusBoard statuses={statuses} onConnect={goToConnect} onDisconnect={disconnect} />\n );\n}\n\n/** The first step (host picker) — picking a card advances to the right path. */\nfunction selectStep(hosts: readonly AiHostGuide[]): GuidedStep {\n return {\n id: \"select\",\n label: \"Escolher\",\n render: (nav: GuidedNav) => (\n <HostSelectStep\n hosts={hosts}\n selectedId={(nav.data.selectedHost as string | undefined) ?? null}\n onSelect={(hostId) =>\n nav.goTo(hosts.find((h) => h.id === hostId)?.pluginUrl ? \"install\" : \"copy\", {\n selectedHost: hostId,\n })\n }\n />\n ),\n };\n}\n\n/** The Copiar-URL step, shared by every manual flow. */\nfunction copyStep(endpointUrl: string): GuidedStep {\n return {\n id: \"copy\",\n label: \"Copiar URL\",\n render: (nav: GuidedNav) => <CopyUrlStep nav={nav} endpointUrl={endpointUrl} />,\n };\n}\n\n/**\n * The middle steps for the full manual flow. A host with `configureStages`\n * (e.g. ChatGPT) gets one step per stage — each with its own deep link — after\n * Copiar URL; otherwise the default copy → configure → connect split.\n */\nfunction manualMiddle(\n host: AiHostGuide,\n hosts: readonly AiHostGuide[],\n endpointUrl: string,\n connectPrompt: string,\n): GuidedStep[] {\n if (host.configureStages && host.configureStages.length > 0) {\n return [\n copyStep(endpointUrl),\n ...host.configureStages.map((stage) => ({\n id: stage.id,\n label: stage.label,\n render: (nav: GuidedNav) => (\n <ConfigureStageStep nav={nav} host={resolveHost(hosts, nav.data.selectedHost)} stage={stage} />\n ),\n })),\n ];\n }\n return [\n copyStep(endpointUrl),\n {\n id: \"configure\",\n label: \"Configurar\",\n render: (nav: GuidedNav) => <ConfigureStep nav={nav} host={resolveHost(hosts, nav.data.selectedHost)} />,\n },\n {\n id: \"connect\",\n label: \"Conectar\",\n render: (nav: GuidedNav) => (\n <ConnectStep nav={nav} host={resolveHost(hosts, nav.data.selectedHost)} connectPrompt={connectPrompt} />\n ),\n },\n ];\n}\n\n/** The single middle step for the simplified plugin flow: install. */\nfunction simpleMiddle(hosts: readonly AiHostGuide[], connectPrompt: string): GuidedStep[] {\n return [\n {\n id: \"install\",\n label: \"Instalar\",\n render: (nav: GuidedNav) => (\n <InstallStep nav={nav} host={resolveHost(hosts, nav.data.selectedHost)} connectPrompt={connectPrompt} />\n ),\n },\n ];\n}\n\n/**\n * Build the wizard's steps for the selected host's path: the simplified\n * `install` flow when the host has a published `pluginUrl`, the per-stage flow\n * when it declares `configureStages`, otherwise the full copy → configure →\n * connect flow. `select` and `confirm` bookend all of them.\n */\nexport function buildFlowSteps(opts: {\n host: AiHostGuide;\n hosts: readonly AiHostGuide[];\n endpointUrl: string;\n connectPrompt: string;\n connections: readonly AiConnection[];\n onRetest: () => void;\n}): GuidedStep[] {\n const { host, hosts, endpointUrl, connectPrompt, connections, onRetest } = opts;\n const middle = host.pluginUrl\n ? simpleMiddle(hosts, connectPrompt)\n : manualMiddle(host, hosts, endpointUrl, connectPrompt);\n const confirm: GuidedStep = {\n id: \"confirm\",\n label: \"Confirmar\",\n render: (nav: GuidedNav) => (\n <ConfirmStep nav={nav} connections={connections} hosts={hosts} onRetest={onRetest} />\n ),\n };\n return [selectStep(hosts), ...middle, confirm];\n}\n"],"mappings":"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","names":["Box","Stack","Text","Box","jsx","Box","Box","Text","jsx","jsxs","Box","Text","jsx","jsxs","Stack","Box","Text","OpenInNewIcon","useState","Button","Box","Stack","Text","Button","Box","Stack","Text","jsx","jsx","jsxs","Box","Text","Button","Stack","jsx","jsxs","Stack","Button","Box","Text","useState","OpenInNewIcon","CheckCircleIcon","Button","Box","Stack","Text","jsx","jsxs","Stack","Box","Text","Button","CheckCircleIcon","CheckCircleIcon","Box","Stack","Text","jsx","jsxs","Box","Text","CheckCircleIcon","Stack","jsx","jsx"]}
|
package/package.json
CHANGED
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic MCP server core: generate one MCP tool per OpenAPI operation and proxy each call carrying the caller's bearer token (permission passthrough). Also ships the OAuth 2.1 authorization server (./oauth, ./hono: register/authorize/token, JWKS and both .well-known documents), the package-owned Prisma partial + migration for its three tables, the mcp:generate/mcp:check (./generate) and mcp:coverage (./coverage) gates, and the reusable AI-connect onboarding UI (./react).",
|
|
6
6
|
"exports": {
|
|
7
|
-
".":
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"./
|
|
12
|
-
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./react": {
|
|
12
|
+
"types": "./dist/react/index.d.ts",
|
|
13
|
+
"default": "./dist/react/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./oauth": {
|
|
16
|
+
"types": "./dist/oauth/index.d.ts",
|
|
17
|
+
"default": "./dist/oauth/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./hono": {
|
|
20
|
+
"types": "./dist/hono/index.d.ts",
|
|
21
|
+
"default": "./dist/hono/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./coverage": {
|
|
24
|
+
"types": "./dist/coverage-gate/index.d.ts",
|
|
25
|
+
"default": "./dist/coverage-gate/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./generate": {
|
|
28
|
+
"types": "./dist/generate/index.d.ts",
|
|
29
|
+
"default": "./dist/generate/index.js"
|
|
30
|
+
},
|
|
13
31
|
"./package.json": "./package.json"
|
|
14
32
|
},
|
|
15
33
|
"scripts": {
|
|
34
|
+
"build": "rm -rf dist && tsup",
|
|
16
35
|
"clean": "rm -rf node_modules coverage",
|
|
17
36
|
"test": "node ../../scripts/vitest-with-teardown.mjs run --passWithNoTests",
|
|
18
37
|
"test:watch": "vitest watch",
|
|
@@ -23,9 +42,9 @@
|
|
|
23
42
|
"prisma:sync:check": "node scripts/sync-mcp-schema.mjs --check"
|
|
24
43
|
},
|
|
25
44
|
"dependencies": {
|
|
26
|
-
"@12-apps/onboarding": "^2.0.
|
|
27
|
-
"@12-apps/rbac": "^4.0.
|
|
28
|
-
"@12-apps/ui": "^5.0.
|
|
45
|
+
"@12-apps/onboarding": "^2.0.1",
|
|
46
|
+
"@12-apps/rbac": "^4.0.2",
|
|
47
|
+
"@12-apps/ui": "^5.0.1",
|
|
29
48
|
"@mui/icons-material": "^6.5.0",
|
|
30
49
|
"jose": "^6.1.3",
|
|
31
50
|
"react": "^19.2.0"
|
|
@@ -46,6 +65,7 @@
|
|
|
46
65
|
"hono": "^4.6.0",
|
|
47
66
|
"jsdom": "^25.0.1",
|
|
48
67
|
"react-dom": "^19.2.0",
|
|
68
|
+
"tsup": "^8.0.0",
|
|
49
69
|
"typescript": "^5.8.2",
|
|
50
70
|
"vitest": "^3.2.4"
|
|
51
71
|
},
|
|
@@ -71,6 +91,7 @@
|
|
|
71
91
|
"*.mjs",
|
|
72
92
|
"*.md",
|
|
73
93
|
"!eslint.config.js",
|
|
94
|
+
"!tsup.config.ts",
|
|
74
95
|
"!**/__tests__/**",
|
|
75
96
|
"!**/tests/**",
|
|
76
97
|
"!**/*.test.*",
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
-- Authorization codes are deliberately absent: they are STATELESS signed blobs,
|
|
19
19
|
-- so there is no table to create and nothing to sweep.
|
|
20
20
|
--
|
|
21
|
-
-- The columns, defaults, indexes and CHECK are
|
|
21
|
+
-- The columns, defaults, indexes and CHECK are future-pay's
|
|
22
22
|
-- `20260713120000_add_oauth_client_refresh`,
|
|
23
23
|
-- `20260715180000_add_onboarding_state_mcp_connection` (the mcp_connections half
|
|
24
24
|
-- — the onboarding half belongs to @12-apps/onboarding) and
|
|
25
25
|
-- `20260720120000_add_mcp_connection_host` verbatim, minus the FK to `users`:
|
|
26
26
|
-- this package cannot know the name of a host's user table, and a host that has
|
|
27
|
-
-- one keeps its own constraint (
|
|
27
|
+
-- one keeps its own constraint (future-pay's is ON DELETE CASCADE).
|
|
28
28
|
--
|
|
29
29
|
-- EVERY statement is guarded (`IF NOT EXISTS`, and a conrelid-scoped DO block for
|
|
30
30
|
-- the CHECK, which has no IF NOT EXISTS form). That is what makes adoption by a
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
-- the difference bites exactly the host this file is written for: `CREATE TABLE IF
|
|
36
36
|
-- NOT EXISTS` skips the whole table, columns included, so a host holding an OLDER
|
|
37
37
|
-- shape of one of these tables silently keeps it. Each table below is therefore
|
|
38
|
-
-- followed by a guarded `ADD COLUMN` for every column that reached
|
|
38
|
+
-- followed by a guarded `ADD COLUMN` for every column that reached future-pay in a
|
|
39
39
|
-- LATER migration than its own CREATE. The full audit: `oauth_refresh_tokens
|
|
40
40
|
-- .user_sub` (`20260713150000_add_oauth_refresh_user_sub`) and `mcp_connections
|
|
41
41
|
-- .host` (`20260720120000_add_mcp_connection_host`). `oauth_clients` needs none —
|
|
@@ -112,10 +112,10 @@ CREATE INDEX IF NOT EXISTS "oauth_refresh_tokens_user_email_client_id_idx"
|
|
|
112
112
|
-- `CREATE TABLE IF NOT EXISTS` skips the WHOLE table, so a host that already holds
|
|
113
113
|
-- `oauth_refresh_tokens` in an OLDER SHAPE gets none of the columns declared above
|
|
114
114
|
-- — statement-level guarding is not the same as column-level guarding. That is
|
|
115
|
-
-- precisely how
|
|
115
|
+
-- precisely how future-pay's own history ran: `user_sub` arrived in a SECOND
|
|
116
116
|
-- migration (FUT-105, `20260713150000_add_oauth_refresh_user_sub`), so a host
|
|
117
117
|
-- frozen before it would adopt this file, skip the CREATE, never get the column,
|
|
118
|
-
-- and then fail on every refresh the package serves. Mirror
|
|
118
|
+
-- and then fail on every refresh the package serves. Mirror future-pay's pair
|
|
119
119
|
-- verbatim — guarded add with a backfill default to satisfy NOT NULL, then drop
|
|
120
120
|
-- the default so the column matches the Prisma schema (`String`, no default).
|
|
121
121
|
-- Both statements are no-ops on a fresh host and on a replay.
|
|
@@ -147,6 +147,6 @@ CREATE INDEX IF NOT EXISTS "mcp_connections_last_active_at_idx"
|
|
|
147
147
|
ON "mcp_connections"("last_active_at");
|
|
148
148
|
|
|
149
149
|
-- A host adopting this migration where `mcp_connections` predates the `host`
|
|
150
|
-
-- column (
|
|
150
|
+
-- column (future-pay added it in a later migration) gets it here; a fresh host
|
|
151
151
|
-- already has it from the CREATE above, so the guard makes both cases a no-op.
|
|
152
152
|
ALTER TABLE "mcp_connections" ADD COLUMN IF NOT EXISTS "host" TEXT;
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,11 @@ export type {
|
|
|
36
36
|
OpenApiResponse,
|
|
37
37
|
} from "./openapi/generate";
|
|
38
38
|
export { dispatchTool, DispatchInputError } from "./dispatch/proxy";
|
|
39
|
+
// Both halves of the redaction contract. `redactResponseSchema` narrows what a
|
|
40
|
+
// tool ADVERTISES at generate time; `redactResponseBody` removes the same paths
|
|
41
|
+
// from what it RETURNS at dispatch. Taking one without the other reintroduces the
|
|
42
|
+
// schema/payload disagreement they exist to prevent — see `server/redact.ts`.
|
|
43
|
+
export { redactResponseSchema, redactResponseBody } from "./server/redact";
|
|
39
44
|
export {
|
|
40
45
|
createToolRegistry,
|
|
41
46
|
HTTP_STATUS_META_KEY,
|
package/src/server/redact.ts
CHANGED
|
@@ -1,12 +1,102 @@
|
|
|
1
|
+
import type { JsonSchema } from "../types";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
+
* Both halves of the redaction contract: the SCHEMA a tool advertises, and the
|
|
5
|
+
* BODY it returns.
|
|
6
|
+
*
|
|
7
|
+
* They only work as a pair, and the failure mode when they disagree is not a
|
|
8
|
+
* cosmetic one. The dispatcher forwards the wrapped endpoint's response body
|
|
9
|
+
* verbatim, so a narrowed `outputSchema` alone would only change what the
|
|
10
|
+
* manifest CLAIMS is returned — the value still reaches the agent. Strip the
|
|
11
|
+
* body but leave the schema advertising the field, and every successful call
|
|
12
|
+
* fails validation against the very schema the manifest published, worst of all
|
|
13
|
+
* when the field was `required`.
|
|
4
14
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
15
|
+
* So `redactResponseSchema` removes the paths at generate time and
|
|
16
|
+
* `redactResponseBody` removes the same paths at dispatch, from one list. A host
|
|
17
|
+
* that takes one and hand-rolls the other is back to the disagreement this pair
|
|
18
|
+
* exists to prevent.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Structural keywords that wrap a value without naming a field. */
|
|
22
|
+
const SCHEMA_WRAPPERS = ["items", "anyOf", "oneOf", "allOf"] as const;
|
|
23
|
+
|
|
24
|
+
function isSchema(value: unknown): value is JsonSchema {
|
|
25
|
+
return Boolean(value) && typeof value === "object";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Drop `field` from an object schema's properties AND its `required` list. */
|
|
29
|
+
function deleteProperty(schema: JsonSchema, field: string): true {
|
|
30
|
+
delete (schema.properties as Record<string, JsonSchema>)[field];
|
|
31
|
+
if (Array.isArray(schema.required)) {
|
|
32
|
+
const kept = (schema.required as string[]).filter((name) => name !== field);
|
|
33
|
+
if (kept.length) schema.required = kept;
|
|
34
|
+
else delete schema.required;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Descend into wrappers without consuming a segment, so `data.taxId` addresses
|
|
41
|
+
* an array of rows and a `.nullable()` union branch alike.
|
|
9
42
|
*/
|
|
43
|
+
function omitInWrappers(schema: JsonSchema, segments: readonly string[]): boolean {
|
|
44
|
+
return SCHEMA_WRAPPERS.reduce((removed, keyword) => {
|
|
45
|
+
const child = schema[keyword];
|
|
46
|
+
const branches = Array.isArray(child) ? child : [child];
|
|
47
|
+
return branches.reduce<boolean>(
|
|
48
|
+
(acc, branch) => (isSchema(branch) && omitSchemaPath(branch, segments)) || acc,
|
|
49
|
+
removed,
|
|
50
|
+
);
|
|
51
|
+
}, false);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function omitSchemaPath(schema: JsonSchema, segments: readonly string[]): boolean {
|
|
55
|
+
const inWrappers = omitInWrappers(schema, segments);
|
|
56
|
+
|
|
57
|
+
const properties = schema.properties as Record<string, JsonSchema> | undefined;
|
|
58
|
+
const [head, ...rest] = segments;
|
|
59
|
+
if (!properties || !head || !(head in properties)) return inWrappers;
|
|
60
|
+
|
|
61
|
+
if (rest.length === 0) return deleteProperty(schema, head);
|
|
62
|
+
|
|
63
|
+
const child = properties[head];
|
|
64
|
+
return (isSchema(child) && omitSchemaPath(child, rest)) || inWrappers;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Return `schema` without the listed dotted paths — the advertised half.
|
|
69
|
+
*
|
|
70
|
+
* THROWS when a path names nothing, rather than returning quietly: a typo'd or
|
|
71
|
+
* stale redaction would otherwise protect nothing at all, and it would do so
|
|
72
|
+
* invisibly, which is the one outcome a redaction list must never have. Failing
|
|
73
|
+
* here turns it into a generator error naming the offending path.
|
|
74
|
+
*
|
|
75
|
+
* The input is cloned, not narrowed in place: a caller may hold the converted
|
|
76
|
+
* schema for other uses (a shared `$defs` component, a schema reused across two
|
|
77
|
+
* operations), and mutating it would redact those too.
|
|
78
|
+
*
|
|
79
|
+
* `operationId` only shapes the error message — pass it so the failure names
|
|
80
|
+
* which tool declared the bad path.
|
|
81
|
+
*/
|
|
82
|
+
export function redactResponseSchema(
|
|
83
|
+
schema: JsonSchema,
|
|
84
|
+
paths: readonly string[],
|
|
85
|
+
operationId?: string,
|
|
86
|
+
): JsonSchema {
|
|
87
|
+
if (!paths.length) return schema;
|
|
88
|
+
|
|
89
|
+
const clone = structuredClone(schema);
|
|
90
|
+
paths.forEach((path) => {
|
|
91
|
+
if (!omitSchemaPath(clone, path.split("."))) {
|
|
92
|
+
const subject = operationId ? `MCP endpoint "${operationId}"` : "This response schema";
|
|
93
|
+
throw new Error(
|
|
94
|
+
`${subject} declares a response redaction for "${path}", which is not a field of its response schema`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return clone;
|
|
99
|
+
}
|
|
10
100
|
|
|
11
101
|
/** Walk one dotted path, mapping over arrays, and delete the leaf. */
|
|
12
102
|
function stripPath(value: unknown, segments: readonly string[]): void {
|