This guide shows you how to build a Gmail agent using LangGraph with Arcade's MCP servers and authentication infrastructure. The agent will handle email operations through OAuth 2.0 with proper user-specific permissions.
What You'll Build
An agent that can:
- List and read Gmail messages
- Send emails from user accounts
- Search email threads
- Manage labels and folders
- Handle multi-user scenarios with isolated credentials
Prerequisites
Before starting, set up your development environment with these requirements:
- Python 3.11+ installed
- An Arcade API key
- Google Cloud Console project with OAuth 2.0 credentials
- Basic Python async/await knowledge
Step 1: Install Required Packages
Install the Arcade integration for agents and the necessary dependencies:
pip install langchain-arcade langchain-openai langgraph
Configure your API keys in your environment:
export ARCADE_API_KEY="your_arcade_api_key"
export OPENAI_API_KEY="your_openai_api_key"
Get an Arcade API key from the Arcade dashboard.
Step 2: Configure Google OAuth
Set up Google authentication to allow your agent to access Gmail on behalf of users.
Create Google OAuth Credentials
Follow these steps to create OAuth credentials:
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable the Gmail API for your project
- Navigate to "APIs & Services" > "Credentials"
- Click "Create Credentials" > "OAuth client ID"
- Choose "Web application" as the application type
- Add authorized redirect URIs (Arcade will generate this)
- Download the client ID and secret
Add Google Provider to Arcade
Configure your Google OAuth provider in Arcade's dashboard:
- Navigate to OAuth > Providers
- Click "Add OAuth Provider"
- Select "Google" from the dropdown
- Enter your Client ID and Client Secret
- Note the generated redirect URL
- Add this redirect URL to your Google Cloud Console authorized URIs
The minimum required scopes are:
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/gmail.modify
For detailed configuration steps, see Arcade's Google auth documentation.
Step 3: Load Gmail Tools from Arcade
Use Arcade's tool manager to fetch the Gmail MCP server:
from langchain_arcade import ArcadeToolManager
import os
# Initialize Arcade tool manager
arcade_api_key = os.environ.get("ARCADE_API_KEY")
manager = ArcadeToolManager(api_key=arcade_api_key)
# Get all Gmail tools
gmail_tools = manager.get_tools(toolkits=["Gmail"])
# View available tools
print(f"Loaded {len(manager.tools)} Gmail tools:")
for tool in manager.tools:
print(f" - {tool.name}")
This loads Arcade's pre-built Gmail MCP server with tools for:
Gmail.ListEmails- Retrieve messages from inboxGmail.SendEmail- Send emails with attachmentsGmail.SearchThreads- Search by keywordsGmail.WriteDraftEmail- Create draft messagesGmail.DeleteEmail- Remove messagesGmail.TrashEmail- Move messages to trash
View all available tools in the Arcade MCP servers documentation.
Step 4: Create the LangGraph Agent
Build a ReAct-style agent that can use Gmail tools:
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
# Configure the language model
openai_api_key = os.environ.get("OPENAI_API_KEY")
model = ChatOpenAI(model="gpt-4o", api_key=openai_api_key)
# Bind tools to the model
bound_model = model.bind_tools(gmail_tools)
# Add memory for conversation context
memory = MemorySaver()
# Create the agent
agent = create_react_agent(
model=bound_model,
tools=gmail_tools,
checkpointer=memory
)
The agent uses a reasoning loop to select appropriate tools based on user requests.
Step 5: Handle User Authorization
When a user invokes Gmail tools for the first time, Arcade triggers an OAuth authorization flow. Handle this with proper error catching:
from langgraph.errors import NodeInterrupt
def run_gmail_agent(user_id: str, query: str):
"""Execute agent with authorization handling."""
config = {
"configurable": {
"thread_id": f"gmail_thread_{user_id}",
"user_id": user_id
}
}
user_input = {
"messages": [("user", query)]
}
try:
# Stream agent responses
for chunk in agent.stream(user_input, config, stream_mode="values"):
chunk["messages"][-1].pretty_print()
except NodeInterrupt as exc:
# Authorization required
print(f"\n⚠️ Authorization Required")
print(f"Visit this URL to grant Gmail access:")
print(exc.args[0].get("url"))
print("\nRun the agent again after authorizing.")
The user_id parameter ensures each user maintains separate OAuth credentials. Arcade handles token storage, refresh, and rotation automatically.
See the complete example in Arcade's repository.
Step 6: Build Agent Capabilities
List Recent Emails
# User query
query = "List my 10 most recent emails and summarize the important ones."
# Agent automatically selects Gmail.ListEmails tool
run_gmail_agent(user_id="user@example.com", query=query)
The agent retrieves messages and formats them for readability.
Send Emails
query = """
Send an email to team@example.com with:
- Subject: Weekly Update
- Body: Project completed successfully. Details attached.
"""
run_gmail_agent(user_id="user@example.com", query=query)
The agent uses Gmail.SendEmail with the authenticated user's credentials.
Search and Filter
query = "Find all emails from john@company.com in the last week about the project."
run_gmail_agent(user_id="user@example.com", query=query)
The agent applies Gmail.SearchThreads with appropriate filters.
Step 7: Manage Multiple Users
Each user needs isolated OAuth credentials. Arcade handles this through the user_id parameter:
# User A's session
run_gmail_agent(
user_id="alice@company.com",
query="Check my inbox"
)
# User B's session (completely separate credentials)
run_gmail_agent(
user_id="bob@company.com",
query="Send an email to the team"
)
Tokens never mix between users. See Arcade's authorization documentation for details on the security model.
Step 8: Custom Authorization Flows
For web applications, implement custom authorization handling:
from arcadepy import Arcade
async def handle_gmail_authorization(user_id: str):
"""Initiate OAuth flow for a user."""
client = Arcade(api_key=arcade_api_key)
# Start authorization
auth_response = await client.auth.start(
user_id=user_id,
provider="google",
scopes=["https://www.googleapis.com/auth/gmail.modify"]
)
if auth_response.status != "completed":
# Return URL to user for OAuth consent
return {
"authorization_required": True,
"url": auth_response.url
}
# Wait for completion
await client.auth.wait_for_completion(auth_response)
return {"authorized": True}
Display the authorization URL in your web interface and retry the tool execution after the user completes OAuth.
Step 9: Extend with Multiple Toolkits
Combine Gmail with other Arcade MCP servers:
# Load multiple toolkits
tools = manager.get_tools(toolkits=[
"Gmail",
"Google Calendar",
"Slack",
"GitHub"
])
# Create agent with all tools
multi_toolkit_agent = create_react_agent(
model=bound_model,
tools=tools,
checkpointer=memory
)
# Agent can now coordinate across tools
query = """
Check my calendar for tomorrow's meetings,
then email the attendees a reminder about the 2pm standup.
"""
run_agent(user_id="user@example.com", query=query)
Browse all available MCP servers in the Arcade toolkits directory.
Step 10: Build Custom Gmail Tools
Create specialized tools using Arcade's Tool Development Kit:
from arcade_tdk import ToolContext, tool
from arcade_tdk.auth import Google
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
@tool(
requires_auth=Google(
scopes=["https://www.googleapis.com/auth/gmail.send"]
)
)
async def send_weekly_report(
context: ToolContext,
recipient: str,
report_data: dict
) -> str:
"""Send a formatted weekly report via Gmail."""
# Access user's Gmail with their token
credentials = Credentials(context.authorization.token)
gmail = build("gmail", "v1", credentials=credentials)
# Generate email body
html_body = format_report_html(report_data)
# Create and send message
message = create_html_message(
to=recipient,
subject="Weekly Report",
html_body=html_body
)
result = gmail.users().messages().send(
userId="me",
body=message
).execute()
return f"Report sent: {result['id']}"
Deploy custom tools to the Arcade Registry for reuse across projects.
Production Deployment
Self-Host Arcade Engine
For production environments, deploy Arcade Engine on your infrastructure:
# Install Arcade Engine
pip install arcade-ai
# Configure engine
arcade-engine init
# Start engine and workers
arcade-engine start
arcade-worker start
The engine serves as your MCP server and handles tool execution. Configure it with your OAuth providers in engine.yaml:
auth:
providers:
- id: google-prod
type: oauth2
provider_id: google
client_id: ${GOOGLE_CLIENT_ID}
client_secret: ${GOOGLE_CLIENT_SECRET}
api:
host: 0.0.0.0
port: 9099
See Arcade's deployment documentation for Kubernetes and Docker configurations.
Monitor Agent Performance
Track tool execution and authorization flows:
import logging
logging.basicConfig(level=logging.INFO)
# Arcade tools automatically log execution
run_gmail_agent(
user_id="user@example.com",
query="Check my emails"
)
# View logs:
# INFO:arcade:Tool execution: Gmail.ListEmails
# INFO:arcade:Authorization: completed
# INFO:arcade:Response: 10 messages retrieved
Use these logs to monitor authorization success rates and tool performance.
Best Practices
Follow these guidelines when building Gmail agents:
Security
- Never hardcode API keys in source code
- Use environment variables for all credentials
- Implement custom user verifiers for production
- Each user maintains separate OAuth tokens through Arcade
Performance
- Cache tool definitions to reduce initialization time
- Use streaming responses for long-running operations
- Limit tool counts per agent (5-10 tools maximum)
- Select specific tools rather than entire toolkits when possible
Authorization
- Handle
NodeInterruptexceptions gracefully - Display clear instructions for OAuth consent
- Implement retry logic after authorization completion
- Test authorization flows with multiple users
Tool Selection
- Choose relevant tools only - avoid loading unnecessary toolkits
- Be aware of duplicate functionality across tools
- Customize agent instructions to guide tool selection
TypeScript Implementation
Build the same agent in TypeScript:
import { Arcade } from "@arcadeai/arcadejs";
import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib";
import { ChatOpenAI } from "@langchain/openai";
import { MemorySaver } from "@langchain/langgraph";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
// Initialize Arcade
const arcade = new Arcade();
// Get Gmail tools
const gmailToolkit = await arcade.tools.list({
toolkit: "Gmail",
limit: 30
});
// Convert to Zod format for LangGraph
const tools = gmailToolkit.items.map(tool =>
toZod({
tool,
client: arcade,
userId: "user@example.com",
executeFactory: executeOrAuthorizeZodTool
})
);
// Create agent
const model = new ChatOpenAI({ model: "gpt-4o" });
const boundModel = model.bindTools(tools);
const memory = new MemorySaver();
const agent = createReactAgent({
llm: boundModel,
tools,
checkpointer: memory
});
// Run agent
const result = await agent.invoke({
messages: [{ role: "user", content: "List my recent emails" }]
}, {
configurable: {
thread_id: "gmail_thread_1",
user_id: "user@example.com"
}
});
See the complete TypeScript example in Arcade's repository.
Additional Resources
Continue learning about Arcade and agent development:
- Arcade Documentation
- Arcade API Reference
- Arcade GitHub Repository
- MCP Servers Catalog
- Tool Development Kit
- Sample Applications
Next Steps
Expand your agent's capabilities:
- Add Google Calendar integration for scheduling
- Combine with Slack tools for notifications
- Build custom tools for internal APIs
- Deploy to production with Arcade Engine
- Explore other frameworks: CrewAI, Mastra, or Vercel AI SDK
This architecture scales from prototype to production while maintaining proper security boundaries. Arcade handles the OAuth complexity, allowing you to focus on building agent capabilities.



