Skip to content

Connect AG2 Agents to 8,000+ Tools with Arcade.dev

AG2 x Arcade.dev

Overview#

Arcade.dev is a tool execution runtime that provides 8,000+ pre-built integrations with just-in-time authorization. It sits between your agents and enterprise systems: your agent calls a tool, the user authenticates once through Arcade, and Arcade handles token management, scope negotiation, and credential storage from that point on. No service accounts, no secrets in your codebase.

This solves common problems when connecting agents to external services:

  • Building and maintaining OAuth flows for each API (Gmail, Linear, Slack, etc.)
  • Managing token lifecycle: refresh, rotation, expiry
  • Storing credentials securely and isolating them from the LLM layer
  • Handling scope negotiation and incremental authorization

In this post, we'll build a Gmail assistant using AG2 and the Arcade SDK. The agent will list, search, read threads, send, reply, and archive emails using AG2's AssistantAgent + UserProxyAgent, pattern while Arcade handles all the authentication and API complexity behind the scenes.

How It Works#

Your AG2 agent calls tools by name. Arcade routes each call to the right service with the right credentials:

AG2 x Arcade.dev diagram

AG2 handles agent orchestration. Arcade handles tool infrastructure. Each does what it's best at.

Building a Gmail Agent#

You'll need an OpenAI API key, an Arcade API key, and the email you used to register at arcade.dev.

Installation#

pip install "ag2[openai]" arcadepy

Step 1: Initialize Arcade and the tool helper#

The call_arcade_tool helper handles authorization automatically: if a tool hasn't been authorized yet, it prints a URL for the user to visit, then waits for completion.

main.py
import os
from arcadepy import Arcade

arcade = Arcade(api_key=os.environ["ARCADE_API_KEY"])
ARCADE_USER_ID = os.environ["ARCADE_USER_ID"]

def call_arcade_tool(tool_name: str, inputs: dict) -> dict:
    """Authorize (if needed) and execute an Arcade tool, returning the output."""
    auth = arcade.tools.authorize(tool_name=tool_name, user_id=ARCADE_USER_ID)
    if auth.status != "completed":
        print(f"\n[Auth required] Visit this URL to authorize {tool_name}:\n  {auth.url}\n")
        arcade.auth.wait_for_completion(auth.id)

    result = arcade.tools.execute(
        tool_name=tool_name,
        input=inputs,
        user_id=ARCADE_USER_ID,
    )
    if not result.output:
        return {}
    data = result.output.model_dump()
    return data.get("value") or {}

The first time a tool is called, Arcade sends the user an OAuth link. After that, tokens are cached automatically.

Step 2: Create tool wrappers for AG2#

We create thin wrappers that call call_arcade_tool with the Arcade tool name and inputs. The full example includes 11 tools (search, archive, trash, reply, drafts, and more). Here are three of the core ones:

main.py
def list_emails(
    sender: str = None, max_results: int = 50, is_unread: bool = False
) -> dict:
    """List emails from Gmail inbox.
    - sender: filter by sender email address (optional)
    - max_results: maximum number of emails to return (default 50)
    - is_unread: if True, return only unread emails
    """
    inputs: dict = {"max_results": max_results, "is_unread": is_unread}
    if sender:
        inputs["sender"] = sender
    return call_arcade_tool("Gmail.ListEmails", inputs)

def get_thread(thread_id: str) -> dict:
    """Fetch the full conversation thread by thread ID."""
    return call_arcade_tool("Gmail.GetThread", {"thread_id": thread_id})

def send_email(to: str, subject: str, body: str) -> dict:
    """Send a new email.
    - to: recipient email address
    - subject: email subject line
    - body: plain-text email body
    """
    return call_arcade_tool(
        "Gmail.SendEmail", {"recipient": to, "subject": subject, "body": body}
    )

Step 3: Wire up the AG2 agents#

The AssistantAgent decides which tools to call, and the ConversableAgent executes them. We register each function on both agents:

