Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 12 additions & 85 deletions app/en/get-started/agent-frameworks/mastra/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<GuideOverview>
<GuideOverview.Outcomes>
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
};
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
};
}
Expand Down Expand Up @@ -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.

</Steps>
Expand All @@ -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
Expand All @@ -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.

<Callout type="info">
**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.
</Callout>

## Complete code
Expand Down Expand Up @@ -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 };
```

</details>
authUrl: z.string().optional(),