import { useEffect, useState } from "react";
import { assistantClientNames, mcpAuthorizationHandoffInstructions, mcpInvitation, mcpSetupSteps, type AssistantClient, type McpConnection } from "@paperclipai/shared";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Check, Copy, ExternalLink, Globe, Paperclip, Plug, Terminal } from "lucide-react";
import { publicMcpApi } from "@/api/publicMcp";
import { ApiError } from "@/api/client";
import { useCompany } from "@/context/CompanyContext";
import { useBreadcrumbs } from "@/context/BreadcrumbContext";
import { Link } from "@/lib/router";
import { copyTextToClipboard } from "@/lib/clipboard";
import { deriveInitials, Identity } from "@/components/Identity";
import { assistantConnectionDisplayName } from "./connection-owner";
import { AgentSetupPrompt } from "@/components/AgentSetupPrompt";
import { OpenCodeLogoIcon } from "@/components/OpenCodeLogoIcon";
import { CompanyPatternIcon } from "@/components/CompanyPatternIcon";
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
export const ASSISTANT_CONNECTION_PATH = "/apps/assistant-connection";
const connectionsKey = ["mcp-connections"];
type Assistant = AssistantClient;
const assistants = assistantClientNames;
function AssistantIcon({ assistant }: { assistant: Assistant }) {
if (assistant === "codex" || assistant === "claude") return ;
if (assistant === "opencode") return ;
const Icon = assistant === "browser" ? Globe : assistant === "headless" ? Terminal : Plug;
return ;
}
export function useAssistantConnections(poll = false) {
const { selectedCompanyId } = useCompany();
const query = useQuery({
queryKey: connectionsKey, queryFn: publicMcpApi.connections, retry: false,
enabled: Boolean(selectedCompanyId), refetchInterval: poll ? 5000 : false,
});
return { ...query, rows: (query.data ?? []).filter(row => row.companyId === selectedCompanyId && !row.revokedAt && !row.scopes.includes("paperclip:agent")) };
}
/** Inbound assistant access belongs beside the existing outbound connectors. */
export function AssistantConnectionCard({ onNavigate }: { onNavigate: (href: string) => void }) {
const connections = useAssistantConnections();
const active = connections.rows;
const action = !connections.isSuccess ? "Open" : active.length ? "Manage" : "Set up";
return
Use your Paperclip organization from Codex, Claude, OpenCode, or another assistant.
Checking your connection status…
} {connections.isError &&Couldn’t load your connection status.
{value}
{error ? "Couldn’t copy. Select and copy the text above." : copied ? "Copied" : null}
Select an organization to connect an assistant.
; const serverUrl = setup.data?.serverUrl ?? ""; const invitation = serverUrl ? mcpInvitation(serverUrl, { id: selectedCompanyId, name: selectedCompany.name }) : ""; return{selectedCompany.name}
Connect your assistant to Paperclip. Review work, create tasks, and follow up using your account’s access to this organization.
Loading assistant setup…
} {setup.error &&{setupError(setup.error)}
An instance administrator must turn on Assistant connections (MCP) in Experimental settings. Then return here to connect your assistant.
Paste this invitation into your assistant. It will help set up the connection and ask you to approve access in Paperclip.
{step.text}
{step.code &&{mcpAuthorizationHandoffInstructions}
}Choose {selectedCompany.name}, review access, then click Connect organization.
Creating tasks and adding comments may start agent work using the organization’s configured execution budget.
Loading connections…
} {(connections.error || revoke.error) &&{revoke.error ? "Couldn’t revoke this connection. Try again." : "Couldn’t load your connections. Try again."}
} {connections.isSuccess && connections.rows.length === 0 &&No assistants connected to {selectedCompany.name} yet.
}{`Connected as you · ${row.scopes.includes("paperclip:write") ? "Read and write" : "Read only"}${row.scopes.includes("paperclip:configure") ? " · Configure agents, projects and skills" : ""}`}