main.py
import datetime
from autogen import AssistantAgent, ConversableAgent, LLMConfig

llm_config = LLMConfig({"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]})

_today = datetime.date.today().strftime("%Y-%m-%d")

assistant = AssistantAgent(
    name="GmailAssistant",
    llm_config=llm_config,
    system_message=(
        f"Today's date is {_today}.\n\n"
        "You are a helpful Gmail assistant that manages emails via Arcade tools. "
        "Use the provided tools to complete the user's requests.\n\n"
        "Always confirm with the user before sending any email. "
        "After completing a task, end your reply with TERMINATE."
    ),
    is_termination_msg=lambda msg: "TERMINATE" in (msg.get("content") or ""),
)

user_proxy = ConversableAgent(
    name="User",
    human_input_mode="NEVER",
    max_consecutive_auto_reply=20,
    is_termination_msg=lambda msg: "TERMINATE" in (msg.get("content") or ""),
)

# Register tools on both agents
for func, description in [
    (list_emails, "List emails from Gmail. Filter by sender, unread status, and max results."),
    (get_thread, "Get the full conversation thread by thread ID."),
    (send_email, "Send a new email to a recipient with subject and body."),
]:
    user_proxy.register_for_execution()(func)
    assistant.register_for_llm(description=description)(func)

Step 4: Conversation loop#

main.py
if __name__ == "__main__":
    print("Gmail Assistant ready. Type your request (or 'exit' to quit).")
    while True:
        message = input("\nYou: ").strip()
        if not message or message.lower() in ("exit", "quit", "bye"):
            print("Goodbye!")
            break
        user_proxy.run(
            recipient=assistant,
            message=message,
            clear_history=False,
        ).process()
        print("\nAnything else? (type your next request or 'exit' to quit)")

Run it:

source .env && uv run python main.py

On the first run, Arcade will print an authorization URL. Open it in your browser, sign in with Google, and grant the requested Gmail permissions. This happens once. Subsequent runs skip this step since Arcade caches the token.

Gmail Assistant ready. Type your request (or 'exit' to quit).

You: Show my unread emails

GmailAssistant: Let me fetch your unread emails.
  [Calling Gmail.ListEmails with is_unread=True]

GmailAssistant: Here are your unread emails:

1. From: alice@company.com | Subject: Q2 Planning Doc | Date: 2026-03-18
   Snippet: Hey, I've attached the Q2 planning document...

2. From: bob@company.com | Subject: Re: API design review | Date: 2026-03-17
   Snippet: Looks good overall, just a few comments on the auth flow...

...

TERMINATE

Anything else? (type your next request or 'exit' to quit)

What Arcade Handles For You#

  • OAuth flow: No Google Cloud Console project, no client secrets file, no redirect URIs. Arcade manages the entire handshake.
  • Token lifecycle: Access token refresh and rotation handled transparently.
  • Scope management: Just-in-time authorization instead of broad scopes upfront.
  • Credential isolation: Tokens never reach the LLM or your agent code.

Try It Yourself#

The complete working code is available in the build-with-ag2 repository:

  • Gmail Agent: The full example from this post with 11 Gmail tools, runnable end-to-end
  • Linear Agent: Manage Linear issues (list, create, update) using the same Arcade pattern

Where to Take It Next#

The Gmail agent uses Arcade's Gmail toolkit, but Arcade exposes the entire Google Workspace suite (Calendar, Drive, Docs) plus Linear, Slack, GitHub, and thousands more. The pattern stays exactly the same: write a thin wrapper, register it with your AG2 agent, and Arcade handles the rest.

A few ideas:

  • Multi-service agent: Combine Gmail and Linear tools in the same agent. Read an email about a bug report, automatically create a Linear issue, and reply to the sender with the ticket link.
  • Multi-agent pipeline: Use AG2's group chat to build a team of specialists. An email triage agent reads and categorizes incoming mail. A project management agent creates tasks in Linear for anything actionable. A response drafter agent prepares replies for emails that need one.

Learn More#