From 29e12552b38bf193539d01f88c05794927e2ea15 Mon Sep 17 00:00:00 2001 From: Rachel Lee Nabors Date: Thu, 22 Jan 2026 18:06:12 +0000 Subject: [PATCH 01/19] Rewrite Mastra tutorial as single-page guide with agent and workflow - Consolidate multi-page tutorial into single comprehensive page - Add tool configuration with MCP servers and individual tools - Include output truncation to prevent token overflow - Add email digest workflow example - Update Steps component CSS to not number nested h4-h6 headings - Add callout linking to multi-user auth guide - Update redirects to point old pages to new single page Co-Authored-By: Claude Opus 4.5 --- app/en/get-started/agent-frameworks/_meta.tsx | 2 +- .../agent-frameworks/mastra/_meta.tsx | 22 - .../agent-frameworks/mastra/overview/page.mdx | 33 - .../agent-frameworks/mastra/page.mdx | 823 ++++++++++++++++++ .../mastra/use-arcade-tools/page.mdx | 161 ---- .../mastra/user-auth-interrupts/page.mdx | 153 ---- app/globals.css | 14 + next.config.ts | 30 +- 8 files changed, 860 insertions(+), 378 deletions(-) delete mode 100644 app/en/get-started/agent-frameworks/mastra/_meta.tsx delete mode 100644 app/en/get-started/agent-frameworks/mastra/overview/page.mdx create mode 100644 app/en/get-started/agent-frameworks/mastra/page.mdx delete mode 100644 app/en/get-started/agent-frameworks/mastra/use-arcade-tools/page.mdx delete mode 100644 app/en/get-started/agent-frameworks/mastra/user-auth-interrupts/page.mdx diff --git a/app/en/get-started/agent-frameworks/_meta.tsx b/app/en/get-started/agent-frameworks/_meta.tsx index 3ebbaae3a..6ececb277 100644 --- a/app/en/get-started/agent-frameworks/_meta.tsx +++ b/app/en/get-started/agent-frameworks/_meta.tsx @@ -23,7 +23,7 @@ export const meta: MetaRecord = { title: "OpenAI Agents", }, vercelai: { - title: "Vercel AI", + title: "Vercel AI SDK", }, }; diff --git a/app/en/get-started/agent-frameworks/mastra/_meta.tsx b/app/en/get-started/agent-frameworks/mastra/_meta.tsx deleted file mode 100644 index 9c7bbf1eb..000000000 --- a/app/en/get-started/agent-frameworks/mastra/_meta.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { MetaRecord } from "nextra"; - -const meta: MetaRecord = { - "*": { - theme: { - breadcrumb: true, - toc: true, - copyPage: true, - }, - }, - overview: { - title: "Overview", - }, - "use-arcade-tools": { - title: "Using Arcade tools", - }, - "user-auth-interrupts": { - title: "Managing user authorization", - }, -}; - -export default meta; diff --git a/app/en/get-started/agent-frameworks/mastra/overview/page.mdx b/app/en/get-started/agent-frameworks/mastra/overview/page.mdx deleted file mode 100644 index 53dd45c2a..000000000 --- a/app/en/get-started/agent-frameworks/mastra/overview/page.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: "Integrating Arcade with Mastra" -description: "Leverage Arcade's tool ecosystem within your Mastra applications." ---- - -import { Callout } from "nextra/components"; - -## Overview: Arcade Tools in Mastra - -[Mastra](https://mastra.ai/docs) is an open-source TypeScript agent framework that provides essential primitives for building AI applications. Integrate Arcade's extensive tool ecosystem to enhance your Mastra agents and enable them to interact seamlessly with numerous third-party services. - -This integration enables you to: - -- **Access a wide range of tools:** Use Arcade's [pre-built tools](/resources/integrations) for GitHub, Google Workspace, Slack, and more directly within your Mastra agent. -- **Simplify tool management:** Let Arcade handle the complexities of tool discovery, execution, and authentication. -- **Build sophisticated agents:** Combine Mastra's agent framework (including memory, workflows, and RAG) with Arcade's powerful tool capabilities. - -### How it Works - -The integration works through three key mechanisms: - -1. **Tool Discovery:** Access available tools through a unified API (`arcade.tools.list`). -2. **Schema Conversion:** Transform Arcade's tool definitions into Mastra-compatible Zod schemas with the `toZodToolSet` utility, enabling seamless integration between the two frameworks without manual schema mapping. -3. **Execution Delegation:** Seamlessly route tool calls from your Mastra agent through Arcade's API, which handles all the complexities of third-party service authentication and execution. - - - Before starting, obtain an [Arcade API key](/get-started/setup/api-keys). - - -### Next Steps - -- Learn how to [use Arcade tools](/get-started/agent-frameworks/mastra/use-arcade-tools) in a Mastra agent -- Implement [user authentication handling](/get-started/agent-frameworks/mastra/user-auth-interrupts) for tools in multi-user applications diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx new file mode 100644 index 000000000..bb9a069e5 --- /dev/null +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -0,0 +1,823 @@ +--- +title: "Build an AI Agent and Workflow with Arcade and Mastra" +description: "Create a TypeScript agent that uses Arcade tools to access Gmail and Slack" +--- + +import { Steps, Tabs, Callout } from "nextra/components"; + +[Mastra](https://mastra.ai/docs) is an open-source, TypeScript agent framework for building AI applications. It provides agents with memory, tool calling, workflows, and RAG capabilities. This guide uses **Mastra v1.x**. + +In this guide, you'll build an agent lets you read emails, send messages, and interact with Gmail and Slack using Arcade's MCP tools in a conversational interface with built-in authentication. You will also build a workflow that summarizes emails and sends them to Slack. + + + + +A Mastra agent and workflow that integrates Arcade tools for Gmail and Slack. + + + + + +- +- [Node.js 18+](https://nodejs.org/) +- An [OpenAI API key](https://platform.openai.com/api-keys) (or another supported model provider) + + + + + +- How to retrieve Arcade tools and convert them to Mastra format +- How to create an agent with tool calling capabilities +- How to create a workflow with multiple steps +- How to handle Arcade's authorization flow in your application +- How to test your agent and workflow with Mastra Studio + + + + +## Mastra concepts + +Before diving into the code, here are the key Mastra concepts you'll use: + +- [Mastra Studio](https://mastra.ai/docs/getting-started/studio): An interactive development environment for building and testing agents locally. +- [Zod schemas](https://zod.dev): Mastra uses Zod for type-safe tool definitions. +- [Memory](https://mastra.ai/docs/memory/overview): Persists conversation history across sessions using storage backends like LibSQL. +- [Processors](https://mastra.ai/docs/memory/processors): Transform messages before they reach the LLM. This tutorial uses: + - `ToolCallFilter`: Removes tool calls and results from memory to prevent large API responses from bloating context. + - `TokenLimiterProcessor`: Limits input tokens to stay within model context limits. + +## Build the agent + + + +### Create a new Mastra project + +```bash +npx create-mastra@latest arcade-agent +``` + +Select your preferred model provider when prompted (OpenAI is recommended). Enter your API key when asked. + +Then navigate to the project directory and install the Arcade client: + + + + + +```bash +cd arcade-agent +npm install @arcadeai/arcadejs @ai-sdk/openai zod@3 +``` + + + + + +```bash +cd arcade-agent +pnpm add @arcadeai/arcadejs @ai-sdk/openai zod@3 +``` + + + + + +```bash +cd arcade-agent +yarn add @arcadeai/arcadejs @ai-sdk/openai zod@3 +``` + + + + + + + We explicitly install `zod@3` because the Arcade SDK's `toZodToolSet` currently requires Zod 3.x. Zod 4 has a different internal API that isn't yet supported. + + +### Set up environment variables + +Add your Arcade API key to **.env**: + +```env filename=".env" +ARCADE_API_KEY={arcade_api_key} +ARCADE_USER_ID={arcade_user_id} +``` + +The `ARCADE_USER_ID` is your app's internal identifier for the user (often the email you signed up with, a UUID, etc.). Arcade uses this to track authorizations per user. + +### Create the tool configuration + +Create **src/mastra/tools/arcade.ts** to handle Arcade tool fetching and conversion. + + + **Handling large tool outputs:** Tools like `Gmail.ListEmails` can return 200KB+ of email content. When this data is passed back to the LLM in the agentic loop, it can exceed token limits and cause rate limit errors. The code below includes output truncation to prevent this. + + +```ts filename="src/mastra/tools/arcade.ts" +import { Arcade } from "@arcadeai/arcadejs"; +import { + toZodToolSet, + executeOrAuthorizeZodTool, +} from "@arcadeai/arcadejs/lib"; + +const config = { + // Get all tools from these MCP servers + mcpServers: ["Slack"], + // Add specific individual tools + individualTools: ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"], +}; + +// Maximum characters for any string field in tool output +// Keeps responses small while preserving structure (subjects, senders, snippets) +const MAX_STRING_CHARS = 300; + +/** + * Recursively truncates all large strings in objects/arrays. + * This prevents token overflow when tool results are passed back to the LLM. + */ +function truncateDeep(obj: unknown): unknown { + if (obj === null || obj === undefined) return obj; + + if (typeof obj === "string") { + if (obj.length > MAX_STRING_CHARS) { + return obj.slice(0, MAX_STRING_CHARS) + "..."; + } + return obj; + } + + if (Array.isArray(obj)) { + return obj.map(truncateDeep); + } + + if (typeof obj === "object") { + const result: Record = {}; + for (const [key, value] of Object.entries(obj as Record)) { + result[key] = truncateDeep(value); + } + return result; + } + + return obj; +} + +export async function getArcadeTools(userId: string) { + const arcade = new Arcade(); + + // Fetch tools from MCP servers + const mcpTools = await Promise.all( + config.mcpServers.map(async (server) => { + const response = await arcade.tools.list({ toolkit: server }); + return response.items; + }) + ); + + // Fetch individual tools + const individualTools = await Promise.all( + config.individualTools.map((toolName) => arcade.tools.get(toolName)) + ); + + // Combine all tools + const allTools = [...mcpTools.flat(), ...individualTools]; + + // Convert to Zod format for Mastra compatibility + const zodTools = toZodToolSet({ + tools: allTools, + client: arcade, + userId, + executeFactory: executeOrAuthorizeZodTool, + }); + + // Wrap tools with truncation and add 'id' property for Mastra Studio + type ToolType = (typeof zodTools)[string] & { id: string }; + const mastraTools: Record = {}; + + for (const [toolName, tool] of Object.entries(zodTools)) { + const originalExecute = tool.execute; + mastraTools[toolName] = { + ...tool, + id: toolName, + execute: async (input: unknown) => { + const result = await originalExecute(input); + return truncateDeep(result) as Awaited>; + }, + } as ToolType; + } + + return mastraTools; +} +``` + +#### What this code does + +##### Tool fetching + +- `mcpServers`: Fetches *all* tools from an MCP server. Use this when you want everything a service offers (e.g., `"Slack"` gives you `Slack_SendMessage`, `Slack_ListChannels`, `Slack_ListUsers`, etc.) +- `individualTools`: Fetches specific tools by name. Use this to cherry-pick only what you need (e.g., `"Gmail_ListEmails"` without `Gmail_DeleteEmail` or other tools you don't want exposed) + +There are a few reasons you might want to select your tools individually. + +* **Security** You may not want to expose all the tools a service offers, for instance `Gmail_DeleteEmail` is not necessary and could even be dangerous to expose to an agent designed to summarize emails. +* **Cost** Each tool's schema consumes tokens. Loading all Gmail tools (~20 tools) uses significantly more tokens than loading just the 3 you need. This matters for rate limits and cost. + + + Browse the [complete MCP server catalog](/resources/integrations) to see available servers and their tools. + + +##### Arcade SDK functions + +- `arcade.tools.list({ toolkit })`: Fetches all tools from an MCP server +- `arcade.tools.get(toolName)`: Fetches a single tool by its full name +- `toZodToolSet`: Converts Arcade tools to [Zod](https://zod.dev) schemas that Mastra requires +- `executeOrAuthorizeZodTool`: Handles tool execution and returns authorization URLs when needed + +### Output handling + +- `truncateDeep`: Recursively limits all strings to 300 characters to prevent token overflow when tool results are passed back to the LLM + +### Create the agent + +Create **src/mastra/agents/arcade.ts**: + +```ts filename="src/mastra/agents/arcade.ts" +import { Agent } from "@mastra/core/agent"; +import { TokenLimiterProcessor, ToolCallFilter } from "@mastra/core/processors"; +import { Memory } from "@mastra/memory"; +import { LibSQLStore } from "@mastra/libsql"; +import { openai } from "@ai-sdk/openai"; +import { getArcadeTools } from "../tools/arcade"; + +const userId = process.env.ARCADE_USER_ID || "default-user"; + +// Fetch Arcade tools at startup +const arcadeTools = await getArcadeTools(userId); + +// Configure memory with conversation history +const memory = new Memory({ + storage: new LibSQLStore({ + id: "arcade-agent-memory", + url: "file:memory.db", + }), + options: { + lastMessages: 10, + }, +}); + +export const arcadeAgent = new Agent({ + id: "arcade-agent", + name: "arcadeAgent", + instructions: `You are a helpful assistant that can access Gmail and Slack. +Always use the available tools to fulfill user requests. + +For Gmail: +- Use Gmail_ListEmails to fetch recent emails +- Use Gmail_SendEmail to send emails +- Use Gmail_WhoAmI to get the user's email address +- To find sent emails, use the query parameter with "in:sent" +- To find received emails, use "in:inbox" or no query +- When composing emails, use plain text (no markdown) + +For Slack: +- Use Slack_SendMessage to send messages to channels or users +- Use Slack_ListChannels to see available channels + +After completing any action, always confirm what you did with specific details. + +IMPORTANT: When a tool returns an authorization response with a URL, tell the user to visit that URL to grant access. After they authorize, they can retry their request.`, + model: openai("gpt-4o"), + tools: arcadeTools, + memory, + // Filter out tool results from memory (they can be huge) and limit tokens + inputProcessors: [new ToolCallFilter(), new TokenLimiterProcessor({ limit: 50000 })], +}); +``` + +### Register the agent + +Replace the contents of **src/mastra/index.ts** with the following to register your agent: + +```ts filename="src/mastra/index.ts" +import { Mastra } from "@mastra/core"; +import { arcadeAgent } from "./agents/arcade"; + +export const mastra = new Mastra({ + agents: { + arcadeAgent, + }, +}); +``` + +### Test with Mastra Studio + +Start the development server: + + + + + +```bash +npm run dev +``` + + + + + +```bash +pnpm dev +``` + + + + + +```bash +yarn dev +``` + + + + + +Open [http://localhost:4111](http://localhost:4111) to access Mastra Studio. Select **arcadeAgent** from the list and try prompts like: + +- "Summarize my last 3 emails" +- "Send a Slack DM to myself saying hello" +- "What's my Gmail address?" + +On first use, the agent will return an authorization URL. Visit the URL to connect your Gmail or Slack account, then retry your request. Arcade remembers this authorization for future requests. + + + +## Build a workflow + +Agents are great for open-ended conversations, but sometimes you want a **deterministic process** that runs the same way every time. Mastra workflows let you chain steps together, with each step's output feeding into the next. + +Let's build an email digest workflow that: +1. Fetches emails from Gmail +2. Summarizes them with an LLM +3. Sends the digest to Slack + +This also demonstrates how workflows handle large data: the full email content stays internal to the workflow—only the compact summary gets sent to Slack. + + + +### Create the workflow + +Create **src/mastra/workflows/email-digest.ts**: + +```ts filename="src/mastra/workflows/email-digest.ts" +import { createStep, createWorkflow } from "@mastra/core/workflows"; +import { z } from "zod"; +import { Arcade } from "@arcadeai/arcadejs"; + +// Step 1: Fetch emails from Gmail +const fetchEmails = createStep({ + id: "fetch-emails", + inputSchema: z.object({ + userId: z.string(), + maxEmails: z.number().optional(), + }), + outputSchema: z.object({ + emails: z.array(z.object({ + subject: z.string(), + from: z.string(), + snippet: z.string(), + })), + userId: z.string(), + }), + execute: async ({ inputData }) => { + const arcade = new Arcade(); + const result = await arcade.tools.execute({ + tool_name: "Gmail.ListEmails", + user_id: inputData!.userId, + input: { n_emails: inputData!.maxEmails ?? 5 }, + }); + + const response = result as { output?: { value?: { emails?: any[] } } }; + const emails = (response.output?.value?.emails || []).map((e: any) => ({ + subject: String(e.subject || "(No subject)"), + from: String(e.sender || e.from || "Unknown"), + snippet: String(e.snippet || "").slice(0, 200), + })); + + return { emails, userId: inputData!.userId }; + }, +}); + +// Step 2: Summarize with LLM +const summarizeEmails = createStep({ + id: "summarize-emails", + inputSchema: z.object({ + emails: z.array(z.object({ + subject: z.string(), + from: z.string(), + snippet: z.string(), + })), + userId: z.string(), + }), + outputSchema: z.object({ + summary: z.string(), + userId: z.string(), + }), + execute: async ({ inputData, mastra }) => { + const { emails, userId } = inputData!; + + if (emails.length === 0) { + return { summary: "No new emails.", userId }; + } + + const agent = mastra?.getAgent("arcadeAgent"); + const emailList = emails.map((e, i) => + `${i + 1}. From: ${e.from}\n Subject: ${e.subject}\n Preview: ${e.snippet}` + ).join("\n\n"); + + const response = await agent!.generate( + `Summarize these emails in 2-3 bullet points:\n\n${emailList}` + ); + + return { summary: response.text, userId }; + }, +}); + +// Step 3: Send to Slack +const sendToSlack = createStep({ + id: "send-to-slack", + inputSchema: z.object({ + summary: z.string(), + userId: z.string(), + }), + outputSchema: z.object({ + success: z.boolean(), + message: z.string(), + }), + execute: async ({ inputData }) => { + const arcade = new Arcade(); + const { summary, userId } = inputData!; + + await arcade.tools.execute({ + tool_name: "Slack.SendMessage", + user_id: userId, + input: { + message: `📬 *Email Digest*\n\n${summary}`, + channel_name: "general", + }, + }); + + return { success: true, message: "Digest sent to Slack" }; + }, +}); + +// Chain the steps together +const emailDigestWorkflow = createWorkflow({ + id: "email-digest", + inputSchema: z.object({ + userId: z.string(), + maxEmails: z.number().optional(), + }), + outputSchema: z.object({ + success: z.boolean(), + message: z.string(), + }), +}) + .then(fetchEmails) + .then(summarizeEmails) + .then(sendToSlack); + +emailDigestWorkflow.commit(); + +export { emailDigestWorkflow }; +``` + +### Register the workflow + +Update **src/mastra/index.ts**: + +```ts filename="src/mastra/index.ts" +import { Mastra } from "@mastra/core"; +import { arcadeAgent } from "./agents/arcade"; +import { emailDigestWorkflow } from "./workflows/email-digest"; + +export const mastra = new Mastra({ + agents: { + arcadeAgent, + }, + workflows: { + emailDigestWorkflow, + }, +}); +``` + +### Test the workflow + +Restart the dev server and open Mastra Studio. Under **Workflows** in the sidebar, you'll see **emailDigestWorkflow**. Run it with your user ID to fetch emails, summarize them, and send the digest to Slack. + + + +### Agent vs Workflow + +The agent handles open-ended requests ("help me with my emails"). The workflow handles repeatable processes ("every morning, summarize and send to Slack"). Use both together for powerful automation. + +## Key takeaways + +- **Arcade tools work seamlessly with Mastra**: Use `toZodToolSet` to convert Arcade tools to the Zod schema format Mastra expects. +- **Truncate large outputs**: Tools like Gmail can return 200KB+ of data. Wrap tool execution with truncation to prevent token overflow in the agentic loop. +- **Authorization is automatic**: The `executeOrAuthorizeZodTool` factory handles auth flows. When a tool needs authorization, it returns a URL for the user to visit. +- **Use agents for conversation, workflows for automation**: Agents handle open-ended requests; workflows handle repeatable, deterministic processes. + +## Next steps + +- **Add more tools**: Browse the [tool catalog](/resources/integrations) and add tools for GitHub, Notion, Linear, and more. +- **Schedule your workflow**: Use a cron job or [Mastra's scheduling](https://mastra.ai/docs/workflows/scheduling) to run your email digest every morning. +- **Deploy to production**: Follow Mastra's [deployment guides](https://mastra.ai/docs/deployment/overview) to deploy your agent and workflows. + + + **Building a multi-user app?** This tutorial uses a single `ARCADE_USER_ID` for simplicity. For production apps where each user needs their own OAuth tokens, see [Secure auth for production](/guides/user-facing-agents/secure-auth-production) to learn how to dynamically pass user IDs and handle per-user authorization. + + +## Complete code + +
+**src/mastra/tools/arcade.ts** (full file) + +```ts filename="src/mastra/tools/arcade.ts" +import { Arcade } from "@arcadeai/arcadejs"; +import { + toZodToolSet, + executeOrAuthorizeZodTool, +} from "@arcadeai/arcadejs/lib"; + +const config = { + mcpServers: ["Slack"], + individualTools: ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"], +}; + +const MAX_STRING_CHARS = 300; + +function truncateDeep(obj: unknown): unknown { + if (obj === null || obj === undefined) return obj; + + if (typeof obj === "string") { + if (obj.length > MAX_STRING_CHARS) { + return obj.slice(0, MAX_STRING_CHARS) + "..."; + } + return obj; + } + + if (Array.isArray(obj)) { + return obj.map(truncateDeep); + } + + if (typeof obj === "object") { + const result: Record = {}; + for (const [key, value] of Object.entries(obj as Record)) { + result[key] = truncateDeep(value); + } + return result; + } + + return obj; +} + +export async function getArcadeTools(userId: string) { + const arcade = new Arcade(); + + const mcpTools = await Promise.all( + config.mcpServers.map(async (server) => { + const response = await arcade.tools.list({ toolkit: server }); + return response.items; + }) + ); + + const individualTools = await Promise.all( + config.individualTools.map((toolName) => arcade.tools.get(toolName)) + ); + + const allTools = [...mcpTools.flat(), ...individualTools]; + + const zodTools = toZodToolSet({ + tools: allTools, + client: arcade, + userId, + executeFactory: executeOrAuthorizeZodTool, + }); + + type ToolType = (typeof zodTools)[string] & { id: string }; + const mastraTools: Record = {}; + + for (const [toolName, tool] of Object.entries(zodTools)) { + const originalExecute = tool.execute; + mastraTools[toolName] = { + ...tool, + id: toolName, + execute: async (input: unknown) => { + const result = await originalExecute(input); + return truncateDeep(result) as Awaited>; + }, + } as ToolType; + } + + return mastraTools; +} +``` + +
+ +
+**src/mastra/agents/arcade.ts** (full file) + +```ts filename="src/mastra/agents/arcade.ts" +import { Agent } from "@mastra/core/agent"; +import { TokenLimiterProcessor, ToolCallFilter } from "@mastra/core/processors"; +import { Memory } from "@mastra/memory"; +import { LibSQLStore } from "@mastra/libsql"; +import { openai } from "@ai-sdk/openai"; +import { getArcadeTools } from "../tools/arcade"; + +const userId = process.env.ARCADE_USER_ID || "default-user"; +const arcadeTools = await getArcadeTools(userId); + +const memory = new Memory({ + storage: new LibSQLStore({ + id: "arcade-agent-memory", + url: "file:memory.db", + }), + options: { + lastMessages: 10, + }, +}); + +export const arcadeAgent = new Agent({ + id: "arcade-agent", + name: "arcadeAgent", + instructions: `You are a helpful assistant that can access Gmail and Slack. +Always use the available tools to fulfill user requests. + +For Gmail: +- Use Gmail_ListEmails to fetch recent emails +- Use Gmail_SendEmail to send emails +- Use Gmail_WhoAmI to get the user's email address +- To find sent emails, use the query parameter with "in:sent" +- To find received emails, use "in:inbox" or no query +- When composing emails, use plain text (no markdown) + +For Slack: +- Use Slack_SendMessage to send messages to channels or users +- Use Slack_ListChannels to see available channels + +After completing any action, always confirm what you did with specific details. + +IMPORTANT: When a tool returns an authorization response with a URL, tell the user to visit that URL to grant access. After they authorize, they can retry their request.`, + model: openai("gpt-4o"), + tools: arcadeTools, + memory, + // Filter out tool results from memory (they can be huge) and limit tokens + inputProcessors: [new ToolCallFilter(), new TokenLimiterProcessor({ limit: 50000 })], +}); +``` + +
+ +
+**src/mastra/index.ts** (full file) + +```ts filename="src/mastra/index.ts" +import { Mastra } from "@mastra/core"; +import { arcadeAgent } from "./agents/arcade"; +import { emailDigestWorkflow } from "./workflows/email-digest"; + +export const mastra = new Mastra({ + agents: { + arcadeAgent, + }, + workflows: { + emailDigestWorkflow, + }, +}); +``` + +
+ +
+**src/mastra/workflows/email-digest.ts** (full file) + +```ts filename="src/mastra/workflows/email-digest.ts" +import { createStep, createWorkflow } from "@mastra/core/workflows"; +import { z } from "zod"; +import { Arcade } from "@arcadeai/arcadejs"; + +const fetchEmails = createStep({ + id: "fetch-emails", + inputSchema: z.object({ + userId: z.string(), + maxEmails: z.number().optional(), + }), + outputSchema: z.object({ + emails: z.array(z.object({ + subject: z.string(), + from: z.string(), + snippet: z.string(), + })), + userId: z.string(), + }), + execute: async ({ inputData }) => { + const arcade = new Arcade(); + const result = await arcade.tools.execute({ + tool_name: "Gmail.ListEmails", + user_id: inputData!.userId, + input: { n_emails: inputData!.maxEmails ?? 5 }, + }); + + const response = result as { output?: { value?: { emails?: any[] } } }; + const emails = (response.output?.value?.emails || []).map((e: any) => ({ + subject: String(e.subject || "(No subject)"), + from: String(e.sender || e.from || "Unknown"), + snippet: String(e.snippet || "").slice(0, 200), + })); + + return { emails, userId: inputData!.userId }; + }, +}); + +const summarizeEmails = createStep({ + id: "summarize-emails", + inputSchema: z.object({ + emails: z.array(z.object({ + subject: z.string(), + from: z.string(), + snippet: z.string(), + })), + userId: z.string(), + }), + outputSchema: z.object({ + summary: z.string(), + userId: z.string(), + }), + execute: async ({ inputData, mastra }) => { + const { emails, userId } = inputData!; + + if (emails.length === 0) { + return { summary: "No new emails.", userId }; + } + + const agent = mastra?.getAgent("arcadeAgent"); + const emailList = emails.map((e, i) => + `${i + 1}. From: ${e.from}\n Subject: ${e.subject}\n Preview: ${e.snippet}` + ).join("\n\n"); + + const response = await agent!.generate( + `Summarize these emails in 2-3 bullet points:\n\n${emailList}` + ); + + return { summary: response.text, userId }; + }, +}); + +const sendToSlack = createStep({ + id: "send-to-slack", + inputSchema: z.object({ + summary: z.string(), + userId: z.string(), + }), + outputSchema: z.object({ + success: z.boolean(), + message: z.string(), + }), + execute: async ({ inputData }) => { + const arcade = new Arcade(); + const { summary, userId } = inputData!; + + await arcade.tools.execute({ + tool_name: "Slack.SendMessage", + user_id: userId, + input: { + message: `📬 *Email Digest*\n\n${summary}`, + channel_name: "general", + }, + }); + + return { success: true, message: "Digest sent to Slack" }; + }, +}); + +const emailDigestWorkflow = createWorkflow({ + id: "email-digest", + inputSchema: z.object({ + userId: z.string(), + maxEmails: z.number().optional(), + }), + outputSchema: z.object({ + success: z.boolean(), + message: z.string(), + }), +}) + .then(fetchEmails) + .then(summarizeEmails) + .then(sendToSlack); + +emailDigestWorkflow.commit(); + +export { emailDigestWorkflow }; +``` + +
diff --git a/app/en/get-started/agent-frameworks/mastra/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/mastra/use-arcade-tools/page.mdx deleted file mode 100644 index ff85034af..000000000 --- a/app/en/get-started/agent-frameworks/mastra/use-arcade-tools/page.mdx +++ /dev/null @@ -1,161 +0,0 @@ ---- -title: "Using Arcade tools with Mastra" -description: "Integrate Arcade tools into your Mastra applications for basic use cases." ---- - -import { Steps, Tabs, Callout } from "nextra/components"; - -This guide shows you how to integrate and use Arcade tools within a Mastra agent. For the complete working example, check out our [GitHub repository](https://github.com/ArcadeAI/arcade-ai/tree/main/examples/mastra). - - - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys) -- Basic familiarity with TypeScript and Mastra concepts - -### Create a Mastra project - -Start by creating a new Mastra project using the official CLI: - -```bash -# Create a new Mastra project -npx create-mastra@latest my-arcade-agent - -# Navigate to the project -cd my-arcade-agent -``` - -For more details on setting up a Mastra project, refer to the [Mastra documentation](https://mastra.ai/docs/getting-started/installation). - -### Install Arcade client - -Install the Arcade client: - - - - - -```bash -pnpm add @arcadeai/arcadejs -``` - - - - - -```bash -npm install @arcadeai/arcadejs -``` - - - - - -```bash -yarn install @arcadeai/arcadejs -``` - - - - - -### Configure API keys - -Set up your environment with the required API keys: - -```typescript -// Set your API keys in your environment variables or .env file -process.env.ARCADE_API_KEY = "your_arcade_api_key"; -process.env.ANTHROPIC_API_KEY = "your_anthropic_api_key"; // or another supported model provider -``` - -### Convert Arcade tools to Mastra tools - -Arcade offers methods to convert tools into Zod schemas, which is essential since Mastra defines tools using Zod. The `toZodToolSet` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with Mastra. Learn more about Arcade's Zod integration options [here](/guides/tool-calling/custom-apps/get-tool-definitions#get-zod-tool-definitions). - -```ts -import { Arcade } from "@arcadeai/arcadejs"; -import { - executeOrAuthorizeZodTool, - toZodToolSet, -} from "@arcadeai/arcadejs/lib"; - -// Initialize Arcade -const arcade = new Arcade(); - -// Get Gmail MCP Server -// MCP Server names can be found in the Arcade dashboard via Tools > view > MCP Server or via the CLI `arcade workers list` -const gmailToolkit = await arcade.tools.list({ toolkit: "Gmail", limit: 30 }); - -// Get Gmail tools -export const gmailTools = toZodToolSet({ - tools: gmailToolkit.items, - client: arcade, - userId: "", // Your app's internal ID for the user (an email, UUID, etc). It's used internally to identify your user in Arcade - executeFactory: executeOrAuthorizeZodTool, // Checks if tool is authorized and executes it, or returns authorization URL if needed -}); - -``` - -### Create and configure your Mastra agent - -Now create a Mastra agent that uses Arcade tools: - -```typescript -import { Agent } from "@mastra/core/agent"; -import { anthropic } from "@ai-sdk/anthropic"; - -// Create the Mastra agent with Arcade tools -export const gmailAgent = new Agent({ - name: "gmailAgent", - instructions: `You are a Gmail assistant that helps users manage their inbox. - -When helping users: -- Always verify their intent before performing actions -- Keep responses clear and concise -- Confirm important actions before executing them -- Respect user privacy and data security - -Use the gmailTools to interact with various Gmail services and perform related tasks.`, - model: anthropic("claude-3-7-sonnet-20250219"), - tools: gmailTools, -}); -``` - -### Interact with your agent - -You can interact with your agent in two main ways: - -**1. Using the Mastra Development Playground:** - -Start the Mastra development server: - -```bash -npm run dev -``` - -This will launch a local development playground, typically accessible at `http://localhost:4111`. Open this URL in your browser, select the `gmailAgent` from the list of available agents, and start chatting with it directly in the UI. - -**2. Programmatically:** - -Alternatively, you can interact with the agent directly in your code: - -```typescript -// Generate a response from the agent -const response = await gmailAgent.generate( - "Read my last email and summarize it in a few sentences", -); -console.log(response.text); - -// Or stream the response for a more interactive experience -const stream = await gmailAgent.stream("Send an email to dev@arcade.dev with the subject 'Hello from Mastra'"); - -for await (const chunk of stream.textStream) { - process.stdout.write(chunk); -} -``` - - - -When running your agent for the first time with tools that require user consent (like Google or Github), the agent will return an authorization reponse (for example, `{ authorization_required: true, url: '...', message: '...' }`). Your agent's instructions should guide it to present this URL to the user. After the user visits this URL and grants permissions, the tool can be used successfully. See the [Managing user authorization](/get-started/agent-frameworks/mastra/user-auth-interrupts) guide for more details on handling authentication flows. diff --git a/app/en/get-started/agent-frameworks/mastra/user-auth-interrupts/page.mdx b/app/en/get-started/agent-frameworks/mastra/user-auth-interrupts/page.mdx deleted file mode 100644 index eea1f31dc..000000000 --- a/app/en/get-started/agent-frameworks/mastra/user-auth-interrupts/page.mdx +++ /dev/null @@ -1,153 +0,0 @@ ---- -title: "Managing user authorization" -description: "Handle user-specific authorization for Arcade tools in Mastra applications." ---- - -import { Callout } from "nextra/components"; - -## Dynamic Tool Loading with Toolsets - -Mastra lets you dynamically provide tools to an agent at runtime using toolsets. This approach is essential when integrating Arcade tools in web applications where each user needs their own authentication flow. - -### Per-User Tool Authentication in Web Applications - -In web applications serving multiple users, implement user-specific authentication flows with these steps: - -First, set up your Mastra configuration and agents in separate files: - -```typescript -// @/mastra/index.ts -import { Mastra } from "@mastra/core"; -import { githubAgent } from "./agents/githubAgent"; - -// Initialize Mastra -export const mastra = new Mastra({ - agents: { - githubAgent, - }, -}); -``` - -```typescript -// @/mastra/agents/githubAgent.ts -import { Agent } from "@mastra/core/agent"; -import { anthropic } from "@ai-sdk/anthropic"; - -// Create the agent without tools - we'll add them at runtime -export const githubAgent = new Agent({ - name: "githubAgent", - instructions: `You are a GitHub Agent that helps with repository management. - - You can help with tasks like: - - Listing repositories - - Creating and managing issues - - Viewing pull requests - - Managing repository settings - - If a tool requires authorization, you will receive an authorization URL. - When that happens, clearly present this URL to the user and ask them to visit it to grant permissions.`, - model: anthropic("claude-3-7-sonnet-20250219"), - // No tools defined here - will be provided dynamically at runtime -}); -``` - -Then, create an API endpoint that provides tools dynamically: - -```typescript -// app/api/chat/route.ts -import { NextRequest, NextResponse } from "next/server"; -import { mastra } from "@/mastra"; -import { Arcade } from "@arcadeai/arcadejs"; -import { getUserSession } from "@/lib/auth"; // Your authentication handling -import { toZodToolSet } from "@arcadeai/arcadejs/lib"; -import { executeOrAuthorizeZodTool } from "@arcadeai/arcadejs/lib"; - -export async function POST(req: NextRequest) { - // Extract request data - const { messages, threadId } = await req.json(); - - // Authenticate the user - const session = await getUserSession(req); - if (!session) { - return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); - } - - try { - // Get the agent from Mastra - const githubAgent = mastra.getAgent("githubAgent"); - - const arcade = new Arcade(); - const githubToolkit = await arcade.tools.list({ toolkit: "github", limit: 30 }); - - // Fetch user-specific Arcade tools for GitHub - const arcadeTools = toZodToolSet({ - tools: githubToolkit.items, - client: arcade, - userId: session.user.email, - executeFactory: executeOrAuthorizeZodTool, - }); - - // Stream the response with dynamically provided tools - const response = await githubAgent.stream(messages, { - threadId, // Optional: For maintaining conversation context - resourceId: session.user.id, // Optional: For associating memory with user - toolChoice: "auto", - toolsets: { - arcade: arcadeTools, // Provide tools in a named toolset - }, - }); - - // Return streaming response - return response.toDataStreamResponse(); - } catch (error) { - console.error("Error processing GitHub request:", error); - return NextResponse.json( - { message: "Failed to process request" }, - { status: 500 }, - ); - } -} -``` - -This approach provides several benefits: - -- Each user gets their own separate authentication flow with Arcade tools -- A single agent instance works with multiple user-specific toolsets - -The toolsets parameter provides tools only for the current request without modifying the agent's base configuration. This makes it ideal for multi-user applications where each user needs their own secure OAuth flow with Arcade. - -## Handling Tool Authorization - -When a tool requires user authorization, the agent receives a response with: - -```typescript -{ - authorization_required: true, - url: "https://auth.arcade.com/...", - message: "Forward this url to the user for authorization" -} -``` - -Your agent should recognize this pattern and present the URL to the user. To create a better user experience: - -- Display the authorization URL as a clickable link in your UI -- Explain which service needs authorization and why -- Provide a way for users to retry their request after authorization - -## Tips for Selecting Tools - -- **Focus on relevance**: Choose tools that directly support your agent's specific purpose -- **Consider performance**: Some tools may have higher latency than others -- **Handle errors gracefully**: Implement robust error handling for third-party service failures -- **Create clear user flows**: Design intuitive authorization experiences - -## Next Steps - -After integrating Arcade tools into your Mastra agent, you can: - -- Add [memory capabilities](https://mastra.ai/docs/agents/agent-memory) to maintain context between interactions -- Implement [structured workflows](https://mastra.ai/docs/workflows/overview) for complex multi-step operations -- Enhance your agent with [RAG capabilities](https://mastra.ai/docs/rag/overview) for domain-specific knowledge -- Set up [logging and tracing](https://mastra.ai/docs/observability/logging) to monitor performance - -For more detailed information on Mastra's capabilities, visit the [Mastra documentation](https://mastra.ai/docs). diff --git a/app/globals.css b/app/globals.css index fa510d573..830563d51 100644 --- a/app/globals.css +++ b/app/globals.css @@ -97,3 +97,17 @@ nav > div:has(.nextra-search) { .guide-overview ul ul { margin-top: 0; } + +/* Only number h3 (###) in Steps component, not smaller nested headings */ +.nextra-steps h4, +.nextra-steps h5, +.nextra-steps h6 { + counter-increment: none !important; +} + +.nextra-steps h4::before, +.nextra-steps h5::before, +.nextra-steps h6::before { + content: none !important; + display: none !important; +} diff --git a/next.config.ts b/next.config.ts index 784d55f97..7cddacdde 100644 --- a/next.config.ts +++ b/next.config.ts @@ -23,6 +23,24 @@ const nextConfig: NextConfig = withLlmsTxt({ withNextra({ async redirects() { return [ + // Auto-added redirects for deleted pages + { + source: "/:locale/get-started/agent-frameworks/mastra/overview", + destination: "/:locale/get-started/agent-frameworks/mastra", + permanent: true, + }, + { + source: + "/:locale/get-started/agent-frameworks/mastra/use-arcade-tools", + destination: "/:locale/get-started/agent-frameworks/mastra", + permanent: true, + }, + { + source: + "/:locale/get-started/agent-frameworks/mastra/user-auth-interrupts", + destination: "/:locale/get-started/agent-frameworks/mastra", + permanent: true, + }, // Moved from guides to get-started { source: @@ -52,8 +70,7 @@ const nextConfig: NextConfig = withLlmsTxt({ }, { source: "/:locale/home/mastra/user-auth-interrupts", - destination: - "/:locale/get-started/agent-frameworks/mastra/user-auth-interrupts", + destination: "/:locale/get-started/agent-frameworks/mastra", permanent: true, }, { @@ -278,8 +295,7 @@ const nextConfig: NextConfig = withLlmsTxt({ }, { source: "/:locale/home/mastra/use-arcade-tools", - destination: - "/:locale/get-started/agent-frameworks/mastra/use-arcade-tools", + destination: "/:locale/get-started/agent-frameworks/mastra", permanent: true, }, { @@ -461,8 +477,7 @@ const nextConfig: NextConfig = withLlmsTxt({ }, { source: "/:locale/guides/agent-frameworks/mastra/typescript", - destination: - "/:locale/get-started/agent-frameworks/mastra/use-arcade-tools", + destination: "/:locale/get-started/agent-frameworks/mastra", permanent: true, }, { @@ -485,8 +500,7 @@ const nextConfig: NextConfig = withLlmsTxt({ // Old resource paths { source: "/:locale/resources/mastra/user-auth-interrupts", - destination: - "/:locale/get-started/agent-frameworks/mastra/user-auth-interrupts", + destination: "/:locale/get-started/agent-frameworks/mastra", permanent: true, }, { From 91fb98b5c5abff4b4a3387cd39ca977c7b7e80e3 Mon Sep 17 00:00:00 2001 From: Rachel Lee Nabors Date: Thu, 22 Jan 2026 18:55:21 +0000 Subject: [PATCH 02/19] Update Mastra workflow to use auth handling and Slack DM - Add auth error handling with arcade.auth.start() in workflow steps - Pass authRequired/authUrl through workflow steps - Use Slack_WhoAmI to get user's Slack ID - Send digest as DM instead of posting to #general channel - Use defaultUserId from ARCADE_USER_ID env var - Update documentation to explain workflow auth handling Co-Authored-By: Claude Opus 4.5 --- .../agent-frameworks/mastra/page.mdx | 282 +++++++++++++----- 1 file changed, 214 insertions(+), 68 deletions(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index bb9a069e5..35220a910 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -46,7 +46,7 @@ Before diving into the code, here are the key Mastra concepts you'll use: - `ToolCallFilter`: Removes tool calls and results from memory to prevent large API responses from bloating context. - `TokenLimiterProcessor`: Limits input tokens to stay within model context limits. -## Build the agent +## Build an agent @@ -353,12 +353,15 @@ On first use, the agent will return an authorization URL. Visit the URL to conne Agents are great for open-ended conversations, but sometimes you want a **deterministic process** that runs the same way every time. Mastra workflows let you chain steps together, with each step's output feeding into the next. -Let's build an email digest workflow that: +This workflow does the following: 1. Fetches emails from Gmail 2. Summarizes them with an LLM -3. Sends the digest to Slack +3. Sends the digest as a direct message to the user on Slack -This also demonstrates how workflows handle large data: the full email content stays internal to the workflow—only the compact summary gets sent to Slack. +This also demonstrates how workflows: +* handle **large data** the full email content stays internal to the workflow, and only the compact summary gets sent to Slack. +* handle **authorization errors** +* **pass auth URLs** through multiple workflow steps @@ -371,11 +374,13 @@ import { createStep, createWorkflow } from "@mastra/core/workflows"; import { z } from "zod"; import { Arcade } from "@arcadeai/arcadejs"; +const defaultUserId = process.env.ARCADE_USER_ID || ""; + // Step 1: Fetch emails from Gmail const fetchEmails = createStep({ id: "fetch-emails", inputSchema: z.object({ - userId: z.string(), + userId: z.string().optional(), maxEmails: z.number().optional(), }), outputSchema: z.object({ @@ -385,23 +390,45 @@ const fetchEmails = createStep({ snippet: z.string(), })), userId: z.string(), + authRequired: z.boolean().optional(), + authUrl: z.string().optional(), }), execute: async ({ inputData }) => { const arcade = new Arcade(); - const result = await arcade.tools.execute({ - tool_name: "Gmail.ListEmails", - user_id: inputData!.userId, - input: { n_emails: inputData!.maxEmails ?? 5 }, - }); - - const response = result as { output?: { value?: { emails?: any[] } } }; - const emails = (response.output?.value?.emails || []).map((e: any) => ({ - subject: String(e.subject || "(No subject)"), - from: String(e.sender || e.from || "Unknown"), - snippet: String(e.snippet || "").slice(0, 200), - })); - - return { emails, userId: inputData!.userId }; + const userId = inputData?.userId || defaultUserId; + + try { + const result = await arcade.tools.execute({ + tool_name: "Gmail.ListEmails", + user_id: userId, + input: { n_emails: inputData!.maxEmails ?? 5 }, + }); + + const response = result as { output?: { value?: { emails?: any[] } } }; + const emails = (response.output?.value?.emails || []).map((e: any) => ({ + subject: String(e.subject || "(No subject)"), + from: String(e.sender || e.from || "Unknown"), + snippet: String(e.snippet || "").slice(0, 200), + })); + + return { emails, userId }; + } catch (error: any) { + // Handle authorization required error + if (error.status === 403 || error.message?.includes("authorization")) { + const authResponse = await arcade.auth.start({ + user_id: userId, + provider: "google", + scopes: ["https://www.googleapis.com/auth/gmail.readonly"], + }); + return { + emails: [], + userId, + authRequired: true, + authUrl: authResponse.url, + }; + } + throw error; + } }, }); @@ -415,13 +442,22 @@ const summarizeEmails = createStep({ snippet: z.string(), })), userId: z.string(), + authRequired: z.boolean().optional(), + authUrl: z.string().optional(), }), outputSchema: z.object({ summary: z.string(), userId: z.string(), + authRequired: z.boolean().optional(), + authUrl: z.string().optional(), }), execute: async ({ inputData, mastra }) => { - const { emails, userId } = inputData!; + const { emails, userId, authRequired, authUrl } = inputData!; + + // Pass through auth requirement + if (authRequired) { + return { summary: "", userId, authRequired, authUrl }; + } if (emails.length === 0) { return { summary: "No new emails.", userId }; @@ -440,31 +476,71 @@ const summarizeEmails = createStep({ }, }); -// Step 3: Send to Slack +// Step 3: Send DM to Slack user const sendToSlack = createStep({ id: "send-to-slack", inputSchema: z.object({ summary: z.string(), userId: z.string(), + authRequired: z.boolean().optional(), + authUrl: z.string().optional(), }), outputSchema: z.object({ success: z.boolean(), message: z.string(), + authUrl: z.string().optional(), }), execute: async ({ inputData }) => { + const { summary, userId, authRequired, authUrl } = inputData!; + + // Return auth URL if authorization is needed + if (authRequired) { + return { + success: false, + message: `Authorization required. Please visit this URL to grant access: ${authUrl}`, + authUrl, + }; + } + const arcade = new Arcade(); - const { summary, userId } = inputData!; - - await arcade.tools.execute({ - tool_name: "Slack.SendMessage", - user_id: userId, - input: { - message: `📬 *Email Digest*\n\n${summary}`, - channel_name: "general", - }, - }); - return { success: true, message: "Digest sent to Slack" }; + try { + // Get the user's Slack identity + const whoAmI = await arcade.tools.execute({ + tool_name: "Slack_WhoAmI", + user_id: userId, + input: {}, + }); + + const slackUserId = (whoAmI as any)?.output?.value?.user_id; + + // Send DM to the user + await arcade.tools.execute({ + tool_name: "Slack_SendMessage", + user_id: userId, + input: { + message: `📬 *Email Digest*\n\n${summary}`, + user_ids: [slackUserId], + }, + }); + + return { success: true, message: "Digest sent as DM" }; + } catch (error: any) { + // Handle Slack authorization required + if (error.status === 403 || error.message?.includes("authorization")) { + const slackAuth = await arcade.auth.start({ + user_id: userId, + provider: "slack", + scopes: ["chat:write", "users:read"], + }); + return { + success: false, + message: `Slack authorization required. Please visit: ${slackAuth.url}`, + authUrl: slackAuth.url, + }; + } + throw error; + } }, }); @@ -472,12 +548,13 @@ const sendToSlack = createStep({ const emailDigestWorkflow = createWorkflow({ id: "email-digest", inputSchema: z.object({ - userId: z.string(), - maxEmails: z.number().optional(), + userId: z.string().default(defaultUserId), + maxEmails: z.number().default(5), }), outputSchema: z.object({ success: z.boolean(), message: z.string(), + authUrl: z.string().optional(), }), }) .then(fetchEmails) @@ -510,19 +587,20 @@ export const mastra = new Mastra({ ### Test the workflow -Restart the dev server and open Mastra Studio. Under **Workflows** in the sidebar, you'll see **emailDigestWorkflow**. Run it with your user ID to fetch emails, summarize them, and send the digest to Slack. +1. Restart the dev server and open Mastra Studio. In the sidebar, open **Workflows**. Select **email-digest**. +2. In the right sidebar, select "run" to run the workflow. +3. If authorization is required, the workflow returns an auth URL. Visit the URL, complete authorization, then run the workflow again. +4. Check your Slack DMs for the digest. -### Agent vs Workflow - -The agent handles open-ended requests ("help me with my emails"). The workflow handles repeatable processes ("every morning, summarize and send to Slack"). Use both together for powerful automation. - ## Key takeaways - **Arcade tools work seamlessly with Mastra**: Use `toZodToolSet` to convert Arcade tools to the Zod schema format Mastra expects. +- **Agent vs Workflow**: The agent handles open-ended requests ("help me with my emails"). The workflow handles repeatable processes ("every morning, summarize and send to Slack"). Use both together for powerful automation. - **Truncate large outputs**: Tools like Gmail can return 200KB+ of data. Wrap tool execution with truncation to prevent token overflow in the agentic loop. - **Authorization is automatic**: The `executeOrAuthorizeZodTool` factory handles auth flows. When a tool needs authorization, it returns a URL for the user to visit. +- **Workflows need explicit auth handling**: Unlike agents, workflows don't have built-in auth handling. Catch 403 errors, call `arcade.auth.start()`, and pass the auth URL through your workflow steps. - **Use agents for conversation, workflows for automation**: Agents handle open-ended requests; workflows handle repeatable, deterministic processes. ## Next steps @@ -706,10 +784,12 @@ import { createStep, createWorkflow } from "@mastra/core/workflows"; import { z } from "zod"; import { Arcade } from "@arcadeai/arcadejs"; +const defaultUserId = process.env.ARCADE_USER_ID || ""; + const fetchEmails = createStep({ id: "fetch-emails", inputSchema: z.object({ - userId: z.string(), + userId: z.string().optional(), maxEmails: z.number().optional(), }), outputSchema: z.object({ @@ -719,23 +799,44 @@ const fetchEmails = createStep({ snippet: z.string(), })), userId: z.string(), + authRequired: z.boolean().optional(), + authUrl: z.string().optional(), }), execute: async ({ inputData }) => { const arcade = new Arcade(); - const result = await arcade.tools.execute({ - tool_name: "Gmail.ListEmails", - user_id: inputData!.userId, - input: { n_emails: inputData!.maxEmails ?? 5 }, - }); - - const response = result as { output?: { value?: { emails?: any[] } } }; - const emails = (response.output?.value?.emails || []).map((e: any) => ({ - subject: String(e.subject || "(No subject)"), - from: String(e.sender || e.from || "Unknown"), - snippet: String(e.snippet || "").slice(0, 200), - })); - - return { emails, userId: inputData!.userId }; + const userId = inputData?.userId || defaultUserId; + + try { + const result = await arcade.tools.execute({ + tool_name: "Gmail.ListEmails", + user_id: userId, + input: { n_emails: inputData!.maxEmails ?? 5 }, + }); + + const response = result as { output?: { value?: { emails?: any[] } } }; + const emails = (response.output?.value?.emails || []).map((e: any) => ({ + subject: String(e.subject || "(No subject)"), + from: String(e.sender || e.from || "Unknown"), + snippet: String(e.snippet || "").slice(0, 200), + })); + + return { emails, userId }; + } catch (error: any) { + if (error.status === 403 || error.message?.includes("authorization")) { + const authResponse = await arcade.auth.start({ + user_id: userId, + provider: "google", + scopes: ["https://www.googleapis.com/auth/gmail.readonly"], + }); + return { + emails: [], + userId, + authRequired: true, + authUrl: authResponse.url, + }; + } + throw error; + } }, }); @@ -748,13 +849,21 @@ const summarizeEmails = createStep({ snippet: z.string(), })), userId: z.string(), + authRequired: z.boolean().optional(), + authUrl: z.string().optional(), }), outputSchema: z.object({ summary: z.string(), userId: z.string(), + authRequired: z.boolean().optional(), + authUrl: z.string().optional(), }), execute: async ({ inputData, mastra }) => { - const { emails, userId } = inputData!; + const { emails, userId, authRequired, authUrl } = inputData!; + + if (authRequired) { + return { summary: "", userId, authRequired, authUrl }; + } if (emails.length === 0) { return { summary: "No new emails.", userId }; @@ -778,37 +887,74 @@ const sendToSlack = createStep({ inputSchema: z.object({ summary: z.string(), userId: z.string(), + authRequired: z.boolean().optional(), + authUrl: z.string().optional(), }), outputSchema: z.object({ success: z.boolean(), message: z.string(), + authUrl: z.string().optional(), }), execute: async ({ inputData }) => { + const { summary, userId, authRequired, authUrl } = inputData!; + + if (authRequired) { + return { + success: false, + message: `Authorization required. Please visit this URL to grant access: ${authUrl}`, + authUrl, + }; + } + const arcade = new Arcade(); - const { summary, userId } = inputData!; - - await arcade.tools.execute({ - tool_name: "Slack.SendMessage", - user_id: userId, - input: { - message: `📬 *Email Digest*\n\n${summary}`, - channel_name: "general", - }, - }); - return { success: true, message: "Digest sent to Slack" }; + try { + const whoAmI = await arcade.tools.execute({ + tool_name: "Slack_WhoAmI", + user_id: userId, + input: {}, + }); + + const slackUserId = (whoAmI as any)?.output?.value?.user_id; + + await arcade.tools.execute({ + tool_name: "Slack_SendMessage", + user_id: userId, + input: { + message: `📬 *Email Digest*\n\n${summary}`, + user_ids: [slackUserId], + }, + }); + + return { success: true, message: "Digest sent as DM" }; + } catch (error: any) { + if (error.status === 403 || error.message?.includes("authorization")) { + const slackAuth = await arcade.auth.start({ + user_id: userId, + provider: "slack", + scopes: ["chat:write", "users:read"], + }); + return { + success: false, + message: `Slack authorization required. Please visit: ${slackAuth.url}`, + authUrl: slackAuth.url, + }; + } + throw error; + } }, }); const emailDigestWorkflow = createWorkflow({ id: "email-digest", inputSchema: z.object({ - userId: z.string(), - maxEmails: z.number().optional(), + userId: z.string().default(defaultUserId), + maxEmails: z.number().default(5), }), outputSchema: z.object({ success: z.boolean(), message: z.string(), + authUrl: z.string().optional(), }), }) .then(fetchEmails) From b058349798ce20ef9fff2a60fd778fdaaa415838 Mon Sep 17 00:00:00 2001 From: Rachel Lee Nabors Date: Thu, 22 Jan 2026 18:57:07 +0000 Subject: [PATCH 03/19] Merge origin/main into mastra-tutorial-update --- .../agent-frameworks/langchain/_meta.tsx | 3 +- .../langchain/use-arcade-tools/page.mdx | 243 -------- .../use-arcade-with-langchain/page.mdx | 3 + .../langchain/user-auth-interrupts/page.mdx | 373 ------------ .../agent-frameworks/openai-agents/_meta.tsx | 1 + .../use-arcade-with-openai-agents/page.mdx | 560 ++++++++++++++++++ app/en/get-started/agent-frameworks/page.mdx | 8 +- .../mcp-clients/claude-desktop/page.mdx | 2 +- .../mcp-clients/copilot-studio/page.mdx | 2 +- .../get-started/mcp-clients/cursor/page.mdx | 2 +- .../mcp-clients/visual-studio-code/page.mdx | 2 +- .../quickstarts/call-tool-client/page.mdx | 2 +- app/en/guides/_meta.tsx | 3 + .../deployment-hosting/arcade-deploy/page.mdx | 4 +- app/en/guides/mcp-gateways/_meta.tsx | 12 + .../mcp-gateways/create-via-ai/page.mdx | 77 +++ .../create-via-dashboard}/page.mdx | 40 +- app/en/guides/mcp-gateways/page.mdx | 65 ++ app/en/references/changelog/page.mdx | 2 +- next.config.ts | 44 +- pnpm-lock.yaml | 52 +- public/images/icons/python.svg | 265 +++++++++ public/llms.txt | 9 +- scripts/vale-editorial.ts | 53 +- 24 files changed, 1143 insertions(+), 684 deletions(-) delete mode 100644 app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx delete mode 100644 app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx create mode 100644 app/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents/page.mdx create mode 100644 app/en/guides/mcp-gateways/_meta.tsx create mode 100644 app/en/guides/mcp-gateways/create-via-ai/page.mdx rename app/en/guides/{create-tools/mcp-gateways => mcp-gateways/create-via-dashboard}/page.mdx (57%) create mode 100644 app/en/guides/mcp-gateways/page.mdx create mode 100644 public/images/icons/python.svg diff --git a/app/en/get-started/agent-frameworks/langchain/_meta.tsx b/app/en/get-started/agent-frameworks/langchain/_meta.tsx index a2708a562..070d90407 100644 --- a/app/en/get-started/agent-frameworks/langchain/_meta.tsx +++ b/app/en/get-started/agent-frameworks/langchain/_meta.tsx @@ -1,5 +1,4 @@ export default { - "use-arcade-tools": "Using Arcade tools", - "user-auth-interrupts": "User authorization", "auth-langchain-tools": "Authorizing existing tools", + "use-arcade-with-langchain": "Setup Arcade with LangChain", }; diff --git a/app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx deleted file mode 100644 index a16ccd5b4..000000000 --- a/app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx +++ /dev/null @@ -1,243 +0,0 @@ ---- -title: "Use Arcade tools with LangGraph" -description: "Integrate Arcade tools into your LangGraph applications" ---- - -import { Steps, Tabs, Callout } from "nextra/components"; - -## Use LangGraph with Arcade - -In this guide, let's explore how to integrate Arcade tools into your LangGraph application. Follow the step-by-step instructions below. For complete working examples, see our [Python](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain/langgraph_arcade_minimal.py) and [JavaScript](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain-ts/langgraph-arcade-minimal.ts) examples. - - - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys) - -### Set up your environment - -Install the required packages, and ensure your environment variables are set with your Arcade and OpenAI API keys: - - - -```bash -pip install langchain-arcade langchain-openai langgraph -``` - - -```bash -npm install @arcadeai/arcadejs @langchain/openai @langchain/core @langchain/langgraph -``` - - - - -### Configure API keys - -Provide your Arcade and OpenAI API keys. You can store them in environment variables or directly in your code: - -> Need an Arcade API key? Visit the [Get an API key](/get-started/setup/api-keys) page to create one. - - - -```python -import os - -arcade_api_key = os.environ.get("ARCADE_API_KEY", "YOUR_ARCADE_API_KEY") -openai_api_key = os.environ.get("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY") -``` - - -```bash -ARCADE_API_KEY= -OPENAI_API_KEY= -``` - - - -### Create and manage Arcade tools - - - -Use the ArcadeToolManager to retrieve specific tools or entire MCP Servers: - -```python -from langchain_arcade import ArcadeToolManager - -manager = ArcadeToolManager(api_key=arcade_api_key) - -# Fetch the "ScrapeUrl" tool from the "Firecrawl" MCP Server -tools = manager.get_tools(tools=["Firecrawl.ScrapeUrl"]) -print(manager.tools) - -# Get all tools from the "Gmail" MCP Server -tools = manager.get_tools(toolkits=["Gmail"]) -print(manager.tools) -``` - - -Arcade offers methods to convert tools into Zod schemas, which is essential since LangGraph defines tools using Zod. The `toZod` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with LangGraph. Learn more about Arcade's Zod integration options [here](/guides/tool-calling/custom-apps/get-tool-definitions#get-zod-tool-definitions). -```javascript -import { Arcade } from "@arcadeai/arcadejs"; -import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib"; -import { tool } from "@langchain/core/tools"; - -// Initialize the Arcade client -const arcade = new Arcade(); - -// Get the Arcade tools, you can customize the MCP Server (e.g. "github", "notion", "gmail", etc.) -const googleToolkit = await arcade.tools.list({ toolkit: "gmail", limit: 30 }); -const arcadeTools = toZod({ - tools: googleToolkit.items, - client: arcade, - userId: "", // Replace this with your application's user ID (e.g. email address, UUID, etc.) -}); -// Convert Arcade tools to LangGraph tools -const tools = arcadeTools.map(({ name, description, execute, parameters }) => - tool(execute, { - name, - description, - schema: parameters, - }), -); -console.log(tools); -``` - - - - -### Set up the language model and memory - -Create an AI model and bind your tools. Use MemorySaver for checkpointing: - - - -```python -from langchain_openai import ChatOpenAI -from langgraph.checkpoint.memory import MemorySaver - -model = ChatOpenAI(model="gpt-4o", api_key=openai_api_key) -bound_model = model.bind_tools(tools) - -memory = MemorySaver() -``` - - -```javascript -import { ChatOpenAI } from "@langchain/openai"; -import { MemorySaver } from "@langchain/langgraph"; - -const model = new ChatOpenAI({ model: "gpt-4o", apiKey: process.env.OPENAI_API_KEY }); -const boundModel = model.bindTools(tools); -const memory = new MemorySaver(); -``` - - - -### Create a ReAct-style agent - -Use the prebuilt ReAct agent from LangGraph to handle your Arcade tools: - - -```python -from langgraph.prebuilt import create_react_agent - -graph = create_react_agent(model=bound_model, tools=tools, checkpointer=memory) -``` - - -```javascript -import { createReactAgent } from "@langchain/langgraph/prebuilt"; - -const graph = createReactAgent({ llm: boundModel, tools, checkpointer: memory }); -``` - - - -### Provide configuration and user query - -Supply a basic config dictionary and a user query. Notice that user_id is required for tool authorization: - - -```python -config = { - "configurable": { - "thread_id": "1", - "user_id": "{arcade_user_id}" - } -} -user_input = { - "messages": [ - ("user", "List any new and important emails in my inbox.") - ] -} -``` - - -```javascript -const config = { - configurable: { - thread_id: "1", - user_id: "{arcade_user_id}", - }, - streamMode: "values" as const, -}; -const user_input = { - messages: [ - { - role: "user", - content: "List any new and important emails in my inbox.", - }, - ], -}; -``` - - - -### Stream the response - -Stream the assistant's output. If the tool requires authorization, the agent will ask the user to authorize the tool. - - - -```python -from langgraph.errors import NodeInterrupt - -try: - for chunk in graph.stream(user_input, config, stream_mode="values"): - chunk["messages"][-1].pretty_print() -except NodeInterrupt as exc: - print(f"\nNodeInterrupt occurred: {exc}") - print("Please authorize the tool or update the request, then re-run.") -``` - - -```javascript -try { - const stream = await graph.stream(user_input, config); - for await (const chunk of stream) { - console.log(chunk.messages[chunk.messages.length - 1]); - } -} catch (error) { - console.error("Error streaming response:", error); -} -``` - - - - -## Tips for selecting tools - -- **Relevance**: Pick only the tools you need. Avoid using all tools at once. -- **Avoid conflicts**: Be mindful of duplicate or overlapping functionality. - -## Next steps - -Now that you have integrated Arcade tools into your LangGraph agent, you can: - -- Experiment with different MCP Servers, such as "Math" or "Search." -- Customize the agent's prompts for specific tasks. -- Try out other language models and compare their performance. - -Enjoy exploring Arcade and building powerful AI-enabled Python applications! diff --git a/app/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx b/app/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx index 16f73cb26..fee9dc3d1 100644 --- a/app/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx +++ b/app/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx @@ -461,6 +461,8 @@ You should see the agent responding to your prompts like any model, as well as h ## Example code +
+**main.ts** (full file) ```ts filename="main.ts" "use strict"; import { Arcade } from "@arcadeai/arcadejs"; @@ -737,3 +739,4 @@ async function main() { // Run the main function main().catch((err) => console.error(err)); ``` +
diff --git a/app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx b/app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx deleted file mode 100644 index d52678880..000000000 --- a/app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx +++ /dev/null @@ -1,373 +0,0 @@ ---- -title: "Using Arcade User Auth" -description: "Build a custom LangGraph that handles tool authorization with Arcade" ---- - -import { Steps, Tabs, Callout } from "nextra/components"; - -## User Authorization in LangGraph - -In this guide, you will create a LangGraph workflow that requires user authorization before running certain Arcade tools. When a tool needs authorization, the graph displays an authorization URL and waits for the user's approval. This ensures that only the tools you explicitly authorize are available to the language model. For complete working examples, see our [Python](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain/langgraph_with_user_auth.py) and [JavaScript](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain-ts/langgraph-with-user-auth.ts) examples. - - - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys) - -### Install the required packages - -Set up your environment with the following installations: - - - -```bash -pip install langchain-arcade langchain-openai langgraph -``` - - -```bash -npm install @arcadeai/arcadejs @langchain/openai @langchain/core @langchain/langgraph -``` - - - -### Configure your Arcade environment - -Make sure you have set your Arcade API key (and any other relevant keys) in the environment, or assign them directly in the code: - -> Need an Arcade API key? Visit the [Get an API key](/get-started/setup/api-keys) page to create one. - - - -```python -import os - -# Import necessary classes and modules -from langchain_arcade import ArcadeToolManager -from langchain_openai import ChatOpenAI -from langgraph.checkpoint.memory import MemorySaver -from langgraph.graph import END, START, MessagesState, StateGraph -from langgraph.prebuilt import ToolNode -from langchain_core.runnables import RunnableConfig - -arcade_api_key = os.environ["ARCADE_API_KEY"] - -# Initialize the tool manager and fetch tools compatible with langgraph -tool_manager = ArcadeToolManager(api_key=arcade_api_key) -tools = tool_manager.get_tools(toolkits=["Gmail"]) -tool_node = ToolNode(tools) - -# Create a language model instance and bind it with the tools -model = ChatOpenAI(model="gpt-4o") -model_with_tools = model.bind_tools(tools) -``` - -Here are the main code elements: - -- arcade_api_key is your Arcade key. -- tool_manager fetches your Arcade tools, for example the "Gmail" MCP Server. -- tool_node encapsulates these tools for usage in LangGraph. -- model_with_tools binds your tools to the "gpt-4o" language model, enabling tool calls. - - - -```javascript -import { pathToFileURL } from "node:url"; -import { Arcade } from "@arcadeai/arcadejs"; -import { toZod } from "@arcadeai/arcadejs/lib"; -import type { AIMessage } from "@langchain/core/messages"; -import { tool } from "@langchain/core/tools"; -import { MessagesAnnotation, StateGraph } from "@langchain/langgraph"; -import { ToolNode } from "@langchain/langgraph/prebuilt"; -import { ChatOpenAI } from "@langchain/openai"; - -// Initialize Arcade with API key from environment -const arcade = new Arcade(); - -// Replace with your application's user ID (e.g. email address, UUID, etc.) -const USER_ID = "{arcade_user_id}"; - -// Initialize tools from Gmail MCP Server -const googleToolkit = await arcade.tools.list({ toolkit: "gmail", limit: 30 }); -const arcadeTools = toZod({ - tools: googleToolkit.items, - client: arcade, - userId: USER_ID, -}); - -// Convert Arcade tools to LangGraph tools -const tools = arcadeTools.map(({ name, description, execute, parameters }) => - tool(execute, { - name, - description, - schema: parameters, - }), -); - -// Initialize the prebuilt tool node -const toolNode = new ToolNode(tools); - -// Create a language model instance and bind it with the tools -const model = new ChatOpenAI({ - model: "gpt-4o", - apiKey: process.env.OPENAI_API_KEY, -}); -const modelWithTools = model.bindTools(tools); -``` - -Here are the main code elements: - -- arcade.tools.list fetches your Arcade tools, for example the "Gmail" MCP Server. -- toZod converts Arcade tools to Zod schemas, which are required by LangGraph. -- ToolNode encapsulates these tools for usage in LangGraph. -- modelWithTools binds your tools to the "gpt-4o" language model, enabling tool calls. - - - - -### Define the workflow steps - -You will create three primary functions to handle AI interaction, tool authorization, and flow control. - - -```python -# Function to invoke the model and get a response -def call_agent(state: MessagesState): - messages = state["messages"] - response = model_with_tools.invoke(messages) - # Return the updated message history - return {"messages": [response]} - - -# Function to determine the next step in the workflow based on the last message -def should_continue(state: MessagesState): - if state["messages"][-1].tool_calls: - for tool_call in state["messages"][-1].tool_calls: - if tool_manager.requires_auth(tool_call["name"]): - return "authorization" - return "tools" # Proceed to tool execution if no authorization is needed - return END # End the workflow if no tool calls are present - - -# Function to handle authorization for tools that require it -def authorize(state: MessagesState, config: RunnableConfig | None = None): - if config is None: - raise ValueError("Config is required for authorization") - - user_id = config["configurable"].get("user_id") - for tool_call in state["messages"][-1].tool_calls: - tool_name = tool_call["name"] - if not tool_manager.requires_auth(tool_name): - continue - auth_response = tool_manager.authorize(tool_name, user_id) - if auth_response.status != "completed": - # Prompt the user to visit the authorization URL - print(f"Visit the following URL to authorize: {auth_response.url}") - - # Wait for the user to complete the authorization - # and then check the authorization status again - tool_manager.wait_for_auth(auth_response.id) - if not tool_manager.is_authorized(auth_response.id): - # This stops execution if authorization fails - raise ValueError("Authorization failed.") - - return {"messages": []} -``` -Explanations for these functions: - -- call_agent: Invokes the language model using the latest conversation state. -- should_continue: Checks the last AI message for any tool calls. If a tool requires authorization, the flow transitions to authorization. Otherwise, it goes straight to tool execution or ends if no tools are called. -- authorize: Prompts the user to authorize any required tools, blocking until authorization is completed successfully or fails. - - -```javascript -// Function to check if a tool requires authorization -async function requiresAuth(toolName: string): Promise<{ - needsAuth: boolean; - id: string; - authUrl: string; -}> { - const authResponse = await arcade.tools.authorize({ - tool_name: toolName, - user_id: USER_ID, - }); - return { - needsAuth: authResponse.status === "pending", - id: authResponse.id ?? "", - authUrl: authResponse.url ?? "", - }; -} - -// Function to invoke the model and get a response -async function callAgent( - state: typeof MessagesAnnotation.State, -): Promise { - const messages = state.messages; - const response = await modelWithTools.invoke(messages); - return { messages: [response] }; -} - -// Function to determine the next step in the workflow based on the last message -async function shouldContinue( - state: typeof MessagesAnnotation.State, -): Promise { - const lastMessage = state.messages[state.messages.length - 1] as AIMessage; - if (lastMessage.tool_calls?.length) { - for (const toolCall of lastMessage.tool_calls) { - const { needsAuth } = await requiresAuth(toolCall.name); - if (needsAuth) { - return "authorization"; - } - } - return "tools"; // Proceed to tool execution if no authorization is needed - } - return "__end__"; // End the workflow if no tool calls are present -} - -// Function to handle authorization for tools that require it -async function authorize( - state: typeof MessagesAnnotation.State, -): Promise { - const lastMessage = state.messages[state.messages.length - 1] as AIMessage; - for (const toolCall of lastMessage.tool_calls || []) { - const toolName = toolCall.name; - const { needsAuth, id, authUrl } = await requiresAuth(toolName); - if (needsAuth) { - // Prompt the user to visit the authorization URL - console.log(`Visit the following URL to authorize: ${authUrl}`); - - // Wait for the user to complete the authorization - const response = await arcade.auth.waitForCompletion(id); - if (response.status !== "completed") { - throw new Error("Authorization failed"); - } - } - } - - return { messages: [] }; -} -``` - -Explanations for these functions: - -- requiresAuth: Checks if a tool requires authorization. -- callAgent: Invokes the language model using the latest conversation state. -- shouldContinue: Checks the last AI message for any tool calls. If a tool requires authorization, the flow transitions to authorization. Otherwise, it goes straight to tool execution or ends if no tools are called. -- authorize: Prompts the user to authorize any required tools, blocking until authorization is completed successfully or fails. - - - - -### Build and compile your LangGraph workflow - -Use StateGraph to assemble the nodes and edges, then compile the graph with a MemorySaver. - - - -```python -if __name__ == "__main__": - # Build the workflow graph using StateGraph - workflow = StateGraph(MessagesState) - - # Add nodes (steps) to the graph - workflow.add_node("agent", call_agent) - workflow.add_node("tools", tool_node) - workflow.add_node("authorization", authorize) - - # Define the edges and control flow between nodes - workflow.add_edge(START, "agent") - workflow.add_conditional_edges("agent", should_continue, ["authorization", "tools", END]) - workflow.add_edge("authorization", "tools") - workflow.add_edge("tools", "agent") - - # Set up memory for checkpointing the state - memory = MemorySaver() - - # Compile the graph with the checkpointer - graph = workflow.compile(checkpointer=memory) -``` - - -```javascript -// Build the workflow graph -const workflow = new StateGraph(MessagesAnnotation) - .addNode("agent", callAgent) - .addNode("tools", toolNode) - .addNode("authorization", authorize) - .addEdge("__start__", "agent") - .addConditionalEdges("agent", shouldContinue, [ - "authorization", - "tools", - "__end__", - ]) - .addEdge("authorization", "tools") - .addEdge("tools", "agent"); - -// Compile the graph -const graph = workflow.compile(); -``` - - - -### Provide inputs and run the graph - -Finally, define user-supplied messages, authorization config, and stream the outputs. The graph will pause for any required tool authorization. - - - -```python -# Define the input messages from the user -inputs = { - "messages": [ - { - "role": "user", - "content": "Check and see if I have any emails in my inbox", - } - ], -} - -# Configuration with thread and user IDs for authorization purposes -config = {"configurable": {"thread_id": "4", "user_id": "{arcade_user_id}"}} - -# Run the graph and stream the outputs -for chunk in graph.stream(inputs, config=config, stream_mode="values"): - # Pretty-print the last message in the chunk - chunk["messages"][-1].pretty_print() -``` - - -```javascript -const inputs = { - messages: [ - { - role: "user", - content: "Check and see if I have any important emails in my inbox", - }, - ], -}; -// Run the graph and stream the outputs -const stream = await graph.stream(inputs, { streamMode: "values" }); -for await (const chunk of stream) { - // Print the last message in the chunk - console.log(chunk.messages[chunk.messages.length - 1].content); -} -``` - - - -In this example: - -- The user prompts the agent to check emails. -- The message triggers a potential need for the "Gmail" MCP Server. -- If authorization is required, the code prints a URL and waits until you permit the tool call. - - - -## Next steps - -- Experiment with more Arcade MCP Servers for expanded capabilities. -- Explore advanced authorization logic, such as multi-user or role-based checks. -- Integrate additional nodes to handle more complex flows or multi-step tasks in your LangGraph. - -By combining Arcade's authorization features with stateful management in LangGraph, you can build AI-driven workflows that respect user permissions at every step. Have fun exploring Arcade! diff --git a/app/en/get-started/agent-frameworks/openai-agents/_meta.tsx b/app/en/get-started/agent-frameworks/openai-agents/_meta.tsx index cdb53979a..734c1f463 100644 --- a/app/en/get-started/agent-frameworks/openai-agents/_meta.tsx +++ b/app/en/get-started/agent-frameworks/openai-agents/_meta.tsx @@ -1,5 +1,6 @@ export default { overview: "Overview", + "use-arcade-with-openai-agents": "Setup Arcade with OpenAI Agents SDK", "use-arcade-tools": "Using Arcade tools", "user-auth-interrupts": "Managing user authorization", }; diff --git a/app/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents/page.mdx b/app/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents/page.mdx new file mode 100644 index 000000000..37d09a785 --- /dev/null +++ b/app/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents/page.mdx @@ -0,0 +1,560 @@ +--- +title: "Setup Arcade with OpenAI Agents SDK" +description: "Learn how to use Arcade tools in OpenAI Agents applications" +--- + +import { Steps, Tabs, Callout } from "nextra/components"; + +# Setup Arcade with OpenAI Agents SDK + +The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) is a popular Python library for building AI agents. It builds on top of the OpenAI API, and provides an interface for building agents. + + + + +Learn how to integrate Arcade tools using OpenAI Agents primitives. You will implement a CLI agent that can user Arcade tools to help the user with their requests. The harness handles tools that require authorization automatically, so users don't need to worry about it. + + + + + +- +- [Obtain an Arcade API key](/get-started/setup/api-keys) +- The [`uv` package manager](https://docs.astral.sh/uv/) + + + + + +- How to retrieve Arcade tools and transform them into OpenAI Agents tools +- How to build an OpenAI Agents agent +- How to integrate Arcade tools into the OpenAI Agents flow +- How to implement "just in time" (JIT) tool authorization using Arcade's client + + + + +## The agent architecture you will build in this guide + +The OpenAI Agents SDK provides an [Agent](https://openai.github.io/openai-agents-python/ref/agent/#agents.agent.Agent) class that implements a ReAct agent. It provides an interface for you to define the system prompt, the model, the tools, and possible sub-agents for handoffs. In this guide, you will manually keep track of the agent's history and state, and use the `run` method to invoke the agent in an agentic loop. + +## Integrate Arcade tools into an OpenAI Agents agent + + + +### Create a new project + +Create a new directory for your project and initialize a new virtual environment: + +```bash +mkdir openai-agents-arcade-example +cd openai-agents-arcade-example +uv venv +source .venv/bin/activate +``` + +Install the necessary packages: + +```bash +uv pip install openai-agents arcadepy +``` + +Create a new file called `.env` and add the following environment variables: + +```env filename=".env" +# Arcade API key +ARCADE_API_KEY=YOUR_ARCADE_API_KEY +# Arcade user ID (this is the email address you used to login to Arcade) +ARCADE_USER_ID={arcade_user_id} +# OpenAI API key +OPENAI_API_KEY=YOUR_OPENAI_API_KEY +``` + +### Import the necessary packages + +Create a new file called `main.py` and add the following code: + +```python filename="main.py" +from agents import Agent, Runner, TResponseInputItem +from agents.run_context import RunContextWrapper +from agents.tool import FunctionTool +from agents.exceptions import AgentsException +from arcadepy import AsyncArcade +from arcadepy.types.execute_tool_response import ExecuteToolResponse +from dotenv import load_dotenv +from functools import partial +from typing import Any +import os +import asyncio +import json +``` + +This includes several imports, here's a breakdown: + +- Arcade imports: + - `AsyncArcade`: The Arcade client, used to interact with the Arcade API. + - `ExecuteToolResponse`: The response type for the execute tool response. +- OpenAI Agents imports: + - `Agent`: The OpenAI Agents agent, used to define an agent. + - `Runner`: The OpenAI Agents runner, used to run the agent in an agentic loop. + - `TResponseInputItem`: The response input item type, determines the type of message in the conversation history. + - `RunContextWrapper`: Wraps the run context, providing information such as the user ID, the tool name, tool arguments, and other contextual information different parts of the agent may need. + - `FunctionTool`: OpenAI Agents tool definition format. + - `AgentsException`: The OpenAI Agents exception, used to handle errors in the agentic loop. +- Other imports: + - `load_dotenv`: Loads the environment variables from the `.env` file. + - `functools.partial`: Partially applies a function to a given set of arguments. + - `typing.Any`: A type hint for the any type. + - `os`: The operating system module, used to interact with the operating system. + - `asyncio`: The asynchronous I/O module, used to interact with the asynchronous I/O. + - `json`: The JSON module, used to interact with JSON data. + +### Configure the agent + +These variables are used in the rest of the code to customize the agent and manage the tools. Feel free to configure them to your liking. + +```python filename="main.py" +# Load environment variables +load_dotenv() + +# The Arcade User ID identifies who is authorizing each service. +ARCADE_USER_ID = os.getenv("ARCADE_USER_ID") +# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used. +MCP_SERVERS = ["Slack"] +# This determines individual tools. Useful to pick specific tools when you don't need all of them. +TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] +# This determines the maximum number of tool definitions Arcade will return per MCP server +TOOL_LIMIT = 30 +# This prompt defines the behavior of the agent. +SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack." +# This determines which LLM model will be used inside the agent +MODEL = "gpt-4o-mini" +``` + +### Write a custom error and utility functions to help with tool calls + +Here, you define `ToolError` to handle errors from the Arcade tools. It wraps the `AgentsException` and provides an informative error message that can be handled in the agentic loop in case anything goes wrong. + +You also define `convert_output_to_json` to convert the output of the Arcade tools to a JSON string. This is useful because the output of the Arcade tools is not always a JSON object, and the OpenAI Agents SDK expects a JSON string. + +```python filename="main.py" +# Arcade to OpenAI agent exception classes +class ToolError(AgentsException): + def __init__(self, result: ExecuteToolResponse | str): + self.result = None + if isinstance(result, str): + self.message = result + else: + self.message = result.output.error.message + self.result = result + + def __str__(self): + if self.result: + return f"Tool {self.result.tool_name} failed with error: {self.message}" + else: + return self.message + + +def convert_output_to_json(output: Any) -> str: + if isinstance(output, dict) or isinstance(output, list): + return json.dumps(output) + else: + return str(output) +``` + +### Write a helper function to authorize Arcade tools + +This helper function is how you implement "just in time" (JIT) tool authorization using Arcade's client. When the agent tries to execute a tool that requires authorization, the `result` object's `status` will be `"pending"`, and you can use the `authorize` method to get an authorization URL. You then wait for the user to complete the authorization and retry the tool call. If the user has already authorized the tool, the `status` will be `"completed"`, and the OAuth dance is skipped silently, which improves the user experience. + + + This function captures the authorization flow outside of the agent's context, + which is a good practice for security and context engineering. By handling + everything in the harness, you remove the risk of the LLM replacing the + authorization URL or leaking it, and you keep the context free from any + authorization-related traces, which reduces the risk of hallucinations. + + +```python filename="main.py" +async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str): + if not context.context.get("user_id"): + raise ToolError("No user ID and authorization required for tool") + + result = await client.tools.authorize( + tool_name=tool_name, + user_id=context.context.get("user_id"), + ) + + if result.status != "completed": + print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}") + + await client.auth.wait_for_completion(result) +``` + +### Write a helper function to execute Arcade tools + +This helper function is how the OpenAI Agents framework invokes the Arcade tools. It handles the authorization flow, and then calls the tool using the `execute` method. It handles the conversion of the arguments from JSON to a dictionary (expected by Arcade) and the conversion of the output from the Arcade tool to a JSON string (expected by the OpenAI Agents framework). Here is where you call the helper functions defined earlier to authorize the tool and convert the output to a JSON string. + +```python filename="main.py" +async def invoke_arcade_tool( + context: RunContextWrapper, + tool_args: str, + tool_name: str, + client: AsyncArcade, +): + args = json.loads(tool_args) + await authorize_tool(client, context, tool_name) + + print(f"Invoking tool {tool_name} with args: {args}") + result = await client.tools.execute( + tool_name=tool_name, + input=args, + user_id=context.context.get("user_id"), + ) + if not result.success: + raise ToolError(result) + + print(f"Tool {tool_name} called successfully, {MODEL} will now process the result...") + + return convert_output_to_json(result.output.value) +``` + +### Retrieve Arcade tools and transform them into LangChain tools + +Here you get the Arcade tools you want the agent to use, and transform them into OpenAI Agents tools. The first step is to initialize the Arcade client, and get the tools you want to use. Since OpenAI is itself an inference provider, the Arcade API provides a convenient endpoint to get the tools in the OpenAI format, which is also the format expected by the OpenAI Agents framework. + +This helper function is long, here's a breakdown of what it does for clarity: + +- retrieve tools from all configured MCP servers (defined in the `MCP_SERVERS` variable) +- retrieve individual tools (defined in the `TOOLS` variable) +- get the Arcade tools to OpenAI-formatted tools +- create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client. + +```python filename="main.py" +async def get_arcade_tools( + client: AsyncArcade | None = None, + tools: list[str] | None = None, + mcp_servers: list[str] | None = None, +) -> list[FunctionTool]: + + if not client: + client = AsyncArcade() + + # if no tools or MCP servers are provided, raise an error + if not tools and not mcp_servers: + raise ValueError( + "No tools or MCP servers provided to retrieve tool definitions") + + # Use the Arcade Client to get OpenAI-formatted tool definitions + tool_formats = [] + + # Retrieve individual tools if specified + if tools: + # OpenAI-formatted tool definition + tasks = [client.tools.formatted.get(name=tool_id, format="openai") + for tool_id in tools] + responses = await asyncio.gather(*tasks) + for response in responses: + tool_formats.append(response) + + # Retrieve tools from specified toolkits + if mcp_servers: + # Create a task for each toolkit to fetche the formatted tool definition concurrently. + tasks = [client.tools.formatted.list(toolkit=tk, format="openai") + for tk in mcp_servers] + responses = await asyncio.gather(*tasks) + + # Combine the tool definitions from each response. + for response in responses: + # Here the code assumes the returned response has an "items" attribute + # containing a list of ToolDefinition objects. + tool_formats.extend(response.items) + + + # Create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client. + tool_functions = [] + for tool in tool_formats: + tool_name = tool["function"]["name"] + tool_description = tool["function"]["description"] + tool_params = tool["function"]["parameters"] + tool_function = FunctionTool( + name=tool_name, + description=tool_description, + params_json_schema=tool_params, + on_invoke_tool=partial( + invoke_arcade_tool, + tool_name=tool_name, + client=client, + ), + strict_json_schema=False, + ) + tool_functions.append(tool_function) + + return tool_functions +``` + +### Create the main function + +The main function is where you: + +- Get the tools from the configured MCP Servers +- Create an agent with the configured tools +- Initialize the conversation +- Run the loop + +The loop is a while loop that captures the user input, appends it to the conversation history, and then runs the agent. The agent's response is then appended to the conversation history, and the loop continues. + +The loop is interrupted when the agent's response contains a tool call, and the tool call is handled by the helper function you wrote earlier. + +```python filename="main.py" +async def main(): + # Get tools from the configured MCP Servers + tools = await get_arcade_tools(mcp_servers=MCP_SERVERS, + tools=TOOLS) + + # Create an agent with the configured tools + agent = Agent( + name="Inbox Assistant", + instructions=SYSTEM_PROMPT, + model=MODEL, + tools=tools, + ) + + # initialize the conversation + history: list[TResponseInputItem] = [] + # run the loop + while True: + prompt = input("You: ") + if prompt.lower() == "exit": + break + history.append({"role": "user", "content": prompt}) + try: + result = await Runner.run( + starting_agent=agent, + input=history, + context={"user_id": ARCADE_USER_ID}, + ) + history = result.to_input_list() + print(f"Assistant: {result.final_output}") + except ToolError as e: + # Something went wrong with the tool call, print the error message and exit the loop + print(e.message) + break + +# Run the main function as the entry point of the script +if __name__ == "__main__": + asyncio.run(main()) +``` + +### Run the agent + +```bash +uv run main.py +``` + +You should see the agent responding to your prompts like any model, as well as handling any tool calls and authorization requests. Here are some example prompts you can try: + +- "Send me an email with a random haiku about OpenAI Agents" +- "Summarize my latest 3 emails" + + + +## Key takeaways + +- Arcade tools can be integrated into any agentic framework like OpenAI Agents, all you need is to transform the Arcade tools into OpenAI Agents tools and handle the authorization flow. +- Context isolation: By handling the authorization flow outside of the agent's context, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations. + +## Next Steps + +1. Try adding additional tools to the agent or modifying the tools in the catalog for a different use case by modifying the `MCP_SERVERS` and `TOOLS` variables. +2. Try implementing a fully deterministic flow before the agentic loop, use this deterministic phase to prepare the context for the agent, adding things like the current date, time, or any other information that is relevant to the task at hand. + +## Example code + +```python filename="main.py" +from agents import Agent, Runner, TResponseInputItem +from agents.run_context import RunContextWrapper +from agents.tool import FunctionTool +from agents.exceptions import AgentsException +from arcadepy import AsyncArcade +from arcadepy.types.execute_tool_response import ExecuteToolResponse +from dotenv import load_dotenv +from functools import partial +from typing import Any +import os +import asyncio +import json + +# Load environment variables +load_dotenv() + +# The Arcade User ID identifies who is authorizing each service. +ARCADE_USER_ID = os.getenv("ARCADE_USER_ID") +# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used. +MCP_SERVERS = ["Slack"] +# This determines individual tools. Useful to pick specific tools when you don't need all of them. +TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] +# This determines the maximum number of tool definitions Arcade will return per MCP server +TOOL_LIMIT = 30 +# This prompt defines the behavior of the agent. +SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack." +# This determines which LLM model will be used inside the agent +MODEL = "gpt-4o-mini" + +# Arcade to OpenAI agent exception classes +class ToolError(AgentsException): + def __init__(self, result: ExecuteToolResponse | str): + self.result = None + if isinstance(result, str): + self.message = result + else: + self.message = result.output.error.message + self.result = result + + def __str__(self): + if self.result: + return f"Tool {self.result.tool_name} failed with error: {self.message}" + else: + return self.message + + +def convert_output_to_json(output: Any) -> str: + if isinstance(output, dict) or isinstance(output, list): + return json.dumps(output) + else: + return str(output) + +async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str): + if not context.context.get("user_id"): + raise ToolError("No user ID and authorization required for tool") + + result = await client.tools.authorize( + tool_name=tool_name, + user_id=context.context.get("user_id"), + ) + + if result.status != "completed": + print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}") + + await client.auth.wait_for_completion(result) + +async def invoke_arcade_tool( + context: RunContextWrapper, + tool_args: str, + tool_name: str, + client: AsyncArcade, +): + args = json.loads(tool_args) + await authorize_tool(client, context, tool_name) + + print(f"Invoking tool {tool_name} with args: {args}") + result = await client.tools.execute( + tool_name=tool_name, + input=args, + user_id=context.context.get("user_id"), + ) + if not result.success: + raise ToolError(result) + + print(f"Tool {tool_name} called successfully, {MODEL} will now process the result...") + + return convert_output_to_json(result.output.value) + +async def get_arcade_tools( + client: AsyncArcade | None = None, + tools: list[str] | None = None, + mcp_servers: list[str] | None = None, +) -> list[FunctionTool]: + + if not client: + client = AsyncArcade() + + # if no tools or MCP servers are provided, raise an error + if not tools and not mcp_servers: + raise ValueError( + "No tools or MCP servers provided to retrieve tool definitions") + + # Use the Arcade Client to get OpenAI-formatted tool definitions + tool_formats = [] + + # Retrieve individual tools if specified + if tools: + # OpenAI-formatted tool definition + tasks = [client.tools.formatted.get(name=tool_id, format="openai") + for tool_id in tools] + responses = await asyncio.gather(*tasks) + for response in responses: + tool_formats.append(response) + + # Retrieve tools from specified toolkits + if mcp_servers: + # Create a task for each toolkit to fetche the formatted tool definition concurrently. + tasks = [client.tools.formatted.list(toolkit=tk, format="openai") + for tk in mcp_servers] + responses = await asyncio.gather(*tasks) + + # Combine the tool definitions from each response. + for response in responses: + # Here the code assumes the returned response has an "items" attribute + # containing a list of ToolDefinition objects. + tool_formats.extend(response.items) + + + # Create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client. + tool_functions = [] + for tool in tool_formats: + tool_name = tool["function"]["name"] + tool_description = tool["function"]["description"] + tool_params = tool["function"]["parameters"] + tool_function = FunctionTool( + name=tool_name, + description=tool_description, + params_json_schema=tool_params, + on_invoke_tool=partial( + invoke_arcade_tool, + tool_name=tool_name, + client=client, + ), + strict_json_schema=False, + ) + tool_functions.append(tool_function) + + return tool_functions + +async def main(): + # Get tools from the configured MCP Servers + tools = await get_arcade_tools(mcp_servers=MCP_SERVERS, + tools=TOOLS) + + # Create an agent with the configured tools + agent = Agent( + name="Inbox Assistant", + instructions=SYSTEM_PROMPT, + model=MODEL, + tools=tools, + ) + + # initialize the conversation + history: list[TResponseInputItem] = [] + # run the loop + while True: + prompt = input("You: ") + if prompt.lower() == "exit": + break + history.append({"role": "user", "content": prompt}) + try: + result = await Runner.run( + starting_agent=agent, + input=history, + context={"user_id": ARCADE_USER_ID}, + ) + history = result.to_input_list() + print(f"Assistant: {result.final_output}") + except ToolError as e: + # Something went wrong with the tool call, print the error message and exit the loop + print(e.message) + break + +# Run the main function as the entry point of the script +if __name__ == "__main__": + asyncio.run(main()) +``` diff --git a/app/en/get-started/agent-frameworks/page.mdx b/app/en/get-started/agent-frameworks/page.mdx index 879262657..062cd1908 100644 --- a/app/en/get-started/agent-frameworks/page.mdx +++ b/app/en/get-started/agent-frameworks/page.mdx @@ -5,13 +5,19 @@ import { Tabs } from "nextra/components"; Arcade integrates with agent frameworks and MCP clients to add tool-calling capabilities to your AI applications. -These guides are for developers building AI applications who need to connect Arcade tools to their agent frameworks or MCP clients. These guides show you how to authenticate with Arcade, load tools, and execute them within your chosen framework. Each guide provides code examples and configuration steps to get you started quickly. +These guides are for developers building AI applications who need to connect Arcade tools to their agent frameworks or MCP clients. You'll learn how to authenticate with Arcade, load tools, and execute them within your chosen framework. Each guide provides code examples and configuration steps to get you started quickly with the most popular frameworks and programming languages. ## Agent Frameworks
+ Arcade account 2. Get an [Arcade API key](/get-started/setup/api-keys) -3. Create an [Arcade MCP Gateway](/guides/create-tools/mcp-gateways) and select the tools you want to use +3. Create an [Arcade MCP Gateway](/guides/mcp-gateways) and select the tools you want to use diff --git a/app/en/get-started/mcp-clients/copilot-studio/page.mdx b/app/en/get-started/mcp-clients/copilot-studio/page.mdx index 304827b5c..cb635639d 100644 --- a/app/en/get-started/mcp-clients/copilot-studio/page.mdx +++ b/app/en/get-started/mcp-clients/copilot-studio/page.mdx @@ -26,7 +26,7 @@ Connect Microsoft Copilot Studio to an Arcade MCP Gateway. 1. A Microsoft 365 subscription with access to Copilot Studio 2. Create an Arcade account 3. Get an [Arcade API key](/get-started/setup/api-keys) -4. Create an [Arcade MCP Gateway](/guides/create-tools/mcp-gateways) and select the tools you want to use +4. Create an [Arcade MCP Gateway](/guides/mcp-gateways) and select the tools you want to use diff --git a/app/en/get-started/mcp-clients/cursor/page.mdx b/app/en/get-started/mcp-clients/cursor/page.mdx index f02d39683..bf9cf04a2 100644 --- a/app/en/get-started/mcp-clients/cursor/page.mdx +++ b/app/en/get-started/mcp-clients/cursor/page.mdx @@ -14,7 +14,7 @@ Connect Cursor to an Arcade MCP Gateway. 1. Create an Arcade account 2. Get an [Arcade API key](/get-started/setup/api-keys) -3. Create an [Arcade MCP Gateway](/guides/create-tools/mcp-gateways) and select the tools you want to use +3. Create an [Arcade MCP Gateway](/guides/mcp-gateways) and select the tools you want to use diff --git a/app/en/get-started/mcp-clients/visual-studio-code/page.mdx b/app/en/get-started/mcp-clients/visual-studio-code/page.mdx index e8ed31de5..811de3162 100644 --- a/app/en/get-started/mcp-clients/visual-studio-code/page.mdx +++ b/app/en/get-started/mcp-clients/visual-studio-code/page.mdx @@ -10,7 +10,7 @@ In this guide, you'll learn how to connect Visual Studio Code to an Arcade MCP G ### Prerequisites 1. Create an Arcade account -2. Create an [Arcade MCP Gateway](/guides/create-tools/mcp-gateways) and select the tools you want to use +2. Create an [Arcade MCP Gateway](/guides/mcp-gateways) and select the tools you want to use ### Set up Visual Studio Code diff --git a/app/en/get-started/quickstarts/call-tool-client/page.mdx b/app/en/get-started/quickstarts/call-tool-client/page.mdx index 67d2b2490..0375b7592 100644 --- a/app/en/get-started/quickstarts/call-tool-client/page.mdx +++ b/app/en/get-started/quickstarts/call-tool-client/page.mdx @@ -147,7 +147,7 @@ As you interact with the agent, it will call the tools from the MCP Gateway. You ## Next Steps -- Learn more about [MCP Gateways](/guides/create-tools/mcp-gateways). +- Learn more about [MCP Gateways](/guides/mcp-gateways). - Learn how to use MCP Gateways with: - [Cursor](/get-started/mcp-clients/cursor) - [Visual Studio Code](/get-started/mcp-clients/visual-studio-code) diff --git a/app/en/guides/_meta.tsx b/app/en/guides/_meta.tsx index 3c8f69763..11d9904c2 100644 --- a/app/en/guides/_meta.tsx +++ b/app/en/guides/_meta.tsx @@ -1,6 +1,9 @@ import type { MetaRecord } from "nextra"; export const meta: MetaRecord = { + "mcp-gateways": { + title: "MCP Gateways", + }, "tool-calling": { title: "Call tools", }, diff --git a/app/en/guides/deployment-hosting/arcade-deploy/page.mdx b/app/en/guides/deployment-hosting/arcade-deploy/page.mdx index c05e679f7..7b088b9c8 100644 --- a/app/en/guides/deployment-hosting/arcade-deploy/page.mdx +++ b/app/en/guides/deployment-hosting/arcade-deploy/page.mdx @@ -148,11 +148,11 @@ Navigate to the [Servers](https://api.arcade.dev/dashboard/servers) page in your - Test and execute all the tools - Manage users connected to the Auth providers - Manage the secrets for the server -- Create [MCP Gateways](/guides/create-tools/mcp-gateways) +- Create [MCP Gateways](/guides/mcp-gateways) ## Create an MCP Gateway to call the tools in your MCP Server -Once the MCP server is deployed to Arcade, all the tools in the server will be available in the [tool catalog](https://api.arcade.dev/dashboard/tools) page in your Arcade dashboard. To call the tools from an MCP client, you first need to [create an MCP Gateway](/guides/create-tools/mcp-gateways) to pick and choose which tools you want to use in your MCP clients. +Once the MCP server is deployed to Arcade, all the tools in the server will be available in the [tool catalog](https://api.arcade.dev/dashboard/tools) page in your Arcade dashboard. To call the tools from an MCP client, you first need to [create an MCP Gateway](/guides/mcp-gateways) to pick and choose which tools you want to use in your MCP clients. When creating an MCP gateway, you can select the tools you want to include in the Gateway from any MCP Servers available to the project, including the one you just deployed. diff --git a/app/en/guides/mcp-gateways/_meta.tsx b/app/en/guides/mcp-gateways/_meta.tsx new file mode 100644 index 000000000..be0f96723 --- /dev/null +++ b/app/en/guides/mcp-gateways/_meta.tsx @@ -0,0 +1,12 @@ +import type { MetaRecord } from "nextra"; + +export const meta: MetaRecord = { + "create-via-dashboard": { + title: "Create via Dashboard", + }, + "create-via-ai": { + title: "Create via AI Assistant", + }, +}; + +export default meta; diff --git a/app/en/guides/mcp-gateways/create-via-ai/page.mdx b/app/en/guides/mcp-gateways/create-via-ai/page.mdx new file mode 100644 index 000000000..1f990486f --- /dev/null +++ b/app/en/guides/mcp-gateways/create-via-ai/page.mdx @@ -0,0 +1,77 @@ +--- +title: "Arcade Gateway Assistant" +description: "Create and manage MCP Gateways using AI directly from your chat interface" +--- + +import { Callout, Steps } from "nextra/components"; +import { SignupLink } from "@/app/_components/analytics"; + +# Arcade Gateway Assistant + + + + +Create and manage MCP gateways directly from your chat interface using natural language. + + + + + +1. Create an Arcade account +2. An MCP-compatible chat client ([Cursor](/get-started/mcp-clients/cursor), [Claude Desktop](/get-started/mcp-clients/claude-desktop), [VS Code](/get-started/mcp-clients/visual-studio-code), etc.) + + + + + +## Setup + + + +### Connect to the Gateway Assistant + +The Gateway Assistant is an MCP server that creates and manages MCP gateways. To use it, add the Arcade Gateway Assistant (using Arcade Auth) to your MCP client using this URL: + +``` +https://ctl.arcade.dev/mcp +``` + + +The Gateway Assistant uses **Arcade Auth**. This means you'll authenticate with your Arcade account to access the assistant. + + +Each MCP client has a different setup process. See [Connect to MCP clients](/get-started/mcp-clients) for detailed instructions for adding the Gateway Assistant to Cursor, Claude Desktop, VS Code, and other supported clients with Arcade Auth. + +### Authenticate + +When you first use the Gateway Assistant, the system will prompt you to authenticate with your Arcade account. This is a one-time setup that allows the assistant to create and manage gateways on your behalf. + +### Start creating gateways + +Ask your AI assistant to create a gateway by describing what you want to do. For example: + +> "I want to send emails with Gmail and manage my calendar with Google Calendar. Create a gateway for this." + +> "Create a gateway that creates and manages MCP gateways for GitHub PRs and post updates to Slack." + +The assistant will select the appropriate tools from Arcade's catalog and create a gateway for you. + + + + + +## After creating a gateway + +Once you've created a gateway, you'll need to add it to your chat client as a separate MCP server. The assistant will provide your new gateway's MCP URL (for example, `https://api.arcade.dev/mcp/`). + +Follow the same process you used to add the Gateway Assistant - see [Connect to MCP clients](/get-started/mcp-clients) for setup instructions specific to your client. + + +The Gateway Assistant creates gateways that use **Arcade Auth** by default. This means you'll authenticate with your Arcade account to access the gateway. For production use cases with end users who don't have Arcade accounts, you can modify the gateway's authentication settings in the [dashboard](https://api.arcade.dev/dashboard/mcp-gateways). + diff --git a/app/en/guides/create-tools/mcp-gateways/page.mdx b/app/en/guides/mcp-gateways/create-via-dashboard/page.mdx similarity index 57% rename from app/en/guides/create-tools/mcp-gateways/page.mdx rename to app/en/guides/mcp-gateways/create-via-dashboard/page.mdx index 303c9befe..ad16b7198 100644 --- a/app/en/guides/create-tools/mcp-gateways/page.mdx +++ b/app/en/guides/mcp-gateways/create-via-dashboard/page.mdx @@ -1,9 +1,10 @@ --- -title: "MCP Gateways" -description: "Comprehensive guide to using MCP Gateways" +title: "Create via Dashboard" +description: "Create and configure MCP Gateways using the Arcade dashboard" --- import Image from "next/image"; +import { SignupLink } from "@/app/_components/analytics"; export const IMAGE_SCALE_FACTOR = 2; export const TOOL_FILTER_LIGHT_WIDTH = 2052; @@ -11,13 +12,26 @@ export const TOOL_FILTER_LIGHT_HEIGHT = 1412; export const TOOL_FILTER_DARK_WIDTH = 2052; export const TOOL_FILTER_DARK_HEIGHT = 1412; -# MCP Gateways +# Create via Dashboard -MCP Gateways are a way to connect multiple MCP Servers to your agent, application, or IDE. MCP Gateways allow you to federate the tools from multiple MCP Servers into a single collection for easier management, control, and access. You can mix and match tools from different MCP Servers in the same project, and not all tools from a MCP server need to be available to the same LLM. + + -## Configure MCP Gateways +Create and configure an MCP Gateway using the Arcade dashboard with full control over settings. -To configure an MCP Gateway, go to the [MCP Gateways dashboard](https://api.arcade.dev/dashboard/mcp-gateways) and click on the "Create MCP Gateway" button. + + + + +1. Create an Arcade account + + + + + +## Create a Gateway + +To create an MCP Gateway, go to the [MCP Gateways dashboard](https://api.arcade.dev/dashboard/mcp-gateways) and click the "Create MCP Gateway" button. When configuring an MCP Gateway, you can select the tools you want to include in the Gateway from any MCP Servers available to the project: @@ -36,6 +50,8 @@ When configuring an MCP Gateway, you can select the tools you want to include in height={TOOL_FILTER_DARK_HEIGHT / IMAGE_SCALE_FACTOR} /> +## Configuration Options + The options available when configuring an MCP Gateway are: - **Name**: The name of the MCP Gateway. Informative only. @@ -43,14 +59,12 @@ The options available when configuring an MCP Gateway are: - **LLM Instructions**: Optional instructions for the LLM about how to use the MCP Gateway. - **Slug**: The slug of the MCP Gateway. This is the URL slug that will be used to access the MCP Gateway. It must be unique. - **Authentication**: The authentication mode to use for the MCP Gateway. This determines how the MCP Gateway will authenticate requests to the MCP Servers. Users will still need to authenticate to the tools within the MCP Gateway as normal. - - **Arcade Auth**: To access the MCP Gateway, you'll need to authenticate with your Arcade account. We recommend using this authentication mode for MCP Gateways in development or testing phase, or for internal use when you know all the users will have Arcade accounts. - - **Arcade Headers**: To access the MCP Gateway, you'll need to authenticate with your Arcade account by passing an Arcade API key in the `Authorization` header and the user ID of your end-user in the `Arcade-User-ID` header. We recommend using this authentication mode for MCP Gateways in production when your agent or application has users without Arcade accounts. + - **Arcade Auth**: To access the MCP Gateway, you'll need to authenticate with your Arcade account. This authentication mode is recommended for MCP Gateways in development or testing phase, or for internal use when you know all the users will have Arcade accounts. + - **Arcade Headers**: To access the MCP Gateway, you'll need to authenticate with your Arcade account by passing an Arcade API key in the `Authorization` header and the user ID of your end-user in the `Arcade-User-ID` header. This authentication mode is recommended for MCP Gateways in production when your agent or application has users without Arcade accounts. - **Allowed Tools**: A selection of tools in the Arcade Tool Catalog that will be available to the MCP Gateway. -## How to use MCP Gateways +## After Creating a Gateway -Any MCP client that supports the Streamable HTTP transport can use an Arcade MCP Gateway. To use an Arcade MCP Gateway, you can use the `https://api.arcade.dev/mcp/` URL in your MCP client. Learn how to use MCP Gateways with: +Once you've created a gateway, you'll need to add it to your chat client. The assistant will provide the MCP URL (for example, `https://api.arcade.dev/mcp/`). -- [Cursor](/get-started/mcp-clients/cursor) -- [Claude Desktop](/get-started/mcp-clients/claude-desktop) -- [Visual Studio Code](/get-started/mcp-clients/visual-studio-code) +See [Connect to MCP clients](/get-started/mcp-clients) for setup instructions specific to your client. diff --git a/app/en/guides/mcp-gateways/page.mdx b/app/en/guides/mcp-gateways/page.mdx new file mode 100644 index 000000000..56fd3106e --- /dev/null +++ b/app/en/guides/mcp-gateways/page.mdx @@ -0,0 +1,65 @@ +--- +title: "MCP Gateways" +description: "Connect multiple MCP servers to your agent, application, or IDE with Arcade MCP Gateways" +--- + +import { Cards } from "nextra/components"; +import { SignupLink } from "@/app/_components/analytics"; + +# MCP Gateways + +MCP Gateways are a way to connect multiple MCP Servers to your agent, application, or IDE. MCP Gateways allow you to federate the tools from multiple MCP Servers into a single collection for easier management, control, and access. You can mix and match tools from different MCP Servers in the same project. + +## Why use MCP Gateways? + +- Federate tools - Combine tools from multiple MCP servers into a single endpoint +- Control access - Choose exactly which tools are available to each gateway +- Mix and match - Use different tool combinations for different use cases +- Simplify configuration - One gateway URL instead of multiple server configurations +- Server Instructions - Set instructions for how the MCP client should use your gateway to help the LLM better understand your use case + +## Create an MCP Gateway + +You can create an MCP Gateway in two ways: + + + + + + +Dashboard - Use the web interface for full control over gateway settings, authentication modes, and tool selection. Best for production configurations and when you need to use tools +that you built yourself, or were not built by Arcade. + +AI Assistant - Describe what you want in natural language and let AI select the right tools for you. Best for quickly creating a gateway without ever leaving your chat interface. + +## Connect to an MCP Gateway + +Any MCP client that supports the Streamable HTTP transport can use an Arcade MCP Gateway. Use your gateway URL in the following format: + +``` +https://api.arcade.dev/mcp/ +``` + +Learn how to [connect MCP Gateways to your preferred client](/get-started/mcp-clients). + +## Authentication + +MCP Gateways support two authentication modes: + +| Mode | Best For | How It Works | +|------|----------|--------------| +| **Arcade Auth (Recommended)** | Development, testing, internal use | Users authenticate with their Arcade account via OAuth | +| **Arcade Headers** | Production when end-users shouldn't authenticate via Arcade | Pass `Authorization: Bearer {your_api_key}` header and `Arcade-User-ID` header with the end-user identifier | + +See [Create via Dashboard](/guides/mcp-gateways/create-via-dashboard) for detailed authentication configuration. + +## Next Steps + +- Create an Arcade account if you haven't already +- [Browse available integrations](/resources/integrations) to see what tools you can add to your gateway diff --git a/app/en/references/changelog/page.mdx b/app/en/references/changelog/page.mdx index 34c939992..085a74878 100644 --- a/app/en/references/changelog/page.mdx +++ b/app/en/references/changelog/page.mdx @@ -28,7 +28,7 @@ _Here's what's new at Arcade.dev!_ - MCP Gateways now support OAuth! Learn more about it [here](/guides/create-tools/mcp-gateways)! + MCP Gateways now support OAuth! Learn more about it [here](/guides/mcp-gateways)! **Arcade MCP Servers** diff --git a/next.config.ts b/next.config.ts index 7cddacdde..224ff8afe 100644 --- a/next.config.ts +++ b/next.config.ts @@ -23,7 +23,28 @@ const nextConfig: NextConfig = withLlmsTxt({ withNextra({ async redirects() { return [ - // Auto-added redirects for deleted pages + // Moved MCP Gateway UI guide to guides + { + source: "/:locale/guides/create-tools/mcp-gateways", + destination: "/:locale/guides/mcp-gateways", + permanent: true, + }, + // Removed LangChain old stuff + { + source: + "/:locale/get-started/agent-frameworks/langchain/use-arcade-tools", + destination: + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", + permanent: true, + }, + { + source: + "/:locale/get-started/agent-frameworks/langchain/user-auth-interrupts", + destination: + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", + permanent: true, + }, + // Mastra tutorial consolidation { source: "/:locale/get-started/agent-frameworks/mastra/overview", destination: "/:locale/get-started/agent-frameworks/mastra", @@ -53,13 +74,26 @@ const nextConfig: NextConfig = withLlmsTxt({ { source: "/:locale/home/langchain/use-arcade-tools", destination: - "/:locale/get-started/agent-frameworks/langchain/use-arcade-tools", + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", + permanent: true, + }, + { + source: "/:locale/guides/agent-frameworks/langchain/use-arcade-tools", + destination: + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", permanent: true, }, { source: "/:locale/home/langchain/user-auth-interrupts", destination: - "/:locale/get-started/agent-frameworks/langchain/user-auth-interrupts", + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", + permanent: true, + }, + { + source: + "/:locale/guides/agent-frameworks/langchain/user-auth-interrupts", + destination: + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", permanent: true, }, { @@ -320,7 +354,7 @@ const nextConfig: NextConfig = withLlmsTxt({ }, { source: "/:locale/home/mcp-gateways", - destination: "/:locale/guides/create-tools/mcp-gateways", + destination: "/:locale/guides/mcp-gateways", permanent: true, }, { @@ -466,7 +500,7 @@ const nextConfig: NextConfig = withLlmsTxt({ { source: "/:locale/guides/agent-frameworks/langchain/python", destination: - "/:locale/get-started/agent-frameworks/langchain/use-arcade-tools", + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", permanent: true, }, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 436ac536b..1a9f4d014 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: version: 4.6.0(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) nextra-theme-docs: specifier: 4.6.0 - version: 4.6.0(@types/react@19.2.7)(immer@11.0.1)(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nextra@4.6.0(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + version: 4.6.0(@types/react@19.2.7)(immer@11.1.3)(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nextra@4.6.0(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) posthog-js: specifier: 1.321.2 version: 1.321.2 @@ -70,7 +70,7 @@ importers: version: 6.0.2 zustand: specifier: 5.0.8 - version: 5.0.8(@types/react@19.2.7)(immer@11.0.1)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + version: 5.0.8(@types/react@19.2.7)(immer@11.1.3)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: '@anthropic-ai/sdk': specifier: ^0.71.2 @@ -2173,6 +2173,9 @@ packages: '@types/d3-shape@3.1.7': resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + '@types/d3-time-format@4.0.3': resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} @@ -2831,8 +2834,8 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-toolkit@1.43.0: - resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==} + es-toolkit@1.44.0: + resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -2892,6 +2895,9 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -3127,8 +3133,8 @@ packages: immer@10.2.0: resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} - immer@11.0.1: - resolution: {integrity: sha512-naDCyggtcBWANtIrjQEajhhBEuL9b0Zg4zmlWK2CzS6xCWSE39/vvf4LqnMjUAWHBhot4m9MHCM/Z+mfWhUkiA==} + immer@11.1.3: + resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==} immutable@3.8.2: resolution: {integrity: sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==} @@ -3953,8 +3959,8 @@ packages: posthog-js@1.321.2: resolution: {integrity: sha512-h5852d9lYmSNjKWvjDkrmO9/awUU3jayNBEoEBUuMAdfDPc4yYYdxBJeDBxYnCFm6RjCLy4O+vmcwuCRC67EXA==} - preact@10.28.0: - resolution: {integrity: sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==} + preact@10.28.2: + resolution: {integrity: sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==} prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} @@ -6269,7 +6275,7 @@ snapshots: dependencies: '@standard-schema/spec': 1.1.0 '@standard-schema/utils': 0.3.0 - immer: 11.0.1 + immer: 11.1.3 redux: 5.0.1 redux-thunk: 3.1.0(redux@5.0.1) reselect: 5.1.1 @@ -7044,6 +7050,10 @@ snapshots: dependencies: '@types/d3-path': 3.1.1 + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + '@types/d3-time-format@4.0.3': {} '@types/d3-time@3.0.4': {} @@ -7723,7 +7733,7 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - es-toolkit@1.43.0: {} + es-toolkit@1.44.0: {} esast-util-from-estree@2.0.0: dependencies: @@ -7817,6 +7827,8 @@ snapshots: eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -8134,7 +8146,7 @@ snapshots: immer@10.2.0: {} - immer@11.0.1: {} + immer@11.1.3: {} immutable@3.8.2: {} @@ -9021,7 +9033,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@4.6.0(@types/react@19.2.7)(immer@11.0.1)(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nextra@4.6.0(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): + nextra-theme-docs@4.6.0(@types/react@19.2.7)(immer@11.1.3)(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nextra@4.6.0(next@16.1.1(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): dependencies: '@headlessui/react': 2.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 @@ -9033,7 +9045,7 @@ snapshots: react-dom: 19.2.3(react@19.2.3) scroll-into-view-if-needed: 3.1.0 zod: 4.0.0-beta.20250424T163858 - zustand: 5.0.8(@types/react@19.2.7)(immer@11.0.1)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + zustand: 5.0.8(@types/react@19.2.7)(immer@11.1.3)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) transitivePeerDependencies: - '@types/react' - immer @@ -9265,11 +9277,11 @@ snapshots: core-js: 3.47.0 dompurify: 3.3.1 fflate: 0.4.8 - preact: 10.28.0 + preact: 10.28.2 query-selector-shadow-dom: 1.0.1 web-vitals: 4.2.4 - preact@10.28.0: {} + preact@10.28.2: {} prismjs@1.30.0: {} @@ -9428,8 +9440,8 @@ snapshots: '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1))(react@19.2.3) clsx: 2.1.1 decimal.js-light: 2.5.1 - es-toolkit: 1.43.0 - eventemitter3: 5.0.1 + es-toolkit: 1.44.0 + eventemitter3: 5.0.4 immer: 10.2.0 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -10227,7 +10239,7 @@ snapshots: '@types/d3-ease': 3.0.2 '@types/d3-interpolate': 3.0.4 '@types/d3-scale': 4.0.9 - '@types/d3-shape': 3.1.7 + '@types/d3-shape': 3.1.8 '@types/d3-time': 3.0.4 '@types/d3-timer': 3.0.2 d3-array: 3.2.4 @@ -10376,10 +10388,10 @@ snapshots: zod@4.1.12: {} - zustand@5.0.8(@types/react@19.2.7)(immer@11.0.1)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): + zustand@5.0.8(@types/react@19.2.7)(immer@11.1.3)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): optionalDependencies: '@types/react': 19.2.7 - immer: 11.0.1 + immer: 11.1.3 react: 19.2.3 use-sync-external-store: 1.6.0(react@19.2.3) diff --git a/public/images/icons/python.svg b/public/images/icons/python.svg new file mode 100644 index 000000000..467b07b26 --- /dev/null +++ b/public/images/icons/python.svg @@ -0,0 +1,265 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/llms.txt b/public/llms.txt index 121e7a700..e533e985c 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -1,4 +1,4 @@ - + # Arcade @@ -69,6 +69,7 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Arcade Cloud Infrastructure](https://docs.arcade.dev/en/guides/deployment-hosting/arcade-cloud.md): This documentation page provides an overview of the infrastructure supporting Arcade Cloud, detailing essential information such as egress IP addresses and the availability of VPC Peering for enterprise customers. Users can learn how to manage traffic and explore options for enhanced connectivity. - [Arcade for Slack](https://docs.arcade.dev/en/resources/integrations/social-communication/slack/install.md): The documentation page for Arcade for Slack provides users with guidance on integrating Arcade's AI tools into their Slack workspace to enhance team efficiency. It outlines the installation process, functionalities such as sending messages, finding information, and generating content, while also advising on the - [Arcade for Zoom](https://docs.arcade.dev/en/resources/integrations/social-communication/zoom/install.md): The "Arcade for Zoom" documentation page provides users with guidance on integrating Arcade's AI tools with their Zoom accounts to enhance meeting management and information retrieval. It outlines the functionalities available, such as listing upcoming meetings and retrieving invitation details, while also addressing +- [Arcade Gateway Assistant](https://docs.arcade.dev/en/guides/mcp-gateways/create-via-ai.md): The Arcade Gateway Assistant documentation guides users in creating and managing MCP Gateways through a chat interface using natural language. It outlines the prerequisites for setup, including creating an Arcade account and connecting compatible chat clients, and provides step-by-step instructions for authentication and gateway - [Arcade Glossary](https://docs.arcade.dev/en/resources/glossary.md): The Arcade Glossary documentation provides definitions and explanations of key terms and concepts related to the Arcade platform, including agents, tools, and MCP servers. It aims to help users understand the components necessary for building, testing, and deploying applications that utilize large language - [Arcade with Agent Frameworks and MCP Clients](https://docs.arcade.dev/en/get-started/agent-frameworks.md): This documentation page provides developers with guidance on integrating Arcade with various agent frameworks and MCP clients to enhance their AI applications. It includes authentication procedures, tool loading, and execution instructions, along with code examples and configuration steps for quick implementation. Users can explore specific - [Arcade with Google ADK](https://docs.arcade.dev/en/get-started/agent-frameworks/google-adk/overview.md): This documentation page provides a comprehensive guide for integrating the `google-adk-arcade` package with the Google ADK library, enabling users to enhance their AI agents with various Arcade tools such as Google Mail and GitHub. It covers installation, key @@ -105,6 +106,7 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Create a new Mastra project](https://docs.arcade.dev/en/get-started/agent-frameworks/mastra/use-arcade-tools.md): This documentation page provides a comprehensive guide for users to create a new Mastra project and integrate Arcade tools into their applications. It covers prerequisites, project setup, installation of the Arcade client, configuration of API keys, and interaction methods with the Mastra agent - [Create an evaluation suite](https://docs.arcade.dev/en/guides/create-tools/evaluate-tools/create-evaluation-suite.md): This documentation page provides a comprehensive guide on creating an evaluation suite to test AI models' tool usage through Arcade. Users will learn how to set up prerequisites, define evaluation files and suites, run evaluations, and interpret results, ensuring accurate tool selection and parameter - [Create an MCP tool with secrets](https://docs.arcade.dev/en/guides/create-tools/tool-basics/create-tool-secrets.md): This documentation page guides users on how to create custom MCP tools that securely handle sensitive information, or "secrets," using the Arcade platform. It covers the process of reading secrets from various sources, such as environment variables and the Arcade Dashboard, and provides +- [Create via Dashboard](https://docs.arcade.dev/en/guides/mcp-gateways/create-via-dashboard.md): This documentation page guides users through the process of creating and configuring MCP Gateways using the Arcade dashboard, providing detailed instructions on selecting tools, setting authentication modes, and customizing gateway settings. It also outlines prerequisites for creating a gateway and offers post-creation steps - [Creating an MCP Server with Arcade](https://docs.arcade.dev/en/guides/create-tools/tool-basics/build-mcp-server.md): This documentation page provides a comprehensive guide for users to create, test, deploy, and publish a custom MCP Server using the Arcade framework. It details the installation of necessary tools, the scaffolding of a server project, and the setup of environment configurations, - [CursorAgentsApi](https://docs.arcade.dev/en/resources/integrations/development/cursor-agents-api.md): The CursorAgentsApi documentation provides users with tools to manage and interact with background agents via the cursor_agents API, including functionalities for listing, inspecting, and deleting agents, retrieving their statuses and conversation histories, and accessing authentication and model recommendations. It serves as - [CustomerioApi](https://docs.arcade.dev/en/resources/integrations/customer-support/customerio-api.md): The CustomerioApi documentation provides users with a comprehensive set of tools to interact with the Customer.io platform, enabling management of customer communications and marketing campaigns. It outlines various functionalities, such as triggering broadcasts, sending transactional messages, and retrieving campaign metrics, all @@ -120,7 +122,6 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Environment Variables](https://docs.arcade.dev/en/resources/integrations/social-communication/slack/environment-variables.md): This documentation page provides guidance on configuring environment variables related to Slack API interactions, specifically `SLACK_MAX_CONCURRENT_REQUESTS`, `MAX_PAGINATION_SIZE_LIMIT`, and `MAX_PAGINATION_TIMEOUT_SECONDS`. Users will learn how to adjust these settings - [Evaluate Tools](https://docs.arcade.dev/en/guides/create-tools/evaluate-tools.md): The "Evaluate Tools" documentation page provides guidance on systematically testing and enhancing tools using Arcade's evaluation framework. It helps users validate the performance of their tools after initial development and offers techniques for iterative improvements to ensure reliability in production. - [ExaApi](https://docs.arcade.dev/en/resources/integrations/search/exa-api.md): The ExaApi documentation provides users with a comprehensive guide to utilizing the Exa.ai Search API, enabling them to conduct searches, manage websets, and handle research requests effectively. It outlines various tools available within the API, detailing their functionalities such as -- [Fetch the "ScrapeUrl" tool from the "Firecrawl" MCP Server](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/use-arcade-tools.md): This documentation page provides a comprehensive guide on integrating Arcade tools into LangGraph applications, detailing prerequisites, environment setup, API key configuration, and tool management. Users will learn how to create and manage AI models, configure agents, and stream responses while leveraging specific - [Figma](https://docs.arcade.dev/en/resources/integrations/development/figma.md): This documentation page provides users with a comprehensive guide to the Figma MCP Server, enabling interaction with Figma's design files, components, and collaboration features through the Figma REST API. Users can learn to access file structures, manage components, add comments - [FigmaApi](https://docs.arcade.dev/en/resources/integrations/productivity/figma-api.md): The FigmaApi documentation provides a comprehensive guide for developers to utilize tools that enable interaction with the Figma API, facilitating efficient management of design assets and collaboration on projects. Users can learn how to perform various actions such as retrieving Figma files, managing - [Firecrawl](https://docs.arcade.dev/en/resources/integrations/development/firecrawl.md): The Firecrawl documentation provides users with a comprehensive guide to utilizing the Arcade Firecrawl MCP Server, enabling them to build agents and AI applications for scraping, crawling, and mapping websites. It outlines available tools, including functionalities for scraping URLs, crawling websites, @@ -165,7 +166,6 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [HubspotUsersApi](https://docs.arcade.dev/en/resources/integrations/sales/hubspot-users-api.md): The HubspotUsersApi documentation provides users with tools to efficiently manage users and teams within a HubSpot account, including functionalities for retrieving user lists, creating and updating user accounts, and removing users. It offers detailed descriptions of available API tools, along with - [Imgflip](https://docs.arcade.dev/en/resources/integrations/entertainment/imgflip.md): The Imgflip documentation provides users with tools to create and manage memes using the Imgflip API, enabling the development of agents and AI applications. Users can search for meme templates, retrieve popular memes, and create custom memes by adding text to existing templates. - [Imgflip](https://docs.arcade.dev/en/resources/integrations/entertainment/spotify/imgflip.md): The Imgflip documentation page provides users with tools to create and manage memes using the Imgflip API, allowing them to search for meme templates, retrieve popular templates, and create custom memes. It outlines the available features, including a premium search option and customizable -- [Import necessary classes and modules](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/user-auth-interrupts.md): This documentation page guides users in creating a LangGraph workflow that incorporates user authorization for specific Arcade tools, ensuring that only authorized tools are accessible to the language model. It provides step-by-step instructions on setting up the environment, defining workflow functions, and compiling - [In Custom Applications](https://docs.arcade.dev/en/guides/tool-calling/custom-apps.md): This documentation page provides guidance on integrating Arcade tools into custom applications, focusing on user authentication, authorization status checking, and managing tool definitions. It serves as a resource for developers building tool-calling interfaces to ensure proper implementation and functionality. - [Initialize the Arcade client](https://docs.arcade.dev/en/get-started/agent-frameworks/google-adk/use-arcade-tools.md): This documentation page provides a comprehensive guide for integrating Arcade tools into Google ADK applications, detailing the necessary prerequisites, environment setup, and configuration steps. Users will learn how to create and manage Arcade tools, authorize them for agents, and run these agents with - [IntercomApi](https://docs.arcade.dev/en/resources/integrations/customer-support/intercom-api.md): The IntercomApi documentation provides a comprehensive guide to tools that enable users to interact with the Intercom platform using OAuth2 authentication. It details various functionalities, such as managing admin information, creating and updating articles, and handling company data, allowing users to @@ -176,7 +176,7 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [LinkedIn](https://docs.arcade.dev/en/resources/integrations/social-communication/linkedin.md): This documentation page provides an overview of the Arcade LinkedIn MCP Server, which enables users to build agents and AI applications that interact with LinkedIn, specifically for creating text posts. It includes details on available tools, authentication methods, and example code for implementing - [LumaApi](https://docs.arcade.dev/en/resources/integrations/productivity/luma-api.md): The LumaApi documentation provides users with tools and guidance for managing events and calendars within the Luma platform via its API. It covers functionalities such as creating and updating events, managing guest information, and handling invitations and coupons, all aimed at enhancing event - [MailchimpMarketingApi](https://docs.arcade.dev/en/resources/integrations/productivity/mailchimp-marketing-api.md): The Mailchimp Marketing API documentation provides users with a set of tools for managing and optimizing their email marketing campaigns through direct interaction with the Mailchimp platform. Users can learn how to retrieve account information, manage audience contacts, create and automate marketing workflows, and -- [MCP Gateways](https://docs.arcade.dev/en/guides/create-tools/mcp-gateways.md): This documentation page provides a comprehensive guide on configuring and using MCP Gateways, which facilitate the connection of multiple MCP Servers to a single agent, application, or IDE. Users will learn how to create and manage MCP Gateways, select tools from different servers +- [MCP Gateways](https://docs.arcade.dev/en/guides/mcp-gateways.md): This documentation page provides guidance on using MCP Gateways to connect multiple MCP servers to an agent, application, or IDE, enabling users to federate tools for streamlined management and access. It outlines the benefits of using MCP Gateways, details the creation process - [Microsoft Teams](https://docs.arcade.dev/en/resources/integrations/social-communication/microsoft-teams.md): This documentation page provides users with a comprehensive guide to the Microsoft Teams MCP Server, enabling them to effectively manage teams, channels, and chats within Microsoft Teams. Users can learn how to retrieve information, send messages, and manage users and interactions, streamlining - [MicrosoftTeams Reference](https://docs.arcade.dev/en/resources/integrations/social-communication/microsoft-teams/reference.md): The MicrosoftTeams Reference documentation provides a comprehensive overview of key enumerations used in the MicrosoftTeams MCP Server, including types for matching criteria and team membership. Users can learn about specific values such as `PARTIAL_ALL`, `EXACT`, and `DIRECT - [Migrate from toolkits to MCP servers](https://docs.arcade.dev/en/guides/create-tools/migrate-toolkits.md): This documentation page provides a comprehensive guide for users looking to migrate their existing Arcade toolkits to the new MCP Server framework. It outlines the necessary changes in terminology, package dependencies, and code structure, while offering step-by-step instructions for updating imports, creating @@ -219,6 +219,7 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Server-Level vs Tool-Level Authorization](https://docs.arcade.dev/en/learn/server-level-vs-tool-level-auth.md): This documentation page explains the differences between server-level authorization (Resource Server auth) and tool-level authorization in Arcade MCP servers, highlighting their roles in securing access to the server and third-party APIs. It provides guidance on when to implement each type of authorization, - [Set your API key](https://docs.arcade.dev/en/get-started/agent-frameworks/openai-agents/user-auth-interrupts.md): This documentation page provides a comprehensive guide on managing user authorization for Arcade tools within OpenAI Agents applications. It outlines the steps to obtain an API key, configure the environment, handle authorization errors, and implement a complete authorization flow. Users will learn how to - [Setup Arcade with LangChain](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain.md): This documentation page guides users on how to integrate Arcade tools into LangChain agents, enabling them to leverage Arcade's capabilities within the LangChain framework. Users will learn to set up their environment, create a LangChain agent, and manage tool authorization and execution +- [Setup Arcade with OpenAI Agents SDK](https://docs.arcade.dev/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents.md): This documentation page provides a comprehensive guide on integrating Arcade tools with the OpenAI Agents SDK to build AI agents. Users will learn how to set up a project, implement a command-line interface (CLI) agent, and manage tool authorization seamlessly. By following - [Sharepoint](https://docs.arcade.dev/en/resources/integrations/productivity/sharepoint.md): This documentation page provides a comprehensive guide for using the SharePoint MCP Server, enabling users to efficiently interact with SharePoint sites and their contents through various tools. Users can learn to retrieve lists, items, pages, and metadata, as well as search for - [Slack](https://docs.arcade.dev/en/resources/integrations/social-communication/slack.md): This documentation page provides users with tools and functionalities to integrate and interact with the Slack platform, enabling efficient management of conversations and user information. It outlines various capabilities, such as retrieving user details, sending messages, and accessing conversation metadata, all aimed at enhancing - [SlackApi](https://docs.arcade.dev/en/resources/integrations/social-communication/slack_api.md): The SlackApi documentation provides a comprehensive guide for administrators and applications to manage and automate various aspects of Slack workspaces, including user management, messaging, channel operations, and file sharing. It outlines key functionalities such as creating teams, managing user profiles, sending diff --git a/scripts/vale-editorial.ts b/scripts/vale-editorial.ts index 46a5d799c..db9346e7d 100644 --- a/scripts/vale-editorial.ts +++ b/scripts/vale-editorial.ts @@ -45,7 +45,7 @@ const MAX_FILES = 5; const MAX_AI_TOKENS = 8192; const OWNER = "ArcadeAI"; const REPO = "docs"; -const EDITORIAL_COMMENT_REGEX = //; +const SUMMARY_DELIMITER = "---SUMMARY---"; const CODE_FENCE_OPEN_REGEX = /^```(?:mdx?|markdown)?\n/; const CODE_FENCE_CLOSE_REGEX = /\n```$/; const HTTP_UNPROCESSABLE_ENTITY = 422; @@ -254,11 +254,35 @@ ${content} If the doc already conforms to the style guide, return exactly: NO_CHANGES_NEEDED If changes are needed to conform to the style guide: -1. Return ONLY the revised markdown content, no explanations +1. Return ONLY the revised markdown content, no explanations or comments 2. Preserve all code blocks exactly as they are 3. Preserve frontmatter exactly as it is -4. Add a brief HTML comment at the top (after frontmatter) citing which style guide sections required changes: - `; +4. After the content, add a delimiter line "---SUMMARY---" followed by a brief summary of which style guide sections required changes + +Example format: +[revised markdown content here] +---SUMMARY--- +Voice and tone - Changed "we" references; Structure - Added intro line`; +} + +// Extract content and summary from AI response +function extractContentAndSummary(rawResponse: string): { + content: string; + summary: string; +} { + const stripped = stripCodeFences(rawResponse); + const delimiterIndex = stripped.lastIndexOf(SUMMARY_DELIMITER); + + if (delimiterIndex === -1) { + return { content: stripped, summary: "Structural improvements" }; + } + + const content = stripped.slice(0, delimiterIndex).trim(); + const summary = + stripped.slice(delimiterIndex + SUMMARY_DELIMITER.length).trim() || + "Structural improvements"; + + return { content, summary }; } // Get editorial suggestions from Anthropic @@ -280,15 +304,14 @@ async function getEditorialFromAnthropic( return null; } - const revisedContent = stripCodeFences(textBlock.text); + const rawResponse = textBlock.text; - if (revisedContent === "NO_CHANGES_NEEDED") { + if (stripCodeFences(rawResponse) === "NO_CHANGES_NEEDED") { return null; } - // Extract summary from the HTML comment if present - const commentMatch = revisedContent.match(EDITORIAL_COMMENT_REGEX); - const summary = commentMatch ? commentMatch[1] : "Structural improvements"; + const { content: revisedContent, summary } = + extractContentAndSummary(rawResponse); return { filename, @@ -312,17 +335,17 @@ async function getEditorialFromOpenAI( messages: [{ role: "user", content: prompt }], }); - const rawContent = response.choices[0]?.message?.content; - if (!rawContent) { + const rawResponse = response.choices[0]?.message?.content; + if (!rawResponse) { return null; } - const revisedContent = stripCodeFences(rawContent); - if (revisedContent === "NO_CHANGES_NEEDED") { + + if (stripCodeFences(rawResponse) === "NO_CHANGES_NEEDED") { return null; } - const commentMatch = revisedContent.match(EDITORIAL_COMMENT_REGEX); - const summary = commentMatch ? commentMatch[1] : "Structural improvements"; + const { content: revisedContent, summary } = + extractContentAndSummary(rawResponse); return { filename, From e6368d9c54cf723667513c76128218f1994b66d0 Mon Sep 17 00:00:00 2001 From: Rachel Lee Nabors Date: Thu, 22 Jan 2026 19:05:36 +0000 Subject: [PATCH 04/19] adding images to the Mastra example --- .../agent-frameworks/mastra/page.mdx | 4 ++++ .../mastra-studio-agent-interaction.png | Bin 0 -> 76869 bytes .../screenshots/mastra-studio-workflow-run.png | Bin 0 -> 61675 bytes 3 files changed, 4 insertions(+) create mode 100644 public/images/screenshots/mastra-studio-agent-interaction.png create mode 100644 public/images/screenshots/mastra-studio-workflow-run.png diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index 35220a910..71aff0b9c 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -349,6 +349,8 @@ On first use, the agent will return an authorization URL. Visit the URL to conne +![Mastra Studio agent interaction](./images/screenshots/mastra-studio-agent-interaction.png) + ## Build a workflow Agents are great for open-ended conversations, but sometimes you want a **deterministic process** that runs the same way every time. Mastra workflows let you chain steps together, with each step's output feeding into the next. @@ -594,6 +596,8 @@ export const mastra = new Mastra({ +![Mastra Studio workflow run](./images/screenshots/mastra-studio-workflow-run.png) + ## Key takeaways - **Arcade tools work seamlessly with Mastra**: Use `toZodToolSet` to convert Arcade tools to the Zod schema format Mastra expects. diff --git a/public/images/screenshots/mastra-studio-agent-interaction.png b/public/images/screenshots/mastra-studio-agent-interaction.png new file mode 100644 index 0000000000000000000000000000000000000000..1984dc563cb00ac14683a1b11ec0afa78d4363bd GIT binary patch literal 76869 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 literal 0 HcmV?d00001 diff --git a/public/images/screenshots/mastra-studio-workflow-run.png b/public/images/screenshots/mastra-studio-workflow-run.png new file mode 100644 index 0000000000000000000000000000000000000000..5d0d15dc9508b78d17371ce65b148439273dcea0 GIT binary patch literal 61675 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%0D4Rlau-^j)bi_-!W3%PYLU6!`uT3J;_ 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

-VPR6Vv7SPrv1 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 Date: Thu, 22 Jan 2026 19:09:45 +0000 Subject: [PATCH 05/19] fix image path --- app/en/get-started/agent-frameworks/mastra/page.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index 71aff0b9c..bb2feb92b 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -349,7 +349,7 @@ On first use, the agent will return an authorization URL. Visit the URL to conne -![Mastra Studio agent interaction](./images/screenshots/mastra-studio-agent-interaction.png) +![Mastra Studio agent interaction](/images/screenshots/mastra-studio-agent-interaction.png) ## Build a workflow @@ -596,7 +596,7 @@ export const mastra = new Mastra({ -![Mastra Studio workflow run](./images/screenshots/mastra-studio-workflow-run.png) +![Mastra Studio workflow run](/images/screenshots/mastra-studio-workflow-run.png) ## Key takeaways From 82a53a57ff59fe154b571281a058b90fe207bd0f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Jan 2026 19:15:16 +0000 Subject: [PATCH 06/19] =?UTF-8?q?=F0=9F=A4=96=20Regenerate=20LLMs.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/llms.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/public/llms.txt b/public/llms.txt index e533e985c..8869d7fbd 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -1,4 +1,4 @@ - + # Arcade @@ -103,7 +103,6 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Connect Arcade to your LLM](https://docs.arcade.dev/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python.md): This documentation page provides a step-by-step guide for integrating Arcade with a Large Language Model (LLM) using Python. It outlines the necessary prerequisites, including API keys and package installations, and teaches users how to create a harness that facilitates communication between the - [Connect to MCP Clients](https://docs.arcade.dev/en/get-started/mcp-clients.md): This documentation page provides guidance on connecting MCP servers to various MCP-compatible clients and development environments, enabling users to enhance their agent workflows. - [Contact Us](https://docs.arcade.dev/en/resources/contact-us.md): This documentation page provides users with information on how to connect with the Arcade team for support through various channels. It aims to facilitate communication and assistance for users and their agents. -- [Create a new Mastra project](https://docs.arcade.dev/en/get-started/agent-frameworks/mastra/use-arcade-tools.md): This documentation page provides a comprehensive guide for users to create a new Mastra project and integrate Arcade tools into their applications. It covers prerequisites, project setup, installation of the Arcade client, configuration of API keys, and interaction methods with the Mastra agent - [Create an evaluation suite](https://docs.arcade.dev/en/guides/create-tools/evaluate-tools/create-evaluation-suite.md): This documentation page provides a comprehensive guide on creating an evaluation suite to test AI models' tool usage through Arcade. Users will learn how to set up prerequisites, define evaluation files and suites, run evaluations, and interpret results, ensuring accurate tool selection and parameter - [Create an MCP tool with secrets](https://docs.arcade.dev/en/guides/create-tools/tool-basics/create-tool-secrets.md): This documentation page guides users on how to create custom MCP tools that securely handle sensitive information, or "secrets," using the Arcade platform. It covers the process of reading secrets from various sources, such as environment variables and the Arcade Dashboard, and provides - [Create via Dashboard](https://docs.arcade.dev/en/guides/mcp-gateways/create-via-dashboard.md): This documentation page guides users through the process of creating and configuring MCP Gateways using the Arcade dashboard, providing detailed instructions on selecting tools, setting authentication modes, and customizing gateway settings. It also outlines prerequisites for creating a gateway and offers post-creation steps @@ -195,9 +194,8 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [page](https://docs.arcade.dev/en/resources/integrations.md): This documentation page provides a comprehensive registry of all MCP Servers within the Arcade ecosystem, helping users to easily identify and access the available servers. - [page](https://docs.arcade.dev/en/get-started/agent-frameworks/crewai/use-arcade-tools.md): This documentation page provides a comprehensive guide on integrating Arcade tools into CrewAI applications, detailing the necessary prerequisites, setup, and configuration steps. Users will learn how to manage tool authorization and effectively utilize these tools within their CrewAI agent teams. Additionally, it - [page](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/auth-langchain-tools.md): This documentation page provides a step-by-step guide on how to authorize existing LangChain tools, such as the `GmailToolkit`, using the Arcade platform. It outlines the prerequisites, installation of necessary packages, and detailed instructions for the authorization process in both -- [page](https://docs.arcade.dev/en/get-started/agent-frameworks/mastra/overview.md): This documentation page provides guidance on integrating Arcade's tool ecosystem with Mastra applications, enabling users to enhance their Mastra agents with access to a variety of pre-built tools and simplified tool management. It outlines the key mechanisms for integration, including tool discovery, -- [page](https://docs.arcade.dev/en/get-started/agent-frameworks/mastra/user-auth-interrupts.md): This documentation page provides guidance on managing user-specific authorization for Arcade tools within Mastra applications, focusing on dynamic tool loading and per-user authentication flows. It outlines the necessary steps to configure agents, create API endpoints, and handle tool authorization effectively, ensuring a - [page](https://docs.arcade.dev/en/resources/integrations/productivity/dropbox/reference.md): This documentation page defines the various item categories used in Dropbox, including types such as image, document, PDF, spreadsheet, presentation, audio, video, folder, and paper. It helps users understand the classification of files within the Dropbox system. +- [page](https://docs.arcade.dev/en/get-started/agent-frameworks/mastra.md): This documentation page guides users in building a TypeScript AI agent and workflow using the Mastra framework and Arcade tools to interact with Gmail and Slack. Users will learn how to create an agent capable of reading emails, sending messages, and summarizing content, - [PagerDuty](https://docs.arcade.dev/en/resources/integrations/customer-support/pagerduty.md): This documentation page provides guidance on using the PagerDuty MCP Server, which enables agents to access and manage incidents, on-call information, services, and teams through read-only API tools. It includes details on OAuth configuration, available tools with descriptions, and code - [PagerdutyApi](https://docs.arcade.dev/en/resources/integrations/development/pagerduty-api.md): The PagerDutyApi documentation provides a comprehensive overview of tools that enable users to manage incidents, services, and integrations within the PagerDuty platform using the API. It outlines various functionalities, such as assigning tags, retrieving metrics, and managing add-ons, allowing - [Postgres](https://docs.arcade.dev/en/resources/integrations/databases/postgres.md): This documentation page provides users with a comprehensive guide to the Arcade Postgres MCP Server, which enables agents to interact with PostgreSQL databases in a read-only manner. Users can learn how to discover database schemas, explore table structures, and execute safe SELECT queries From f7de5133e5569ee2d5ba86b98734a86f70a0f26b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Jan 2026 19:15:57 +0000 Subject: [PATCH 07/19] =?UTF-8?q?=F0=9F=A4=96=20Regenerate=20LLMs.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/llms.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/llms.txt b/public/llms.txt index 8869d7fbd..34a4829f5 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -192,10 +192,10 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [OutlookMail Reference](https://docs.arcade.dev/en/resources/integrations/productivity/outlook-mail/reference.md): The OutlookMail Reference documentation provides a comprehensive list of enumerations, folder names, email filter properties, and filter operators used in the OutlookMail MCP Server. It helps users understand and utilize these elements effectively when working with OutlookMail tools. This reference is - [page](https://docs.arcade.dev/en/resources/examples.md): This documentation page showcases a collection of example applications that utilize Arcade's tools and MCP servers, providing users with practical implementations and templates for various workflows and agent functionalities. Users can explore detailed descriptions and links to GitHub repositories for each app, enabling them to - [page](https://docs.arcade.dev/en/resources/integrations.md): This documentation page provides a comprehensive registry of all MCP Servers within the Arcade ecosystem, helping users to easily identify and access the available servers. +- [page](https://docs.arcade.dev/en/get-started/agent-frameworks/mastra.md): This documentation page guides users in building a TypeScript AI agent and workflow using the Mastra framework and Arcade tools to interact with Gmail and Slack. Users will learn how to create an agent capable of reading emails, sending messages, and summarizing content, - [page](https://docs.arcade.dev/en/get-started/agent-frameworks/crewai/use-arcade-tools.md): This documentation page provides a comprehensive guide on integrating Arcade tools into CrewAI applications, detailing the necessary prerequisites, setup, and configuration steps. Users will learn how to manage tool authorization and effectively utilize these tools within their CrewAI agent teams. Additionally, it - [page](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/auth-langchain-tools.md): This documentation page provides a step-by-step guide on how to authorize existing LangChain tools, such as the `GmailToolkit`, using the Arcade platform. It outlines the prerequisites, installation of necessary packages, and detailed instructions for the authorization process in both - [page](https://docs.arcade.dev/en/resources/integrations/productivity/dropbox/reference.md): This documentation page defines the various item categories used in Dropbox, including types such as image, document, PDF, spreadsheet, presentation, audio, video, folder, and paper. It helps users understand the classification of files within the Dropbox system. -- [page](https://docs.arcade.dev/en/get-started/agent-frameworks/mastra.md): This documentation page guides users in building a TypeScript AI agent and workflow using the Mastra framework and Arcade tools to interact with Gmail and Slack. Users will learn how to create an agent capable of reading emails, sending messages, and summarizing content, - [PagerDuty](https://docs.arcade.dev/en/resources/integrations/customer-support/pagerduty.md): This documentation page provides guidance on using the PagerDuty MCP Server, which enables agents to access and manage incidents, on-call information, services, and teams through read-only API tools. It includes details on OAuth configuration, available tools with descriptions, and code - [PagerdutyApi](https://docs.arcade.dev/en/resources/integrations/development/pagerduty-api.md): The PagerDutyApi documentation provides a comprehensive overview of tools that enable users to manage incidents, services, and integrations within the PagerDuty platform using the API. It outlines various functionalities, such as assigning tags, retrieving metrics, and managing add-ons, allowing - [Postgres](https://docs.arcade.dev/en/resources/integrations/databases/postgres.md): This documentation page provides users with a comprehensive guide to the Arcade Postgres MCP Server, which enables agents to interact with PostgreSQL databases in a read-only manner. Users can learn how to discover database schemas, explore table structures, and execute safe SELECT queries From 347a54ef324ed1b07e6df3a7c1e20d5da84bab2f Mon Sep 17 00:00:00 2001 From: "RL \"Nearest\" Nabors" <236306+nearestnabors@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:16:49 +0000 Subject: [PATCH 08/19] Update app/en/get-started/agent-frameworks/mastra/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/en/get-started/agent-frameworks/mastra/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index bb2feb92b..cf590e740 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -56,7 +56,7 @@ Before diving into the code, here are the key Mastra concepts you'll use: npx create-mastra@latest arcade-agent ``` -Select your preferred model provider when prompted (OpenAI is recommended). Enter your API key when asked. +Select your preferred model provider when prompted (we recommend OpenAI). Enter your API key when asked. Then navigate to the project directory and install the Arcade client: From 20b1d8161cf90ee1f6cfa1b829fbf07c8e4a69d1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 22 Jan 2026 19:22:45 +0000 Subject: [PATCH 09/19] Fix inconsistent userId defaults and tool name format in Mastra tutorial - Changed workflow default userId from empty string to 'default-user' to match agent behavior - Fixed Gmail tool name from 'Gmail.ListEmails' (dot) to 'Gmail_ListEmails' (underscore) to match tool configuration --- app/en/get-started/agent-frameworks/mastra/page.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index cf590e740..f3bf576a5 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -376,7 +376,7 @@ import { createStep, createWorkflow } from "@mastra/core/workflows"; import { z } from "zod"; import { Arcade } from "@arcadeai/arcadejs"; -const defaultUserId = process.env.ARCADE_USER_ID || ""; +const defaultUserId = process.env.ARCADE_USER_ID || "default-user"; // Step 1: Fetch emails from Gmail const fetchEmails = createStep({ @@ -401,7 +401,7 @@ const fetchEmails = createStep({ try { const result = await arcade.tools.execute({ - tool_name: "Gmail.ListEmails", + tool_name: "Gmail_ListEmails", user_id: userId, input: { n_emails: inputData!.maxEmails ?? 5 }, }); @@ -788,7 +788,7 @@ import { createStep, createWorkflow } from "@mastra/core/workflows"; import { z } from "zod"; import { Arcade } from "@arcadeai/arcadejs"; -const defaultUserId = process.env.ARCADE_USER_ID || ""; +const defaultUserId = process.env.ARCADE_USER_ID || "default-user"; const fetchEmails = createStep({ id: "fetch-emails", @@ -812,7 +812,7 @@ const fetchEmails = createStep({ try { const result = await arcade.tools.execute({ - tool_name: "Gmail.ListEmails", + tool_name: "Gmail_ListEmails", user_id: userId, input: { n_emails: inputData!.maxEmails ?? 5 }, }); From 790dbebaf6655fcac10e2356136e8c87826f97a1 Mon Sep 17 00:00:00 2001 From: "RL \"Nearest\" Nabors" <236306+nearestnabors@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:26:39 +0000 Subject: [PATCH 10/19] Update app/en/get-started/agent-frameworks/mastra/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/en/get-started/agent-frameworks/mastra/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index f3bf576a5..3788eca94 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -92,7 +92,7 @@ yarn add @arcadeai/arcadejs @ai-sdk/openai zod@3 - We explicitly install `zod@3` because the Arcade SDK's `toZodToolSet` currently requires Zod 3.x. Zod 4 has a different internal API that isn't yet supported. +These commands explicitly install `zod@3` because the Arcade SDK's `toZodToolSet` currently requires Zod 3.x. Zod 4 has a different internal API that isn't yet supported. ### Set up environment variables From 391629f9f1f2bc5e4bfc8582ae4ca67009426217 Mon Sep 17 00:00:00 2001 From: "RL \"Nearest\" Nabors" <236306+nearestnabors@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:26:52 +0000 Subject: [PATCH 11/19] Update app/en/get-started/agent-frameworks/mastra/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/en/get-started/agent-frameworks/mastra/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index 3788eca94..37e2cd258 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -111,7 +111,7 @@ The `ARCADE_USER_ID` is your app's internal identifier for the user (often the e Create **src/mastra/tools/arcade.ts** to handle Arcade tool fetching and conversion. - **Handling large tool outputs:** Tools like `Gmail.ListEmails` can return 200KB+ of email content. When this data is passed back to the LLM in the agentic loop, it can exceed token limits and cause rate limit errors. The code below includes output truncation to prevent this. + **Handling large tool outputs:** Tools like `Gmail.ListEmails` can return 200KB+ of email content. When the agent passes this data back to the LLM in the agentic loop, it may exceed token limits, resulting in rate limit errors. The code below includes output truncation to prevent this. ```ts filename="src/mastra/tools/arcade.ts" From 0c7f650e58b96256a89176a8a4de57093f74b168 Mon Sep 17 00:00:00 2001 From: "RL \"Nearest\" Nabors" <236306+nearestnabors@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:27:01 +0000 Subject: [PATCH 12/19] Update app/en/get-started/agent-frameworks/mastra/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/en/get-started/agent-frameworks/mastra/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index 37e2cd258..18ff7ea4b 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -212,7 +212,7 @@ export async function getArcadeTools(userId: string) { ##### Tool fetching -- `mcpServers`: Fetches *all* tools from an MCP server. Use this when you want everything a service offers (e.g., `"Slack"` gives you `Slack_SendMessage`, `Slack_ListChannels`, `Slack_ListUsers`, etc.) +- `mcpServers`: Fetches *all* tools from an MCP server. Use this when you want everything a service offers (for example, `"Slack"` gives you `Slack_SendMessage`, `Slack_ListChannels`, `Slack_ListUsers`, etc.) - `individualTools`: Fetches specific tools by name. Use this to cherry-pick only what you need (e.g., `"Gmail_ListEmails"` without `Gmail_DeleteEmail` or other tools you don't want exposed) There are a few reasons you might want to select your tools individually. From acd636257de09ae67f2c68025169f240a1ba608f Mon Sep 17 00:00:00 2001 From: "RL \"Nearest\" Nabors" <236306+nearestnabors@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:27:11 +0000 Subject: [PATCH 13/19] Update app/en/get-started/agent-frameworks/mastra/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/en/get-started/agent-frameworks/mastra/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index 18ff7ea4b..9c36141aa 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -213,7 +213,7 @@ export async function getArcadeTools(userId: string) { ##### Tool fetching - `mcpServers`: Fetches *all* tools from an MCP server. Use this when you want everything a service offers (for example, `"Slack"` gives you `Slack_SendMessage`, `Slack_ListChannels`, `Slack_ListUsers`, etc.) -- `individualTools`: Fetches specific tools by name. Use this to cherry-pick only what you need (e.g., `"Gmail_ListEmails"` without `Gmail_DeleteEmail` or other tools you don't want exposed) +- `individualTools`: Fetches specific tools by name. Use this to cherry-pick only what you need (for example, `"Gmail_ListEmails"` without `Gmail_DeleteEmail` or other tools you don't want exposed) There are a few reasons you might want to select your tools individually. From d444a095708f73044e61e17c9bdb8123d9a56583 Mon Sep 17 00:00:00 2001 From: "RL \"Nearest\" Nabors" <236306+nearestnabors@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:27:24 +0000 Subject: [PATCH 14/19] Update app/en/get-started/agent-frameworks/mastra/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/en/get-started/agent-frameworks/mastra/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index 9c36141aa..d2cba8db1 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -215,7 +215,7 @@ export async function getArcadeTools(userId: string) { - `mcpServers`: Fetches *all* tools from an MCP server. Use this when you want everything a service offers (for example, `"Slack"` gives you `Slack_SendMessage`, `Slack_ListChannels`, `Slack_ListUsers`, etc.) - `individualTools`: Fetches specific tools by name. Use this to cherry-pick only what you need (for example, `"Gmail_ListEmails"` without `Gmail_DeleteEmail` or other tools you don't want exposed) -There are a few reasons you might want to select your tools individually. +You might select your tools individually for a few reasons: * **Security** You may not want to expose all the tools a service offers, for instance `Gmail_DeleteEmail` is not necessary and could even be dangerous to expose to an agent designed to summarize emails. * **Cost** Each tool's schema consumes tokens. Loading all Gmail tools (~20 tools) uses significantly more tokens than loading just the 3 you need. This matters for rate limits and cost. From 2475e5709c2e5148d716baf3b5d92bb2cc6bed3c Mon Sep 17 00:00:00 2001 From: "RL \"Nearest\" Nabors" <236306+nearestnabors@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:27:48 +0000 Subject: [PATCH 15/19] Update app/en/get-started/agent-frameworks/mastra/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/en/get-started/agent-frameworks/mastra/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index d2cba8db1..53936ad64 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -753,7 +753,7 @@ IMPORTANT: When a tool returns an authorization response with a URL, tell the us model: openai("gpt-4o"), tools: arcadeTools, memory, - // Filter out tool results from memory (they can be huge) and limit tokens + // Filter out tool results from memory (they can be large) and limit tokens inputProcessors: [new ToolCallFilter(), new TokenLimiterProcessor({ limit: 50000 })], }); ``` From 3c3a580b1f3d09e31013642ea20eb8f2a4317818 Mon Sep 17 00:00:00 2001 From: "RL \"Nearest\" Nabors" <236306+nearestnabors@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:28:00 +0000 Subject: [PATCH 16/19] Update app/en/get-started/agent-frameworks/mastra/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/en/get-started/agent-frameworks/mastra/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index 53936ad64..49c910720 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -218,7 +218,7 @@ export async function getArcadeTools(userId: string) { You might select your tools individually for a few reasons: * **Security** You may not want to expose all the tools a service offers, for instance `Gmail_DeleteEmail` is not necessary and could even be dangerous to expose to an agent designed to summarize emails. -* **Cost** Each tool's schema consumes tokens. Loading all Gmail tools (~20 tools) uses significantly more tokens than loading just the 3 you need. This matters for rate limits and cost. +* **Cost** Each tool's schema consumes tokens. Loading all Gmail tools (~20 tools) uses more tokens than loading just the 3 you need. This matters for rate limits and cost. Browse the [complete MCP server catalog](/resources/integrations) to see available servers and their tools. From b4b5a96bfa966752e43bf89159c605306c3f6028 Mon Sep 17 00:00:00 2001 From: "RL \"Nearest\" Nabors" <236306+nearestnabors@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:28:15 +0000 Subject: [PATCH 17/19] Update app/en/get-started/agent-frameworks/mastra/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/en/get-started/agent-frameworks/mastra/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index 49c910720..d19ba11bc 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -287,7 +287,7 @@ IMPORTANT: When a tool returns an authorization response with a URL, tell the us model: openai("gpt-4o"), tools: arcadeTools, memory, - // Filter out tool results from memory (they can be huge) and limit tokens + // Filter out tool results from memory (they can be large) and limit tokens inputProcessors: [new ToolCallFilter(), new TokenLimiterProcessor({ limit: 50000 })], }); ``` From 923a28cc7941b95badc14c04bf944d3f0a7cf183 Mon Sep 17 00:00:00 2001 From: "RL \"Nearest\" Nabors" <236306+nearestnabors@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:34:54 +0000 Subject: [PATCH 18/19] Update app/en/get-started/agent-frameworks/mastra/page.mdx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- app/en/get-started/agent-frameworks/mastra/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index d19ba11bc..168d73d7d 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -134,7 +134,7 @@ const MAX_STRING_CHARS = 300; /** * Recursively truncates all large strings in objects/arrays. - * This prevents token overflow when tool results are passed back to the LLM. + * This prevents token overflow when tool results pass back to the LLM. */ function truncateDeep(obj: unknown): unknown { if (obj === null || obj === undefined) return obj; From 8ebed0300cb237aedd66cca42e6bfedb8aad7a72 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 22 Jan 2026 19:44:32 +0000 Subject: [PATCH 19/19] Remove redundant key takeaway about agents vs workflows --- app/en/get-started/agent-frameworks/mastra/page.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index 168d73d7d..0decca491 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -605,7 +605,6 @@ export const mastra = new Mastra({ - **Truncate large outputs**: Tools like Gmail can return 200KB+ of data. Wrap tool execution with truncation to prevent token overflow in the agentic loop. - **Authorization is automatic**: The `executeOrAuthorizeZodTool` factory handles auth flows. When a tool needs authorization, it returns a URL for the user to visit. - **Workflows need explicit auth handling**: Unlike agents, workflows don't have built-in auth handling. Catch 403 errors, call `arcade.auth.start()`, and pass the auth URL through your workflow steps. -- **Use agents for conversation, workflows for automation**: Agents handle open-ended requests; workflows handle repeatable, deterministic processes. ## Next steps