How to Build an Open Agents SDK Gmail Agent with Arcade (MCP)

How to Build an Open Agents SDK Gmail Agent with Arcade (MCP)

Arcade.dev Team's avatar
Arcade.dev Team
OCTOBER 21, 2025
6 MIN READ
TUTORIALS
Rays decoration image
Ghost Icon

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:

  1. Go to Google Cloud Console
  2. Create a new project or select an existing one
  3. Enable the Gmail API for your project
  4. Navigate to "APIs & Services" > "Credentials"
  5. Click "Create Credentials" > "OAuth client ID"
  6. Choose "Web application" as the application type
  7. Add authorized redirect URIs (Arcade will generate this)
  8. Download the client ID and secret

Add Google Provider to Arcade

Configure your Google OAuth provider in Arcade's dashboard:

  1. Navigate to OAuth > Providers
  2. Click "Add OAuth Provider"
  3. Select "Google" from the dropdown
  4. Enter your Client ID and Client Secret
  5. Note the generated redirect URL
  6. 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 inbox
  • Gmail.SendEmail - Send emails with attachments
  • Gmail.SearchThreads - Search by keywords
  • Gmail.WriteDraftEmail - Create draft messages
  • Gmail.DeleteEmail - Remove messages
  • Gmail.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 NodeInterrupt exceptions 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:

Next Steps

Expand your agent's capabilities:

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.

SHARE THIS POST

RECENT ARTICLES

Rays decoration image
THOUGHT LEADERSHIP

Enterprise MCP Guide For Retail Banking & Payments: Use Cases, Best Practices, and Trends

The global payments industry processes $2.0 quadrillion in value flows annually, generating $2.5 trillion in revenue. Yet despite decades of digital transformation investment, critical banking operations,anti-money laundering investigation, KYC onboarding, payment reconciliation,remain largely manual. Model Context Protocol (MCP) represents the infrastructure breakthrough that enables financial institutions to move beyond chatbot pilots to production-grade AI agents that take multi-user authoriz

Rays decoration image
THOUGHT LEADERSHIP

Enterprise MCP Guide For Capital Markets & Trading: Use Cases, Best Practices, and Trends

Capital markets technology leaders face a critical infrastructure challenge: scattered AI pilots, disconnected integrations, and fragmented, domain-specific systems that turn engineers into human APIs manually stitching together trading platforms, market data feeds, and risk management tools. The Model Context Protocol (MCP) represents a fundamental shift from this costly one-off integration approach to a universal standardization layer that acts as the backbone for AI-native financial enterpris

Rays decoration image
THOUGHT LEADERSHIP

Enterprise MCP Guide For InsurTech: Use Cases, Best Practices, and Trends

The insurance industry faces a pivotal transformation moment. Model Context Protocol (MCP) has moved from experimental technology to production infrastructure, with 16,000+ active servers deployed across enterprises and millions of weekly SDK downloads. For InsurTech leaders, the question is no longer whether to adopt MCP, but how to implement it securely and effectively. Arcade's platform provides the MCP runtime for secure, multi-user authorization so AI agents can act on behalf of users acros

Blog CTA Icon

Get early access to Arcade, and start building now.