import { AlertCircleIcon, BotIcon, CheckCircle2Icon, CpuIcon, Loader2Icon, PlugZapIcon, SearchCheckIcon, } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { Button } from "../components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "../components/ui/card"; import { apiFetch } from "../hooks/use-api"; interface PageProps { navigate: (to: string | number) => void; } interface ConnectorStatus { id: string; appName: string; installKind: "skill" | "mcp"; target: string; scope: "user" | "project"; installed: boolean; path: string; summary: string; nextAction: string; mode: { label: string; detail: string; }; error?: string; } interface ConnectorsResponse { connectors: ConnectorStatus[]; collections: string[]; } interface ConnectorVerificationResponse { verification: { collection: string; lexicalReady: boolean; connectorReady: boolean; generatedAt: string; stages: { connector: { status: "passed" | "pending" | "failed" | "skipped"; code?: string; }; }; }; remediation: string | null; } export default function Connectors({ navigate }: PageProps) { const [connectors, setConnectors] = useState([]); const [collections, setCollections] = useState([]); const [selectedCollection, setSelectedCollection] = useState(""); const [error, setError] = useState(null); const [installingId, setInstallingId] = useState(null); const [verifyingId, setVerifyingId] = useState(null); const [verificationById, setVerificationById] = useState< Record >({}); const loadConnectors = useCallback(async () => { const { data, error: err } = await apiFetch("/api/connectors"); if (err) { setError(err); return; } setConnectors(data?.connectors ?? []); const nextCollections = data?.collections ?? []; setCollections(nextCollections); setSelectedCollection((current) => nextCollections.includes(current) ? current : (nextCollections[0] ?? "") ); setError(null); }, []); useEffect(() => { void loadConnectors(); }, [loadConnectors]); const handleInstall = async (connectorId: string) => { const connector = connectors.find((entry) => entry.id === connectorId); setInstallingId(connectorId); const { error: err } = await apiFetch("/api/connectors/install", { method: "POST", body: JSON.stringify({ connectorId, reinstall: connector?.installed ?? false, }), }); setInstallingId(null); if (err) { setError(err); return; } await loadConnectors(); }; const handleVerify = async (connectorId: string) => { if (!selectedCollection) { setError("Add and index a collection before verifying retrieval."); return; } setVerifyingId(connectorId); setError(null); const { data, error: err } = await apiFetch( "/api/connectors/verify", { method: "POST", body: JSON.stringify({ connectorId, collection: selectedCollection, }), } ); setVerifyingId(null); if (err || !data) { setError(err ?? "Connector verification could not be completed."); return; } setVerificationById((current) => ({ ...current, [connectorId]: data, })); }; return (
Agent Connectors

Install GNO into your coding agents without editing config files

These actions reuse the existing CLI installers. Read/search access is the default path. Write-capable MCP stays an advanced opt-in instead of something the app quietly enables for you.

{collections.length > 0 && ( )}
{error && ( {error} )}
{connectors.map((connector) => { const verification = verificationById[connector.id]; return (
{connector.appName}
{connector.installed ? ( ) : ( )}
{connector.summary}
{connector.mode.label}

{connector.mode.detail}

Install type:{" "} {connector.installKind.toUpperCase()}
Scope:{" "} {connector.scope}
{connector.path}
{connector.error && (

{connector.error}

)}

{connector.nextAction}

{connector.installed && connector.installKind === "skill" && (
Runtime verification unavailable

target_runtime_unverifiable — this client exposes no safe read-only runtime hook. The skill file is installed, but GNO cannot claim the agent used it.

)} {connector.installed && connector.installKind === "mcp" && (

Retrieval proof

Starts this configured MCP once and checks a real, collection-scoped result. Runs only when requested.

{collections.length === 0 && (

Add and index a collection before verifying retrieval.

)} {verification && (

{verification.verification.stages.connector.status} {verification.verification.stages.connector.code ? ` / ${verification.verification.stages.connector.code}` : ""}

Collection: {verification.verification.collection}

{verification.remediation ?? "Retrieval returned the expected indexed source. No action needed."}

)}
)}
); })}
); }