diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..c3bb152cd --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@arcadeai:registry=https://registry.npmjs.org/ diff --git a/app/_data/partner-toolkits.ts b/app/_data/partner-toolkits.ts new file mode 100644 index 000000000..e2779aac9 --- /dev/null +++ b/app/_data/partner-toolkits.ts @@ -0,0 +1,42 @@ +import type { Toolkit } from "@arcadeai/design-system"; + +/** + * Docs-local partner toolkits (remote MCP Servers offered by our partners). + * + * These entries are merged into the live integrations catalog alongside the + * TOOLKITS exported from @arcadeai/design-system. Each partner toolkit uses + * a standard ToolkitType (typically "verified") plus an `isPartner: true` + * flag that renders a Partner badge next to BYOC/Pro on catalog cards. + * + * Once DS adds an explicit `isPartner` field to its Toolkit shape, migrate + * these entries into the DS TOOLKITS array and delete this file. + */ + +export type PartnerToolkit = Toolkit & { + isPartner: true; + /** + * Remote MCP Server URL displayed on the partner detail page. Use a + * placeholder like `YOUR_API_KEY` for any per-user secrets so the URL can + * be copied without leaking credentials. Updating this field updates the + * detail page automatically. + */ + mcpUrl: string; +}; + +export const PARTNER_TOOLKITS: PartnerToolkit[] = [ + { + id: "Tavily", + label: "Tavily", + category: "search", + publicIconUrl: "/images/partners/tavily.svg", + isBYOC: true, + isPro: false, + isPartner: true, + type: "verified", + mcpUrl: "https://mcp.tavily.com/mcp/?tavilyApiKey=YOUR_API_KEY", + docsLink: "https://docs.arcade.dev/en/resources/integrations/search/tavily", + relativeDocsLink: "/en/resources/integrations/search/tavily", + isComingSoon: false, + isHidden: false, + }, +]; diff --git a/app/_lib/toolkit-slug.ts b/app/_lib/toolkit-slug.ts index a9bab4c70..5ff34e21d 100644 --- a/app/_lib/toolkit-slug.ts +++ b/app/_lib/toolkit-slug.ts @@ -9,12 +9,16 @@ export type ToolkitSlugSource = { }; /** - * Toolkit with an optional docsLink property. - * The design-system `Toolkit` type doesn't include `docsLink` in its - * type definitions, but some entries carry it at runtime. This type - * makes the property explicit so both server and client code can share it. + * Toolkit with optional `docsLink` and `isPartner` properties. + * The design-system `Toolkit` type doesn't include either field, but some + * docs-local entries carry them at runtime (e.g. partner toolkits that + * render a Partner badge on cards). This type makes the properties explicit + * so both server and client code can share it. */ -export type ToolkitWithDocsLink = Toolkit & { docsLink?: string | null }; +export type ToolkitWithDocsLink = Toolkit & { + docsLink?: string | null; + isPartner?: boolean; +}; /** * Strip all non-alphanumeric characters and lowercase. diff --git a/app/en/resources/integrations/components/tool-card.tsx b/app/en/resources/integrations/components/tool-card.tsx index 14ff067de..be92ac880 100644 --- a/app/en/resources/integrations/components/tool-card.tsx +++ b/app/en/resources/integrations/components/tool-card.tsx @@ -9,7 +9,7 @@ import { type ToolkitType, } from "@arcadeai/design-system"; import { cn } from "@arcadeai/design-system/lib/utils"; -import { Package } from "lucide-react"; +import { Handshake, Package } from "lucide-react"; import Link from "next/link"; import posthog from "posthog-js"; import type React from "react"; @@ -26,6 +26,7 @@ type ToolCardProps = { isComingSoon?: boolean; isByoc?: boolean; isPro?: boolean; + isPartner?: boolean; }; export const ToolCard: React.FC = ({ @@ -37,6 +38,7 @@ export const ToolCard: React.FC = ({ isComingSoon = false, isByoc = false, isPro = false, + isPartner = false, }) => { const [isModalOpen, setIsModalOpen] = useState(false); const { @@ -45,7 +47,7 @@ export const ToolCard: React.FC = ({ icon: IconComponent, color, } = TOOL_CARD_TYPE_CONFIG[type]; - const showHeaderBadges = isByoc || isPro || isComingSoon; + const showHeaderBadges = isByoc || isPro || isPartner || isComingSoon; const trackToolCardClick = () => { posthog.capture("Tool card clicked", { @@ -55,6 +57,7 @@ export const ToolCard: React.FC = ({ is_coming_soon: isComingSoon, is_byoc: isByoc, is_pro: isPro, + is_partner: isPartner, }); }; @@ -131,6 +134,15 @@ export const ToolCard: React.FC = ({ )} {isByoc && } {isPro && } + {isPartner && ( + + + Partner + + )} )} diff --git a/app/en/resources/integrations/components/toolkits-client.tsx b/app/en/resources/integrations/components/toolkits-client.tsx index 8f478fb17..c0ada8f28 100644 --- a/app/en/resources/integrations/components/toolkits-client.tsx +++ b/app/en/resources/integrations/components/toolkits-client.tsx @@ -5,6 +5,7 @@ import { getToolkitIcon, Separator, } from "@arcadeai/design-system"; +import { Generic as GenericIcon } from "@arcadeai/design-system/components/ui/atoms/icons"; import { cn } from "@arcadeai/design-system/lib/utils"; import { Plus, Search } from "lucide-react"; import Link from "next/link"; @@ -36,25 +37,27 @@ function mapToToolkitPage( /** * Get toolkit icon with fallback for API toolkits. - * If "GithubApi" has no icon, falls back to "Github" icon. + * + * `getToolkitIcon` from @arcadeai/design-system returns the Generic placeholder + * (not null) when a toolkit id isn't in its icon map. For toolkits that ship + * their own `publicIconUrl` (e.g. partner toolkits not yet in the DS), + * we treat Generic as "no match" so the caller can fall through to `iconUrl`. */ function getToolkitIconWithFallback( toolkitId: string ): React.ComponentType> | null { const apiSuffix = "api"; - // Try direct match first - const directIcon = getToolkitIcon(toolkitId); - if (directIcon) { - return directIcon; + const resolved = getToolkitIcon(toolkitId); + if (resolved && resolved !== GenericIcon) { + return resolved; } - // For API toolkits, try the base provider ID const normalizedId = toolkitId.toLowerCase(); if (normalizedId.endsWith(apiSuffix)) { - const baseProviderId = toolkitId.slice(0, -apiSuffix.length); // Remove "Api" suffix + const baseProviderId = toolkitId.slice(0, -apiSuffix.length); const baseIcon = getToolkitIcon(baseProviderId); - if (baseIcon) { + if (baseIcon && baseIcon !== GenericIcon) { return baseIcon; } } @@ -175,6 +178,7 @@ export default function ToolkitsClient({ toolkits }: ToolkitsClientProps) { iconUrl={iconUrl} isByoc={toolkit.isBYOC} isComingSoon={toolkit.isComingSoon} + isPartner={toolkit.isPartner} isPro={toolkit.isPro} key={toolkit.id} link={mapToToolkitPage( diff --git a/app/en/resources/integrations/components/toolkits.tsx b/app/en/resources/integrations/components/toolkits.tsx index 03e46e613..e76f680d2 100644 --- a/app/en/resources/integrations/components/toolkits.tsx +++ b/app/en/resources/integrations/components/toolkits.tsx @@ -1,10 +1,12 @@ import { TOOLKITS, type Toolkit } from "@arcadeai/design-system"; +import { PARTNER_TOOLKITS } from "@/app/_data/partner-toolkits"; import { readToolkitData } from "@/app/_lib/toolkit-data"; -import { normalizeToolkitId } from "@/app/_lib/toolkit-slug"; +import { + normalizeToolkitId, + type ToolkitWithDocsLink, +} from "@/app/_lib/toolkit-slug"; import ToolkitsClient from "./toolkits-client"; -type ToolkitWithDocsLink = Toolkit & { docsLink?: string | null }; - const getToolkitDocsLink = (toolkit: Toolkit): string | undefined => { if ("docsLink" in toolkit) { const value = (toolkit as ToolkitWithDocsLink).docsLink; @@ -33,13 +35,15 @@ const getToolkitsWithDocsLinks = async (): Promise => { }) ); - return TOOLKITS.map((toolkit) => { + const dsToolkits: ToolkitWithDocsLink[] = TOOLKITS.map((toolkit) => { const existing = getToolkitDocsLink(toolkit); const docsLink = existing ?? docsLinkById.get(normalizeToolkitId(toolkit.id)); return docsLink ? { ...toolkit, docsLink } : toolkit; }); + + return [...dsToolkits, ...PARTNER_TOOLKITS]; }; export default async function Toolkits() { diff --git a/app/en/resources/integrations/components/use-toolkit-filters.ts b/app/en/resources/integrations/components/use-toolkit-filters.ts index e26a5e71e..2d43686ff 100644 --- a/app/en/resources/integrations/components/use-toolkit-filters.ts +++ b/app/en/resources/integrations/components/use-toolkit-filters.ts @@ -25,7 +25,7 @@ const TYPE_LABELS: Record = { const getTypePriority = (type: string): number => TYPE_PRIORITY[type as ToolkitType] ?? DEFAULT_PRIORITY; -const compareToolkits = (a: Toolkit, b: Toolkit): number => { +const compareToolkits = (a: T, b: T): number => { // First prioritize available toolkits over coming soon toolkits if (a.isComingSoon !== b.isComingSoon) { return a.isComingSoon ? 1 : -1; @@ -80,7 +80,7 @@ export const useFilterStore = create((set) => ({ }), })); -export function useToolkitFilters(toolkits: Toolkit[]) { +export function useToolkitFilters(toolkits: T[]) { const { selectedCategory, selectedType, @@ -91,7 +91,7 @@ export function useToolkitFilters(toolkits: Toolkit[]) { const debouncedSearchQuery = useDebounce(searchQuery, DEBOUNCE_TIME); - const filteredToolkits = useMemo(() => { + const filteredToolkits = useMemo(() => { const searchLower = debouncedSearchQuery.toLowerCase(); return toolkits diff --git a/app/en/resources/integrations/search/_meta.tsx b/app/en/resources/integrations/search/_meta.tsx index 1936cef66..effa5b88f 100644 --- a/app/en/resources/integrations/search/_meta.tsx +++ b/app/en/resources/integrations/search/_meta.tsx @@ -53,6 +53,14 @@ const meta: MetaRecord = { title: "Exa API", href: "/en/resources/integrations/search/exa-api", }, + "-- Partner": { + type: "separator", + title: "Partner", + }, + tavily: { + title: "Tavily", + href: "/en/resources/integrations/search/tavily", + }, }; export default meta; diff --git a/app/en/resources/integrations/search/tavily/page.mdx b/app/en/resources/integrations/search/tavily/page.mdx new file mode 100644 index 000000000..a7dde7909 --- /dev/null +++ b/app/en/resources/integrations/search/tavily/page.mdx @@ -0,0 +1,80 @@ +--- +title: "Tavily" +description: "Enable agents to search the web in real time and extract structured content. Available directly in Arcade as a Partner MCP Server." +--- + +import { Callout, Steps } from "nextra/components"; + +# Tavily + +This integration is a remote MCP Server offered by [Tavily](https://tavily.com), an Arcade Partner. Add it to an [MCP Gateway](/guides/mcp-gateways/add-remote-servers) for central governance, authorization, and access control alongside Arcade's native servers. + +## MCP Server URL + +Paste the following URL into Arcade: **Servers → Add Server → Remote MCP**. Replace `YOUR_API_KEY` with the key you generate at [tavily.com](https://tavily.com). + +```text +https://mcp.tavily.com/mcp/?tavilyApiKey=YOUR_API_KEY +``` + +## What you can do + +Once you register Tavily in your Arcade project, Arcade discovers these tools automatically: + +| Tool | What it does | +| --- | --- | +| `Tavily.Search` | Real-time web search with agent-optimized ranking. | +| `Tavily.Extract` | Extract structured content from specific URLs. | +| `Tavily.Crawl` | Crawl a site and return content across pages. | +| `Tavily.Map` | Map the structure of a site or domain. | +| `Tavily.Research` | Multi-source deep research across the web. | +| `Tavily.Skill` | High-level research skills built on the tools above. | + +Compose these tools with Google Docs, Slack, Salesforce, GitHub, or any of Arcade's native servers in a single MCP Gateway, so an agent can research, draft, and act in one flow, with full authorization and audit from Arcade's runtime. + +## Add Tavily to your Arcade project + + + +### Get your Tavily API key + +Go to [tavily.com](https://tavily.com) → **Overview** → **Generate MCP Link**. Copy the generated URL (it contains your API key). + +### Add Tavily as a Remote MCP Server in Arcade + +Open the [Arcade Dashboard](https://api.arcade.dev/dashboard) → **Servers** → **Add Server** → **Remote MCP**. Paste the URL from the [MCP Server URL](#mcp-server-url) section above (with your API key in place of `YOUR_API_KEY`) and save. + + + See the full walkthrough in Add remote MCP servers for advanced settings like connection retries, OAuth, and custom headers. + + +### Verify Tavily tools + +Arcade discovers six Tavily tools: `Tavily.Search`, `Tavily.Extract`, `Tavily.Crawl`, `Tavily.Map`, `Tavily.Research`, and `Tavily.Skill`. They appear in the Playground for this project and in the MCP Gateway tool picker. + +### Create an MCP Gateway + +Go to **MCP Gateways** → **Create Gateway**. Select Tavily plus any other MCP Servers you want to compose with (for example, Google Docs and Slack). Set the auth mode to **Arcade Auth** so users authenticate with their Arcade account. Copy the gateway URL. This is what your agent connects to. + + + +## Call Tavily tools from your agent + +Once you create your gateway, any MCP client that supports Streamable HTTP can use it: Cursor, Claude Desktop, VS Code, or a custom application built with the Vercel AI SDK, LangChain, or OpenAI Agents. + +```text +https://api.arcade.dev/mcp/ +``` + +Arcade handles authorization, credential handling, and audit logging at runtime. + +## Example + +See the open-source [Financial Intelligence Agent](https://github.com/arcadeai-labs/financial-intelligence), a web-based research assistant that composes Tavily, Google Docs, and Slack through a single MCP Gateway in under 200 lines of code. + +## Resources + +- [Tavily documentation](https://docs.tavily.com) +- [Add remote MCP servers to Arcade](/guides/mcp-gateways/add-remote-servers) +- [Create an MCP Gateway](/guides/mcp-gateways/create-via-dashboard) +- [Connect to MCP clients](/get-started/mcp-clients) diff --git a/public/images/partners/tavily.svg b/public/images/partners/tavily.svg new file mode 100644 index 000000000..458b2143a --- /dev/null +++ b/public/images/partners/tavily.svg @@ -0,0 +1 @@ +Tavily \ No newline at end of file diff --git a/public/llms.txt b/public/llms.txt index c733823dc..27c669a8a 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -142,6 +142,7 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Setup Arcade with LangChain](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-ts): This documentation page provides a comprehensive guide on integrating Arcade tools within LangChain agents, enabling users to build and manage AI agents effectively. It covers essential concepts, prerequisites, and step-by-step instructions for transforming Arcade tools into LangChain-compatible tools, configuring agents - [Setup Arcade with OpenAI Agents (TypeScript)](https://docs.arcade.dev/en/get-started/agent-frameworks/openai-agents/setup-typescript): This documentation page provides a comprehensive guide for setting up and building AI agents using the OpenAI Agents SDK with Arcade tools in TypeScript. It covers the integration process, including converting Arcade tools to the required format, handling authorization, and executing agent functions. - [Setup Arcade with OpenAI Agents SDK](https://docs.arcade.dev/en/get-started/agent-frameworks/openai-agents/setup-python): This documentation page guides users on how to set up and integrate Arcade tools within OpenAI Agents applications using the OpenAI Agents SDK. It covers the necessary prerequisites, provides step-by-step instructions for creating a CLI agent, and explains how to implement tool authorization +- [Tavily](https://docs.arcade.dev/en/resources/integrations/search/tavily): Documentation page - [The Arcade Registry](https://docs.arcade.dev/en/resources/registry-early-access): The Arcade Registry documentation provides an overview of a platform where developers can share and monetize their tools for agentic applications, similar to HuggingFace or Pypi. It explains how the registry integrates runtime metrics and user feedback to enhance tool development and usage - [Tool error handling](https://docs.arcade.dev/en/guides/tool-calling/error-handling): This documentation page provides guidance on effectively handling errors when using tools with Arcade's Tool Development Kit (TDK). It explains the error handling philosophy, outlines best practices, and offers code examples for managing output errors in various programming languages. Users will learn how - [Tool feedback](https://docs.arcade.dev/en/resources/integrations/tool-feedback): Documentation page