Skip to content

Commit 599fc30

Browse files
avogurugithub-actions[bot]emmithood
authored
add Partner badge and Tavily as first partner MCP (#948)
* feat(integrations): add Partner badge and Tavily as first partner MCP - Add `isPartner` flag to ToolkitWithDocsLink so toolkits can render a Partner badge alongside BYOC/Pro on catalog cards (Handshake icon, pink). - Treat Generic icon from @arcadeai/design-system as "no match" in getToolkitIconWithFallback so toolkits with publicIconUrl render their own SVG instead of the placeholder. - New PARTNER_TOOLKITS data source for partner remote MCP listings. Tavily ships first as type "verified" with isPartner=true and a templated mcpUrl. - Hand-crafted Tavily detail page at /search/tavily with proper markdown heading, MCP Server URL block, and a 4-step "Add to your Arcade project" walkthrough. - Add `.npmrc` so contributors can install @arcadeai/* from public npm without needing GitHub Packages auth. Made-with: Cursor * 🤖 Regenerate LLMs.txt * fix(integrations): make useToolkitFilters generic to preserve docs-local fields Vercel build was failing TypeScript compilation because useToolkitFilters typed its parameter as `Toolkit[]` from @arcadeai/design-system, narrowing the array element type and discarding the `isPartner` and `docsLink` fields that ToolkitsClient needs. Make the hook (and its compareToolkits helper) generic over `T extends Toolkit` so caller-side fields flow through. No runtime change. Made-with: Cursor * Update app/en/resources/integrations/search/tavily/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update app/en/resources/integrations/search/tavily/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update app/en/resources/integrations/search/tavily/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update app/en/resources/integrations/search/tavily/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: emmithood <emmithood@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent b3c172c commit 599fc30

11 files changed

Lines changed: 179 additions & 22 deletions

File tree

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@arcadeai:registry=https://registry.npmjs.org/

app/_data/partner-toolkits.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { Toolkit } from "@arcadeai/design-system";
2+
3+
/**
4+
* Docs-local partner toolkits (remote MCP Servers offered by our partners).
5+
*
6+
* These entries are merged into the live integrations catalog alongside the
7+
* TOOLKITS exported from @arcadeai/design-system. Each partner toolkit uses
8+
* a standard ToolkitType (typically "verified") plus an `isPartner: true`
9+
* flag that renders a Partner badge next to BYOC/Pro on catalog cards.
10+
*
11+
* Once DS adds an explicit `isPartner` field to its Toolkit shape, migrate
12+
* these entries into the DS TOOLKITS array and delete this file.
13+
*/
14+
15+
export type PartnerToolkit = Toolkit & {
16+
isPartner: true;
17+
/**
18+
* Remote MCP Server URL displayed on the partner detail page. Use a
19+
* placeholder like `YOUR_API_KEY` for any per-user secrets so the URL can
20+
* be copied without leaking credentials. Updating this field updates the
21+
* detail page automatically.
22+
*/
23+
mcpUrl: string;
24+
};
25+
26+
export const PARTNER_TOOLKITS: PartnerToolkit[] = [
27+
{
28+
id: "Tavily",
29+
label: "Tavily",
30+
category: "search",
31+
publicIconUrl: "/images/partners/tavily.svg",
32+
isBYOC: true,
33+
isPro: false,
34+
isPartner: true,
35+
type: "verified",
36+
mcpUrl: "https://mcp.tavily.com/mcp/?tavilyApiKey=YOUR_API_KEY",
37+
docsLink: "https://docs.arcade.dev/en/resources/integrations/search/tavily",
38+
relativeDocsLink: "/en/resources/integrations/search/tavily",
39+
isComingSoon: false,
40+
isHidden: false,
41+
},
42+
];

app/_lib/toolkit-slug.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ export type ToolkitSlugSource = {
99
};
1010

1111
/**
12-
* Toolkit with an optional docsLink property.
13-
* The design-system `Toolkit` type doesn't include `docsLink` in its
14-
* type definitions, but some entries carry it at runtime. This type
15-
* makes the property explicit so both server and client code can share it.
12+
* Toolkit with optional `docsLink` and `isPartner` properties.
13+
* The design-system `Toolkit` type doesn't include either field, but some
14+
* docs-local entries carry them at runtime (e.g. partner toolkits that
15+
* render a Partner badge on cards). This type makes the properties explicit
16+
* so both server and client code can share it.
1617
*/
17-
export type ToolkitWithDocsLink = Toolkit & { docsLink?: string | null };
18+
export type ToolkitWithDocsLink = Toolkit & {
19+
docsLink?: string | null;
20+
isPartner?: boolean;
21+
};
1822

1923
/**
2024
* Strip all non-alphanumeric characters and lowercase.

app/en/resources/integrations/components/tool-card.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
type ToolkitType,
1010
} from "@arcadeai/design-system";
1111
import { cn } from "@arcadeai/design-system/lib/utils";
12-
import { Package } from "lucide-react";
12+
import { Handshake, Package } from "lucide-react";
1313
import Link from "next/link";
1414
import posthog from "posthog-js";
1515
import type React from "react";
@@ -26,6 +26,7 @@ type ToolCardProps = {
2626
isComingSoon?: boolean;
2727
isByoc?: boolean;
2828
isPro?: boolean;
29+
isPartner?: boolean;
2930
};
3031

3132
export const ToolCard: React.FC<ToolCardProps> = ({
@@ -37,6 +38,7 @@ export const ToolCard: React.FC<ToolCardProps> = ({
3738
isComingSoon = false,
3839
isByoc = false,
3940
isPro = false,
41+
isPartner = false,
4042
}) => {
4143
const [isModalOpen, setIsModalOpen] = useState(false);
4244
const {
@@ -45,7 +47,7 @@ export const ToolCard: React.FC<ToolCardProps> = ({
4547
icon: IconComponent,
4648
color,
4749
} = TOOL_CARD_TYPE_CONFIG[type];
48-
const showHeaderBadges = isByoc || isPro || isComingSoon;
50+
const showHeaderBadges = isByoc || isPro || isPartner || isComingSoon;
4951

5052
const trackToolCardClick = () => {
5153
posthog.capture("Tool card clicked", {
@@ -55,6 +57,7 @@ export const ToolCard: React.FC<ToolCardProps> = ({
5557
is_coming_soon: isComingSoon,
5658
is_byoc: isByoc,
5759
is_pro: isPro,
60+
is_partner: isPartner,
5861
});
5962
};
6063

@@ -131,6 +134,15 @@ export const ToolCard: React.FC<ToolCardProps> = ({
131134
)}
132135
{isByoc && <ByocBadge className="gap-1.5 rounded-md" />}
133136
{isPro && <ProBadge className="gap-1.5 rounded-md" />}
137+
{isPartner && (
138+
<Badge
139+
className="shrink-0 gap-1.5 whitespace-nowrap rounded-md border-pink-400/50 bg-pink-500/10 text-pink-700 dark:border-pink-400/50 dark:bg-pink-400/10 dark:text-pink-300"
140+
variant="outline"
141+
>
142+
<Handshake className="h-3 w-3" />
143+
Partner
144+
</Badge>
145+
)}
134146
</div>
135147
)}
136148
</div>

app/en/resources/integrations/components/toolkits-client.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
getToolkitIcon,
66
Separator,
77
} from "@arcadeai/design-system";
8+
import { Generic as GenericIcon } from "@arcadeai/design-system/components/ui/atoms/icons";
89
import { cn } from "@arcadeai/design-system/lib/utils";
910
import { Plus, Search } from "lucide-react";
1011
import Link from "next/link";
@@ -36,25 +37,27 @@ function mapToToolkitPage(
3637

3738
/**
3839
* Get toolkit icon with fallback for API toolkits.
39-
* If "GithubApi" has no icon, falls back to "Github" icon.
40+
*
41+
* `getToolkitIcon` from @arcadeai/design-system returns the Generic placeholder
42+
* (not null) when a toolkit id isn't in its icon map. For toolkits that ship
43+
* their own `publicIconUrl` (e.g. partner toolkits not yet in the DS),
44+
* we treat Generic as "no match" so the caller can fall through to `iconUrl`.
4045
*/
4146
function getToolkitIconWithFallback(
4247
toolkitId: string
4348
): React.ComponentType<React.SVGProps<SVGSVGElement>> | null {
4449
const apiSuffix = "api";
4550

46-
// Try direct match first
47-
const directIcon = getToolkitIcon(toolkitId);
48-
if (directIcon) {
49-
return directIcon;
51+
const resolved = getToolkitIcon(toolkitId);
52+
if (resolved && resolved !== GenericIcon) {
53+
return resolved;
5054
}
5155

52-
// For API toolkits, try the base provider ID
5356
const normalizedId = toolkitId.toLowerCase();
5457
if (normalizedId.endsWith(apiSuffix)) {
55-
const baseProviderId = toolkitId.slice(0, -apiSuffix.length); // Remove "Api" suffix
58+
const baseProviderId = toolkitId.slice(0, -apiSuffix.length);
5659
const baseIcon = getToolkitIcon(baseProviderId);
57-
if (baseIcon) {
60+
if (baseIcon && baseIcon !== GenericIcon) {
5861
return baseIcon;
5962
}
6063
}
@@ -175,6 +178,7 @@ export default function ToolkitsClient({ toolkits }: ToolkitsClientProps) {
175178
iconUrl={iconUrl}
176179
isByoc={toolkit.isBYOC}
177180
isComingSoon={toolkit.isComingSoon}
181+
isPartner={toolkit.isPartner}
178182
isPro={toolkit.isPro}
179183
key={toolkit.id}
180184
link={mapToToolkitPage(

app/en/resources/integrations/components/toolkits.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { TOOLKITS, type Toolkit } from "@arcadeai/design-system";
2+
import { PARTNER_TOOLKITS } from "@/app/_data/partner-toolkits";
23
import { readToolkitData } from "@/app/_lib/toolkit-data";
3-
import { normalizeToolkitId } from "@/app/_lib/toolkit-slug";
4+
import {
5+
normalizeToolkitId,
6+
type ToolkitWithDocsLink,
7+
} from "@/app/_lib/toolkit-slug";
48
import ToolkitsClient from "./toolkits-client";
59

6-
type ToolkitWithDocsLink = Toolkit & { docsLink?: string | null };
7-
810
const getToolkitDocsLink = (toolkit: Toolkit): string | undefined => {
911
if ("docsLink" in toolkit) {
1012
const value = (toolkit as ToolkitWithDocsLink).docsLink;
@@ -33,13 +35,15 @@ const getToolkitsWithDocsLinks = async (): Promise<ToolkitWithDocsLink[]> => {
3335
})
3436
);
3537

36-
return TOOLKITS.map((toolkit) => {
38+
const dsToolkits: ToolkitWithDocsLink[] = TOOLKITS.map((toolkit) => {
3739
const existing = getToolkitDocsLink(toolkit);
3840
const docsLink =
3941
existing ?? docsLinkById.get(normalizeToolkitId(toolkit.id));
4042

4143
return docsLink ? { ...toolkit, docsLink } : toolkit;
4244
});
45+
46+
return [...dsToolkits, ...PARTNER_TOOLKITS];
4347
};
4448

4549
export default async function Toolkits() {

app/en/resources/integrations/components/use-toolkit-filters.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const TYPE_LABELS: Record<ToolkitType, string> = {
2525
const getTypePriority = (type: string): number =>
2626
TYPE_PRIORITY[type as ToolkitType] ?? DEFAULT_PRIORITY;
2727

28-
const compareToolkits = (a: Toolkit, b: Toolkit): number => {
28+
const compareToolkits = <T extends Toolkit>(a: T, b: T): number => {
2929
// First prioritize available toolkits over coming soon toolkits
3030
if (a.isComingSoon !== b.isComingSoon) {
3131
return a.isComingSoon ? 1 : -1;
@@ -80,7 +80,7 @@ export const useFilterStore = create<FilterState>((set) => ({
8080
}),
8181
}));
8282

83-
export function useToolkitFilters(toolkits: Toolkit[]) {
83+
export function useToolkitFilters<T extends Toolkit>(toolkits: T[]) {
8484
const {
8585
selectedCategory,
8686
selectedType,
@@ -91,7 +91,7 @@ export function useToolkitFilters(toolkits: Toolkit[]) {
9191

9292
const debouncedSearchQuery = useDebounce(searchQuery, DEBOUNCE_TIME);
9393

94-
const filteredToolkits = useMemo(() => {
94+
const filteredToolkits = useMemo<T[]>(() => {
9595
const searchLower = debouncedSearchQuery.toLowerCase();
9696

9797
return toolkits

app/en/resources/integrations/search/_meta.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ const meta: MetaRecord = {
5353
title: "Exa API",
5454
href: "/en/resources/integrations/search/exa-api",
5555
},
56+
"-- Partner": {
57+
type: "separator",
58+
title: "Partner",
59+
},
60+
tavily: {
61+
title: "Tavily",
62+
href: "/en/resources/integrations/search/tavily",
63+
},
5664
};
5765

5866
export default meta;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
title: "Tavily"
3+
description: "Enable agents to search the web in real time and extract structured content. Available directly in Arcade as a Partner MCP Server."
4+
---
5+
6+
import { Callout, Steps } from "nextra/components";
7+
8+
# Tavily
9+
10+
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.
11+
12+
## MCP Server URL
13+
14+
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).
15+
16+
```text
17+
https://mcp.tavily.com/mcp/?tavilyApiKey=YOUR_API_KEY
18+
```
19+
20+
## What you can do
21+
22+
Once you register Tavily in your Arcade project, Arcade discovers these tools automatically:
23+
24+
| Tool | What it does |
25+
| --- | --- |
26+
| `Tavily.Search` | Real-time web search with agent-optimized ranking. |
27+
| `Tavily.Extract` | Extract structured content from specific URLs. |
28+
| `Tavily.Crawl` | Crawl a site and return content across pages. |
29+
| `Tavily.Map` | Map the structure of a site or domain. |
30+
| `Tavily.Research` | Multi-source deep research across the web. |
31+
| `Tavily.Skill` | High-level research skills built on the tools above. |
32+
33+
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.
34+
35+
## Add Tavily to your Arcade project
36+
37+
<Steps>
38+
39+
### Get your Tavily API key
40+
41+
Go to [tavily.com](https://tavily.com)**Overview****Generate MCP Link**. Copy the generated URL (it contains your API key).
42+
43+
### Add Tavily as a Remote MCP Server in Arcade
44+
45+
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.
46+
47+
<Callout type="info">
48+
See the full walkthrough in <a href="/guides/mcp-gateways/add-remote-servers">Add remote MCP servers</a> for advanced settings like connection retries, OAuth, and custom headers.
49+
</Callout>
50+
51+
### Verify Tavily tools
52+
53+
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.
54+
55+
### Create an MCP Gateway
56+
57+
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.
58+
59+
</Steps>
60+
61+
## Call Tavily tools from your agent
62+
63+
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.
64+
65+
```text
66+
https://api.arcade.dev/mcp/<YOUR-GATEWAY-SLUG>
67+
```
68+
69+
Arcade handles authorization, credential handling, and audit logging at runtime.
70+
71+
## Example
72+
73+
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.
74+
75+
## Resources
76+
77+
- [Tavily documentation](https://docs.tavily.com)
78+
- [Add remote MCP servers to Arcade](/guides/mcp-gateways/add-remote-servers)
79+
- [Create an MCP Gateway](/guides/mcp-gateways/create-via-dashboard)
80+
- [Connect to MCP clients](/get-started/mcp-clients)

public/images/partners/tavily.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)