diff --git a/app/en/get-started/agent-frameworks/mastra/page.mdx b/app/en/get-started/agent-frameworks/mastra/page.mdx index 0decca491..9ba330c37 100644 --- a/app/en/get-started/agent-frameworks/mastra/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/page.mdx @@ -7,7 +7,7 @@ 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. +You'll build an agent that 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'll also build a workflow that summarizes emails and sends them to Slack. @@ -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 (we recommend OpenAI). Enter your API key when asked. +Select your preferred model provider when prompted (the guide recommends OpenAI). Enter your API key when asked. Then navigate to the project directory and install the Arcade client: @@ -233,7 +233,7 @@ You might select your tools individually for a few reasons: ### Output handling -- `truncateDeep`: Recursively limits all strings to 300 characters to prevent token overflow when tool results are passed back to the LLM +- `truncateDeep`: Recursively limits all strings to 300 characters to prevent token overflow when tool results pass back to the LLM ### Create the agent @@ -415,7 +415,7 @@ const fetchEmails = createStep({ return { emails, userId }; } catch (error: any) { - // Handle authorization required error + // Handle authorization needed error if (error.status === 403 || error.message?.includes("authorization")) { const authResponse = await arcade.auth.start({ user_id: userId, @@ -495,11 +495,11 @@ const sendToSlack = createStep({ execute: async ({ inputData }) => { const { summary, userId, authRequired, authUrl } = inputData!; - // Return auth URL if authorization is needed + // Return auth URL if authorization needs to be completed if (authRequired) { return { success: false, - message: `Authorization required. Please visit this URL to grant access: ${authUrl}`, + message: `Authorization must be completed. Please visit this URL to grant access: ${authUrl}`, authUrl, }; } @@ -528,7 +528,7 @@ const sendToSlack = createStep({ return { success: true, message: "Digest sent as DM" }; } catch (error: any) { - // Handle Slack authorization required + // Handle Slack authorization needed if (error.status === 403 || error.message?.includes("authorization")) { const slackAuth = await arcade.auth.start({ user_id: userId, @@ -537,7 +537,7 @@ const sendToSlack = createStep({ }); return { success: false, - message: `Slack authorization required. Please visit: ${slackAuth.url}`, + message: `Slack authorization must be completed. Please visit: ${slackAuth.url}`, authUrl: slackAuth.url, }; } @@ -591,7 +591,7 @@ export const mastra = new Mastra({ 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. +3. If authorization needs to be completed, the workflow returns an auth URL. Visit the URL, complete authorization, then run the workflow again. 4. Check your Slack DMs for the digest. @@ -603,7 +603,7 @@ export const mastra = new Mastra({ - **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. +- **Authorization is automatic**: The `executeOrAuthorizeZodTool` factory handles auth flows. When a tool must be authorized, 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. ## Next steps @@ -613,7 +613,7 @@ export const mastra = new Mastra({ - **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. + **Building a multi-user app?** This tutorial uses a single `ARCADE_USER_ID`. 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 @@ -896,77 +896,4 @@ const sendToSlack = createStep({ 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(); - - 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().default(defaultUserId), - maxEmails: z.number().default(5), - }), - outputSchema: z.object({ - success: z.boolean(), - message: z.string(), - authUrl: z.string().optional(), - }), -}) - .then(fetchEmails) - .then(summarizeEmails) - .then(sendToSlack); - -emailDigestWorkflow.commit(); - -export { emailDigestWorkflow }; -``` - - + authUrl: z.string().optional(), \ No newline at end of file