# How to integrate Google Ads MCP with LlamaIndex

```json
{
  "title": "How to integrate Google Ads MCP with LlamaIndex",
  "toolkit": "Google Ads",
  "toolkit_slug": "googleads",
  "framework": "LlamaIndex",
  "framework_slug": "llama-index",
  "url": "https://composio.dev/toolkits/googleads/framework/llama-index",
  "markdown_url": "https://composio.dev/toolkits/googleads/framework/llama-index.md",
  "updated_at": "2026-10-12T13:33:17.995Z"
}
```

## Introduction

This guide walks you through connecting Google Ads to LlamaIndex using the Composio tool router. By the end, you'll have a working Google Ads agent that can create a new customer list for holiday campaigns, get campaign details by campaign id, list all current customer lists in your account through natural language commands.
This guide will help you understand how to give your LlamaIndex agent real control over a Google Ads account through Composio's Google Ads MCP server.
Before we dive in, let's take a quick look at the key ideas and tools involved.

## Also integrate Google Ads with

- [Claude Code](https://composio.dev/toolkits/googleads/framework/claude-code)
- [Cursor](https://composio.dev/toolkits/googleads/framework/cursor)
- [ChatGPT](https://composio.dev/toolkits/googleads/framework/chatgpt)
- [Claude Cowork](https://composio.dev/toolkits/googleads/framework/claude-cowork)
- [Antigravity](https://composio.dev/toolkits/googleads/framework/antigravity)
- [OpenAI Agents SDK](https://composio.dev/toolkits/googleads/framework/open-ai-agents-sdk)
- [Claude Agent SDK](https://composio.dev/toolkits/googleads/framework/claude-agents-sdk)
- [Grok Bot](https://composio.dev/toolkits/googleads/framework/grok-bot)
- [Codex](https://composio.dev/toolkits/googleads/framework/codex)
- [Kimi Code](https://composio.dev/toolkits/googleads/framework/kimi)
- [Grok Build](https://composio.dev/toolkits/googleads/framework/grok-build)
- [VS Code](https://composio.dev/toolkits/googleads/framework/vscode)
- [OpenCode](https://composio.dev/toolkits/googleads/framework/opencode)
- [OpenClaw](https://composio.dev/toolkits/googleads/framework/openclaw)
- [Hermes](https://composio.dev/toolkits/googleads/framework/hermes-agent)
- [Atomic Agent](https://composio.dev/toolkits/googleads/framework/atomic-agent)
- [CLI](https://composio.dev/toolkits/googleads/framework/cli)
- [Google ADK](https://composio.dev/toolkits/googleads/framework/google-adk)
- [LangChain](https://composio.dev/toolkits/googleads/framework/langchain)
- [Vercel AI SDK](https://composio.dev/toolkits/googleads/framework/ai-sdk)
- [Mastra AI](https://composio.dev/toolkits/googleads/framework/mastra-ai)
- [CrewAI](https://composio.dev/toolkits/googleads/framework/crew-ai)
- [Pydantic AI](https://composio.dev/toolkits/googleads/framework/pydantic-ai)
- [AutoGen](https://composio.dev/toolkits/googleads/framework/autogen)

## TL;DR

Here's what you'll learn:
- Set your OpenAI and Composio API keys
- Install LlamaIndex and Composio packages
- Create a Composio Tool Router session for Google Ads
- Connect LlamaIndex to the Google Ads MCP server
- Build a Google Ads-powered agent using LlamaIndex
- Interact with Google Ads through natural language

## What is LlamaIndex?

LlamaIndex is a data framework for building LLM applications. It provides tools for connecting LLMs to external data sources and services through agents and tools.
Key features include:
- ReAct Agent: Reasoning and acting pattern for tool-using agents
- MCP Tools: Native support for Model Context Protocol
- Context Management: Maintain conversation context across interactions
- Async Support: Built for async/await patterns

## What is the Google Ads MCP server, and what's possible with it?

The Google Ads MCP server is an implementation of the Model Context Protocol that connects your AI agent and assistants like Claude, Cursor, etc directly to your Google Ads account. It provides structured and secure access to your advertising data, so your agent can perform actions like creating customer lists, retrieving campaign details, and managing audience segments on your behalf.
- Automated customer list creation: Instantly have your agent create new customer lists for targeted marketing campaigns, streamlining your segmentation workflows.
- Campaign insights by ID or name: Let your agent fetch detailed information for any ad campaign using its ID or name, making reporting and optimization a breeze.
- Customer list management: Ask your agent to retrieve and review all your existing customer lists, so you always know who you're targeting.
- Add or remove contacts from lists: Seamlessly update audience membership by having your agent add or remove contacts from specified customer lists for more dynamic targeting.

## Supported Tools

| Tool slug | Name | Description |
|---|---|---|
| `GOOGLEADS_ADD_OR_REMOVE_TO_CUSTOMER_LIST` | Add or remove to customer list | Adds or removes contacts from a Google Ads customer list (UserList/audience/remarketing list), not a Google Ads customer account. Note: It takes 6 to 12 hours for changes to be reflected in the customer list. Email addresses must comply with Google Ads policies and applicable privacy/consent laws. |
| `GOOGLEADS_BATCH_MUTATE` | Batch Mutate Google Ads Resources | Create, update, remove, or act on supported Google Ads resource types in one request. With partial_failure=false (the default), all operations are atomic and execute only if every operation is valid. With partial_failure=true, valid operations execute independently while invalid operations return errors. Use negative temporary resource IDs to connect resources created in the same request. Remove operations are destructive. |
| `GOOGLEADS_CREATE_CALLOUT_ASSET` | Create Callout Asset | Create an immutable account asset containing a validated callout. Associate the returned resource using Mutate Customer Assets with field_type CALLOUT. Cleanup removes that association; Google does not allow deleting the asset. |
| `GOOGLEADS_CREATE_CUSTOMER_LIST` | Create customer list | Creates a Google Ads customer list (UserList/audience/remarketing list), not a Google Ads customer account. Note: Requires an authenticated Google Ads connection with customer_id configured. Email-based lists must comply with Google Ads policies and applicable privacy/consent laws. Membership updates can take many hours to propagate; targeting eligibility is not immediate after creation. |
| `GOOGLEADS_GET_CAMPAIGN_BY_ID` | Get Campaign By Id | GetCampaignById Tool returns details of a campaign in Google Ads. Requires an active Google Ads OAuth connection with the correct customer_id configured; missing or mismatched customer_id will cause empty results. |
| `GOOGLEADS_GET_CAMPAIGN_BY_NAME` | Get campaign by name | Queries Google Ads via SQL to retrieve a campaign by its exact name. Requires an active Google Ads connection with valid customer_id and appropriate OAuth scopes. |
| `GOOGLEADS_GET_CONVERSION_ACTION_TAG_SNIPPETS` | Get Conversion Action Tag Snippets | Retrieve provider-generated website or call conversion tag snippets after creating a conversion action. Markup is returned exactly as Google provides it. |
| `GOOGLEADS_GET_CONVERSION_INGESTION_STATUS` | Get conversion ingestion status | Retrieve one Data Manager ingestion status snapshot. SUCCESS, FAILED, and PARTIAL_SUCCESS are terminal. PROCESSING and REQUEST_STATUS_UNKNOWN are not. The caller owns the documented backoff loop. |
| `GOOGLEADS_GET_CUSTOMER_LISTS` | Get customer lists | GetCustomerLists Tool lists all customer lists (audience/remarketing lists) in Google Ads. These are user segments for targeting, not Google Ads accounts — list IDs are distinct from account IDs. When multiple lists share similar names, review all returned results before selecting one for downstream operations. |
| `GOOGLEADS_GET_RMF_REPORT` | Get Required Google Ads Report | Run a prominent, bounded Google Ads report template for Customer, Campaign, Ad, Keyword, Search Term, Dynamic Search Ads Search Term, or Bidding Strategy. Official required columns are always selected; dates default to LAST_30_DAYS. |
| `GOOGLEADS_INGEST_CONVERSION_EVENTS` | Ingest conversion events | Validate or submit a bounded Google Ads conversion-event batch. Set validate_only=true for a no-ingestion check. False starts irreversible asynchronous processing and returns a request ID for later status checks. |
| `GOOGLEADS_LIST_ACCESSIBLE_CUSTOMERS` | List Accessible Customers | List Google Ads customer resource names directly accessible to the authenticated OAuth user. Google determines the complete result from the OAuth credentials and ignores customer_id and login-customer-id; this action does not filter the response. For MCC child account names, status, and direct hierarchy, use List Sub Accounts. |
| `GOOGLEADS_LIST_DATA_MANAGER_CONVERSION_ACTIONS` | List data manager conversion actions | List conversion actions usable as Google Ads Data Manager destinations. Returns numeric destination IDs, true owning accounts, action types, and availability; defaults to enabled offline and enhanced-lead actions. |
| `GOOGLEADS_LIST_SUB_ACCOUNTS` | List Sub Accounts | List direct child/sub-accounts under a Google Ads manager (MCC) account. Use this before account-scoped read or mutate calls when a user needs to choose which child account to target. The returned customer_id values can be passed to other Google Ads tools as their customer_id. |
| `GOOGLEADS_MUTATE_AD_GROUP_ADS` | Mutate Ad Group Ads | Create, update, or remove ads in Google Ads ad groups, including responsive search ads. Use this after creating campaigns, budgets, ad groups, and keywords so the ad group has creative that can serve. |
| `GOOGLEADS_MUTATE_AD_GROUP_ASSETS` | Mutate Ad Group Assets | Create, update, or remove links between assets and ad groups. Use this after creating assets to attach sitelinks, callouts, snippets, images, and other asset types at ad group level. |
| `GOOGLEADS_MUTATE_AD_GROUP_BID_MODIFIERS` | Mutate Ad Group Bid Modifiers | Create, update, or remove ad group bid modifiers, such as device bid adjustments or hotel-specific bid modifier criteria. |
| `GOOGLEADS_MUTATE_AD_GROUP_CRITERIA` | Mutate Ad Group Criteria | Create, update, or remove criteria attached to a Google Ads ad group in a single batch request. Supports keywords (positive and negative), audience targeting (user lists, custom audiences, combined audiences, in-market/affinity), demographics (age range, gender, parental status, income range), placements (websites, mobile apps, YouTube channels/videos), topics, webpages (for Dynamic Search Ads), and listing groups (for Shopping). Use this to programmatically manage keyword bids and match types, build audience targeting, or prune underperforming criteria. Remove operations are irreversible — deleted criteria cannot be recovered. For testing, set validate_only=true to dry-run without committing changes. |
| `GOOGLEADS_MUTATE_AD_GROUPS` | Mutate Ad Groups | Create, update, or remove ad groups within Google Ads campaigns. Supports batch operations with multiple ad group changes in a single request. Use when you need to manage ad groups programmatically, such as creating new ad groups for campaigns, updating ad group settings or status, or removing ad groups that are no longer needed. This action is irreversible for remove operations — deleted ad groups cannot be recovered once removed. |
| `GOOGLEADS_MUTATE_ASSETS` | Mutate Assets | Create or update supported Google Ads assets such as sitelinks, structured snippets, text assets, lead forms, promotions, calls, price assets, and media assets. Use Create Callout Asset for validated immutable callouts. Google Ads does not allow removing assets; cleanup removes their resource associations. |
| `GOOGLEADS_MUTATE_ASSET_SETS` | Mutate Asset Sets | Create, update, or remove Google Ads asset sets, which group assets for feeds, locations, and other campaign uses. Link assets to a set separately with an AssetSetAsset mutation, and attach sets to campaigns with Mutate Campaign Asset Sets. |
| `GOOGLEADS_MUTATE_BIDDING_STRATEGIES` | Mutate Bidding Strategies | Create, update, or remove portfolio bidding strategies such as Target CPA, Target ROAS, Target Spend, Maximize Conversions, Maximize Conversion Value, and Target Impression Share. |
| `GOOGLEADS_MUTATE_CAMPAIGN_ASSETS` | Mutate Campaign Assets | Create, update, or remove links between assets and campaigns. Use this after creating assets to attach sitelinks, callouts, snippets, and other asset types at campaign level. |
| `GOOGLEADS_MUTATE_CAMPAIGN_BUDGETS` | Mutate Campaign Budgets | Create, update, or remove Google Ads campaign budgets. Use this before creating campaigns because campaign creation requires an existing campaign_budget resource name. |
| `GOOGLEADS_MUTATE_CAMPAIGN_CRITERIA` | Mutate Campaign Criteria | Create, update, or remove campaign-level targeting criteria such as locations, languages, campaign-level negative keywords, devices, schedules, audiences, and IP exclusions. |
| `GOOGLEADS_MUTATE_CAMPAIGN_LABELS` | Mutate Campaign Labels | Create or remove relationships between campaigns and labels. Use this after creating labels to organize campaigns for filtering and reporting. |
| `GOOGLEADS_MUTATE_CAMPAIGNS` | Mutate Campaigns | Create, update, or remove Google Ads campaigns in batch. Supports multiple operations (create, update, remove) in a single request. Use when managing campaign lifecycle, applying bulk changes, or automating campaign management workflows. This action is irreversible for remove operations — deleted campaigns cannot be recovered. Plan accordingly and consider using validate_only=true to test changes before applying them. |
| `GOOGLEADS_MUTATE_CAMPAIGNS_V2` | Mutate Campaigns V2 | Create, update, or remove Google Ads campaigns in batch. Supports multiple operations (create, update, remove) in a single request. Use when managing campaign lifecycle, applying bulk changes, or automating campaign management workflows. This action is irreversible for remove operations — deleted campaigns cannot be recovered. Plan accordingly and consider using validate_only=true to test changes before applying them. |
| `GOOGLEADS_MUTATE_CONVERSION_ACTIONS` | Mutate Conversion Actions | Create, update, or remove Google Ads conversion actions for conversion tracking configuration. This manages Google Ads conversion action resources; installing website/app tags remains outside this action. |
| `GOOGLEADS_MUTATE_CUSTOM_AUDIENCES` | Mutate Custom Audiences | Create, update, or remove Google Ads custom audiences used to reach users by interests, searches, visited URLs, place categories, or installed apps. |
| `GOOGLEADS_MUTATE_CUSTOMER_ASSETS` | Mutate Customer Assets | Create or remove customer-level asset associations. Removal irreversibly removes the current association; recreate it to restore the link. Use this after creating assets to attach callouts, sitelinks, snippets, and other asset types across a Google Ads customer account. |
| `GOOGLEADS_MUTATE_LABELS` | Mutate Labels | Create, update, or remove Google Ads labels. Pair this with campaign label mutations to organize campaigns and support filtering. |
| `GOOGLEADS_MUTATE_PORTFOLIO_BIDDING_STRATEGIES` | Mutate Portfolio Bidding Strategies | Create, edit, or remove typed Target CPA and Target ROAS portfolio strategies. Attach with Mutate Campaigns, read with Get Required Google Ads Report, and transition all campaigns before removal. Budget alignment is not exposed because Google requires the bidding strategy and campaign budget to be changed atomically with GoogleAdsService.Mutate, while this action uses the resource-specific mutate endpoint. currency_code can be set on create only for manager-account strategies. |
| `GOOGLEADS_MUTATE_SHARED_SETS` | Mutate Shared Sets | Create, update, or remove Google Ads shared sets used to group shared criteria, such as negative keyword lists, for reuse across campaigns. |
| `GOOGLEADS_SEARCH_GOOGLE_ADS_FIELDS` | Search Google Ads Fields | Search the Google Ads v23 field metadata catalog with GAQL. |
| `GOOGLEADS_SEARCH_STREAM_GAQL` | Search Stream GAQL | Execute a Google Ads Query Language (GAQL) query and aggregate every streamed response batch in order. For very large reports, select only needed fields and use WHERE and LIMIT because action responses are returned inline and are not automatically offloaded. |

## Supported Triggers

None listed.

## Creating MCP Server - Stand-alone vs Composio SDK

The Google Ads MCP server is an implementation of the Model Context Protocol that connects your AI agent to Google Ads. It provides structured and secure access so your agent can perform Google Ads operations on your behalf through a secure, permission-based interface.
With Composio's managed implementation, you don't have to create your own developer app. For production, if you're building an end product, we recommend using your own credentials. The managed server helps you prototype fast and go from 0-1 faster.

## Step-by-step Guide

### 1. Prerequisites

Before you begin, make sure you have:
- Python 3.8/Node 16 or higher installed
- A Composio account with the API key
- An OpenAI API key
- A Google Ads account and project
- Basic familiarity with async Python/Typescript

### 1. Getting API Keys for OpenAI, Composio, and Google Ads

No description provided.

### 2. Installing dependencies

No description provided.
```python
pip install composio-llamaindex llama-index llama-index-llms-openai llama-index-tools-mcp python-dotenv
```

```typescript
npm install @composio/llamaindex @llamaindex/openai @llamaindex/tools @llamaindex/workflow dotenv
```

### 3. Set environment variables

Create a .env file in your project root:
These credentials will be used to:
- Authenticate with OpenAI's GPT-5 model
- Connect to Composio's Tool Router
- Identify your Composio user session for Google Ads access
```bash
OPENAI_API_KEY=your-openai-api-key
COMPOSIO_API_KEY=your-composio-api-key
COMPOSIO_USER_ID=your-user-id
```

### 4. Import modules

No description provided.
```python
import asyncio
import os
import dotenv

from composio import Composio
from composio_llamaindex import LlamaIndexProvider
from llama_index.core.agent.workflow import ReActAgent
from llama_index.core.workflow import Context
from llama_index.llms.openai import OpenAI
from llama_index.tools.mcp import BasicMCPClient, McpToolSpec

dotenv.load_dotenv()
```

```typescript
import "dotenv/config";
import readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";

import { Composio } from "@composio/core";

import { mcp } from "@llamaindex/tools";
import { agent as createAgent } from "@llamaindex/workflow";
import { openai } from "@llamaindex/openai";

dotenv.config();
```

### 5. Load environment variables and initialize Composio

No description provided.
```python
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
COMPOSIO_API_KEY = os.getenv("COMPOSIO_API_KEY")
COMPOSIO_USER_ID = os.getenv("COMPOSIO_USER_ID")

if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY is not set in the environment")
if not COMPOSIO_API_KEY:
    raise ValueError("COMPOSIO_API_KEY is not set in the environment")
if not COMPOSIO_USER_ID:
    raise ValueError("COMPOSIO_USER_ID is not set in the environment")
```

```typescript
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const COMPOSIO_API_KEY = process.env.COMPOSIO_API_KEY;
const COMPOSIO_USER_ID = process.env.COMPOSIO_USER_ID;

if (!OPENAI_API_KEY) throw new Error("OPENAI_API_KEY is not set");
if (!COMPOSIO_API_KEY) throw new Error("COMPOSIO_API_KEY is not set");
if (!COMPOSIO_USER_ID) throw new Error("COMPOSIO_USER_ID is not set");
```

### 6. Create a Tool Router session and build the agent function

What's happening here:
- We create a Composio client using your API key and configure it with the LlamaIndex provider
- We then create a tool router MCP session for your user, specifying the toolkits we want to use (in this case, google ads)
- The session returns an MCP HTTP endpoint URL that acts as a gateway to all your configured tools
- LlamaIndex will connect to this endpoint to dynamically discover and use the available Google Ads tools.
- The MCP tools are mapped to LlamaIndex-compatible tools and plug them into the Agent.
```python
async def build_agent() -> ReActAgent:
    composio_client = Composio(
        api_key=COMPOSIO_API_KEY,
        provider=LlamaIndexProvider(),
    )

    session = composio_client.create(
        user_id=COMPOSIO_USER_ID,
        toolkits=["googleads"],
    )

    mcp_url = session.mcp.url
    print(f"Composio MCP URL: {mcp_url}")

    mcp_client = BasicMCPClient(mcp_url, headers={"x-api-key": COMPOSIO_API_KEY})
    mcp_tool_spec = McpToolSpec(client=mcp_client)
    tools = await mcp_tool_spec.to_tool_list_async()

    llm = OpenAI(model="gpt-5")

    description = "An agent that uses Composio Tool Router MCP tools to perform Google Ads actions."
    system_prompt = """
    You are a helpful assistant connected to Composio Tool Router.
    Use the available tools to answer user queries and perform Google Ads actions.
    """
    return ReActAgent(tools=tools, llm=llm, description=description, system_prompt=system_prompt, verbose=True)
```

```typescript
async function buildAgent() {

  console.log(`Initializing Composio client...${COMPOSIO_USER_ID!}...`);
  console.log(`COMPOSIO_USER_ID: ${COMPOSIO_USER_ID!}...`);

  const composio = new Composio({
    apiKey: COMPOSIO_API_KEY,
    provider: new LlamaindexProvider(),
  });

  const session = await composio.create(
    COMPOSIO_USER_ID!,
    {
      toolkits: ["googleads"],
    },
  );

  const mcpUrl = session.mcp.url;
  console.log(`Composio Tool Router MCP URL: ${mcpUrl}`);

  const server = mcp({
    url: mcpUrl,
    clientName: "composio_tool_router_with_llamaindex",
    requestInit: {
      headers: {
        "x-api-key": COMPOSIO_API_KEY!,
      },
    },
    // verbose: true,
  });

  const tools = await server.tools();

  const llm = openai({ apiKey: OPENAI_API_KEY, model: "gpt-5" });

  const agent = createAgent({
    name: "composio_tool_router_with_llamaindex",
        description : "An agent that uses Composio Tool Router MCP tools to perform actions.",
    systemPrompt:
      "You are a helpful assistant connected to Composio Tool Router."+
"Use the available tools to answer user queries and perform Google Ads actions." ,
    llm,
    tools,
  });

  return agent;
}
```

### 7. Create an interactive chat loop

No description provided.
```python
async def chat_loop(agent: ReActAgent) -> None:
    ctx = Context(agent)
    print("Type 'quit', 'exit', or Ctrl+C to stop.")

    while True:
        try:
            user_input = input("\nYou: ").strip()
        except (KeyboardInterrupt, EOFError):
            print("\nBye!")
            break

        if not user_input or user_input.lower() in {"quit", "exit"}:
            print("Bye!")
            break

        try:
            print("Agent: ", end="", flush=True)
            handler = agent.run(user_input, ctx=ctx)

            async for event in handler.stream_events():
                # Stream token-by-token from LLM responses
                if hasattr(event, "delta") and event.delta:
                    print(event.delta, end="", flush=True)
                # Show tool calls as they happen
                elif hasattr(event, "tool_name"):
                    print(f"\n[Using tool: {event.tool_name}]", flush=True)

            # Get final response
            response = await handler
            print()  # Newline after streaming
        except KeyboardInterrupt:
            print("\n[Interrupted]")
            continue
        except Exception as e:
            print(f"\nError: {e}")
```

```typescript
async function chatLoop(agent: ReturnType<typeof createAgent>) {
  const rl = readline.createInterface({ input, output });

  console.log("Type 'quit' or 'exit' to stop.");

  while (true) {
    let userInput: string;

    try {
      userInput = (await rl.question("\nYou: ")).trim();
    } catch {
      console.log("\nAgent: Bye!");
      break;
    }

    if (!userInput) {
      continue;
    }

    const lower = userInput.toLowerCase();
    if (lower === "quit" || lower === "exit") {
      console.log("Agent: Bye!");
      break;
    }

    try {
      process.stdout.write("Agent: ");

      const stream = agent.runStream(userInput);
      let finalResult: any = null;

      for await (const event of stream) {
        // The event.data contains the streamed content
        const data: any = event.data;

        // Check for streaming delta content
        if (data?.delta) {
          process.stdout.write(data.delta);
        }

        // Store final result for fallback
        if (data?.result || data?.message) {
          finalResult = data;
        }
      }

      // If no streaming happened, show the final result
      if (finalResult) {
        const answer =
          finalResult.result ??
          finalResult.message?.content ??
          finalResult.message ??
          "";
        if (answer && typeof answer === "string" && !answer.includes("[object")) {
          process.stdout.write(answer);
        }
      }

      console.log(); // New line after streaming completes
    } catch (err: any) {
      console.error("\nAgent error:", err?.message ?? err);
    }
  }

  rl.close();
}
```

### 8. Define the main entry point

What's happening here:
- We're orchestrating the entire application flow
- The agent gets built with proper error handling
- Then we kick off the interactive chat loop so you can start talking to Google Ads
```python
async def main() -> None:
    agent = await build_agent()
    await chat_loop(agent)

if __name__ == "__main__":
    # Handle Ctrl+C gracefully
    signal.signal(signal.SIGINT, lambda s, f: (print("\nBye!"), exit(0)))
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nBye!")
```

```typescript
async function main() {
  try {
    const agent = await buildAgent();
    await chatLoop(agent);
  } catch (err) {
    console.error("Failed to start agent:", err);
    process.exit(1);
  }
}

main();
```

### 9. Run the agent

When prompted, authenticate and authorise your agent with Google Ads, then start asking questions.
```bash
python llamaindex_agent.py
```

```typescript
npx ts-node llamaindex-agent.ts
```

## Complete Code

```python
import asyncio
import os
import signal
import dotenv

from composio import Composio
from composio_llamaindex import LlamaIndexProvider
from llama_index.core.agent.workflow import ReActAgent
from llama_index.core.workflow import Context
from llama_index.llms.openai import OpenAI
from llama_index.tools.mcp import BasicMCPClient, McpToolSpec

dotenv.load_dotenv()

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
COMPOSIO_API_KEY = os.getenv("COMPOSIO_API_KEY")
COMPOSIO_USER_ID = os.getenv("COMPOSIO_USER_ID")

if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY is not set")
if not COMPOSIO_API_KEY:
    raise ValueError("COMPOSIO_API_KEY is not set")
if not COMPOSIO_USER_ID:
    raise ValueError("COMPOSIO_USER_ID is not set")

async def build_agent() -> ReActAgent:
    composio_client = Composio(
        api_key=COMPOSIO_API_KEY,
        provider=LlamaIndexProvider(),
    )

    session = composio_client.create(
        user_id=COMPOSIO_USER_ID,
        toolkits=["googleads"],
    )

    mcp_url = session.mcp.url
    print(f"Composio MCP URL: {mcp_url}")

    mcp_client = BasicMCPClient(mcp_url, headers={"x-api-key": COMPOSIO_API_KEY})
    mcp_tool_spec = McpToolSpec(client=mcp_client)
    tools = await mcp_tool_spec.to_tool_list_async()

    llm = OpenAI(model="gpt-5")
    description = "An agent that uses Composio Tool Router MCP tools to perform Google Ads actions."
    system_prompt = """
    You are a helpful assistant connected to Composio Tool Router.
    Use the available tools to answer user queries and perform Google Ads actions.
    """
    return ReActAgent(
        tools=tools,
        llm=llm,
        description=description,
        system_prompt=system_prompt,
        verbose=True,
    );

async def chat_loop(agent: ReActAgent) -> None:
    ctx = Context(agent)
    print("Type 'quit', 'exit', or Ctrl+C to stop.")

    while True:
        try:
            user_input = input("\nYou: ").strip()
        except (KeyboardInterrupt, EOFError):
            print("\nBye!")
            break

        if not user_input or user_input.lower() in {"quit", "exit"}:
            print("Bye!")
            break

        try:
            print("Agent: ", end="", flush=True)
            handler = agent.run(user_input, ctx=ctx)

            async for event in handler.stream_events():
                # Stream token-by-token from LLM responses
                if hasattr(event, "delta") and event.delta:
                    print(event.delta, end="", flush=True)
                # Show tool calls as they happen
                elif hasattr(event, "tool_name"):
                    print(f"\n[Using tool: {event.tool_name}]", flush=True)

            # Get final response
            response = await handler
            print()  # Newline after streaming
        except KeyboardInterrupt:
            print("\n[Interrupted]")
            continue
        except Exception as e:
            print(f"\nError: {e}")

async def main() -> None:
    agent = await build_agent()
    await chat_loop(agent)

if __name__ == "__main__":
    # Handle Ctrl+C gracefully
    signal.signal(signal.SIGINT, lambda s, f: (print("\nBye!"), exit(0)))
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nBye!")
```

```typescript
import "dotenv/config";
import readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";

import { Composio } from "@composio/core";
import { LlamaindexProvider } from "@composio/llamaindex";

import { mcp } from "@llamaindex/tools";
import { agent as createAgent } from "@llamaindex/workflow";
import { openai } from "@llamaindex/openai";

dotenv.config();

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const COMPOSIO_API_KEY = process.env.COMPOSIO_API_KEY;
const COMPOSIO_USER_ID = process.env.COMPOSIO_USER_ID;

if (!OPENAI_API_KEY) {
    throw new Error("OPENAI_API_KEY is not set in the environment");
  }
if (!COMPOSIO_API_KEY) {
    throw new Error("COMPOSIO_API_KEY is not set in the environment");
  }
if (!COMPOSIO_USER_ID) {
    throw new Error("COMPOSIO_USER_ID is not set in the environment");
  }

async function buildAgent() {

  console.log(`Initializing Composio client...${COMPOSIO_USER_ID!}...`);
  console.log(`COMPOSIO_USER_ID: ${COMPOSIO_USER_ID!}...`);

  const composio = new Composio({
    apiKey: COMPOSIO_API_KEY,
    provider: new LlamaindexProvider(),
  });

  const session = await composio.create(
    COMPOSIO_USER_ID!,
    {
      toolkits: ["googleads"],
    },
  );

  const mcpUrl = session.mcp.url;
  console.log(`Composio Tool Router MCP URL: ${mcpUrl}`);

  const server = mcp({
    url: mcpUrl,
    clientName: "composio_tool_router_with_llamaindex",
    requestInit: {
      headers: {
        "x-api-key": COMPOSIO_API_KEY!,
      },
    },
    // verbose: true,
  });

  const tools = await server.tools();

  const llm = openai({ apiKey: OPENAI_API_KEY, model: "gpt-5" });

  const agent = createAgent({
    name: "composio_tool_router_with_llamaindex",
    description:
      "An agent that uses Composio Tool Router MCP tools to perform actions.",
    systemPrompt:
      "You are a helpful assistant connected to Composio Tool Router."+
"Use the available tools to answer user queries and perform Google Ads actions." ,
    llm,
    tools,
  });

  return agent;
}

async function chatLoop(agent: ReturnType<typeof createAgent>) {
  const rl = readline.createInterface({ input, output });

  console.log("Type 'quit' or 'exit' to stop.");

  while (true) {
    let userInput: string;

    try {
      userInput = (await rl.question("\nYou: ")).trim();
    } catch {
      console.log("\nAgent: Bye!");
      break;
    }

    if (!userInput) {
      continue;
    }

    const lower = userInput.toLowerCase();
    if (lower === "quit" || lower === "exit") {
      console.log("Agent: Bye!");
      break;
    }

    try {
      process.stdout.write("Agent: ");

      const stream = agent.runStream(userInput);
      let finalResult: any = null;

      for await (const event of stream) {
        // The event.data contains the streamed content
        const data: any = event.data;

        // Check for streaming delta content
        if (data?.delta) {
          process.stdout.write(data.delta);
        }

        // Store final result for fallback
        if (data?.result || data?.message) {
          finalResult = data;
        }
      }

      // If no streaming happened, show the final result
      if (finalResult) {
        const answer =
          finalResult.result ??
          finalResult.message?.content ??
          finalResult.message ??
          "";
        if (answer && typeof answer === "string" && !answer.includes("[object")) {
          process.stdout.write(answer);
        }
      }

      console.log(); // New line after streaming completes
    } catch (err: any) {
      console.error("\nAgent error:", err?.message ?? err);
    }
  }

  rl.close();
}

async function main() {
  try {
    const agent = await buildAgent();
    await chatLoop(agent);
  } catch (err: any) {
    console.error("Failed to start agent:", err?.message ?? err);
    process.exit(1);
  }
}

main();
```

## Conclusion

You've successfully connected Google Ads to LlamaIndex through Composio's Tool Router MCP layer.
Key takeaways:
- Tool Router dynamically exposes Google Ads tools through an MCP endpoint
- LlamaIndex's ReActAgent handles reasoning and orchestration; Composio handles integrations
- The agent becomes more capable without increasing prompt size
- Async Python provides clean, efficient execution of agent workflows
You can easily extend this to other toolkits like Gmail, Notion, Stripe, GitHub, and more by adding them to the toolkits parameter.

## How to build Google Ads MCP Agent with another framework

- [Claude Code](https://composio.dev/toolkits/googleads/framework/claude-code)
- [Cursor](https://composio.dev/toolkits/googleads/framework/cursor)
- [ChatGPT](https://composio.dev/toolkits/googleads/framework/chatgpt)
- [Claude Cowork](https://composio.dev/toolkits/googleads/framework/claude-cowork)
- [Antigravity](https://composio.dev/toolkits/googleads/framework/antigravity)
- [OpenAI Agents SDK](https://composio.dev/toolkits/googleads/framework/open-ai-agents-sdk)
- [Claude Agent SDK](https://composio.dev/toolkits/googleads/framework/claude-agents-sdk)
- [Grok Bot](https://composio.dev/toolkits/googleads/framework/grok-bot)
- [Codex](https://composio.dev/toolkits/googleads/framework/codex)
- [Kimi Code](https://composio.dev/toolkits/googleads/framework/kimi)
- [Grok Build](https://composio.dev/toolkits/googleads/framework/grok-build)
- [VS Code](https://composio.dev/toolkits/googleads/framework/vscode)
- [OpenCode](https://composio.dev/toolkits/googleads/framework/opencode)
- [OpenClaw](https://composio.dev/toolkits/googleads/framework/openclaw)
- [Hermes](https://composio.dev/toolkits/googleads/framework/hermes-agent)
- [Atomic Agent](https://composio.dev/toolkits/googleads/framework/atomic-agent)
- [CLI](https://composio.dev/toolkits/googleads/framework/cli)
- [Google ADK](https://composio.dev/toolkits/googleads/framework/google-adk)
- [LangChain](https://composio.dev/toolkits/googleads/framework/langchain)
- [Vercel AI SDK](https://composio.dev/toolkits/googleads/framework/ai-sdk)
- [Mastra AI](https://composio.dev/toolkits/googleads/framework/mastra-ai)
- [CrewAI](https://composio.dev/toolkits/googleads/framework/crew-ai)
- [Pydantic AI](https://composio.dev/toolkits/googleads/framework/pydantic-ai)
- [AutoGen](https://composio.dev/toolkits/googleads/framework/autogen)

## Related Toolkits

- [Metaads](https://composio.dev/toolkits/metaads) - Metaads is Meta's official Ads API that lets you manage, analyze, and optimize your Facebook and Instagram ad campaigns. Streamline ad operations and gain deeper insights with robust automation.
- [Adrapid](https://composio.dev/toolkits/adrapid) - Adrapid is a platform for rapid creation of digital marketing visuals using templates. It streamlines design workflows for banners, images, and HTML5 content with automation.
- [AdRoll](https://composio.dev/toolkits/adroll) - AdRoll is a digital marketing platform for managing advertising campaigns, audiences, and account-based marketing. It helps marketers retarget customers across channels and measure performance.
- [Adtraction](https://composio.dev/toolkits/adtraction) - Adtraction is an affiliate marketing platform for partner programs, tracking links, transactions, commissions, statistics, and payments. It helps advertisers and publishers manage performance marketing with clear reporting and payout workflows.
- [Adyntel](https://composio.dev/toolkits/adyntel) - Adyntel is an API that retrieves LinkedIn ads for any company using a domain or LinkedIn Page ID. Easily access competitive ad intelligence to power your marketing workflows.
- [Beaconstac](https://composio.dev/toolkits/beaconstac) - Beaconstac is a platform for creating and managing QR codes and proximity beacons. It helps businesses engage customers and track marketing performance with powerful analytics.
- [Bouncify](https://composio.dev/toolkits/bouncify) - Bouncify is an email verification service for single addresses and bulk lists. Use it to reduce bounces, protect sender reputation, and track verification credits.
- [Bulk Email Checker](https://composio.dev/toolkits/bulk_email_checker) - Bulk Email Checker is an email verification service for checking address validity, mailbox status, domains, and mail-server signals. It helps reduce bounces, spot risky emails, and keep outreach lists cleaner before you send.
- [Campaign cleaner](https://composio.dev/toolkits/campaign_cleaner) - Campaign cleaner is an email campaign optimization tool that boosts compatibility and deliverability across email clients. It helps marketers get better results by cleaning, enhancing, and ensuring high performance for every campaign.
- [Deadline funnel](https://composio.dev/toolkits/deadline_funnel) - Deadline Funnel lets you create personalized deadlines and timers for your marketing campaigns. It helps marketers boost conversions by adding authentic urgency to offers.
- [DeBounce](https://composio.dev/toolkits/debounce) - DeBounce is an email verification service for checking address validity, deliverability, and risk. It helps keep contact lists clean so your campaigns reach real inboxes.
- [Easy Email Verification](https://composio.dev/toolkits/easy_email_verification) - Easy Email Verification is an email validation service for checking single addresses and bulk lists. Use it to reduce bounces, clean contact data, and protect sender reputation.
- [Elastic Email](https://composio.dev/toolkits/elastic_email) - Elastic Email is an email delivery and marketing platform for transactional emails, campaigns, contacts, and reporting. Use it to send reliable emails, manage audiences, verify addresses, and track performance.
- [Fairing](https://composio.dev/toolkits/fairing) - Fairing is a post-purchase survey and attribution platform for ecommerce brands. It helps teams measure which marketing channels, campaigns, and customer touchpoints drive purchases.
- [GoDaddy MCP](https://composio.dev/toolkits/godaddy_mcp) - GoDaddy MCP is a domain discovery toolkit for checking availability and generating AI-powered name ideas. Use it to move from rough business keywords to practical domain options fast.
- [Google AdSense](https://composio.dev/toolkits/google_adsense) - Google AdSense is a platform for managing website advertising, payments, and performance reports. It helps publishers monetize content and track ad revenue in one place.
- [Handwrite.io](https://composio.dev/toolkits/handwrite) - Handwrite.io is a direct mail service for creating physical handwritten card orders. It helps teams send personal cards with selectable handwriting, stationery, and tracking.
- [Inblog](https://composio.dev/toolkits/inblog) - Inblog is an AI-powered blogging platform for posts, authors, tags, redirects, design settings, analytics, and lead forms. Use it to manage SEO-focused content, measure performance, and capture blog leads in one place.
- [Instantly](https://composio.dev/toolkits/instantly) - Instantly is a platform for automating cold email outreach, managing leads, and optimizing deliverability. Get better results from email campaigns with minimal manual effort.
- [Invalid Bounce](https://composio.dev/toolkits/invalid_bounce) - Invalid Bounce is an email verification service with a bulk verification API. Use it to clean email lists, reduce bounces, and protect sender reputation.

## Frequently Asked Questions

### What are the differences in Tool Router MCP and Google Ads MCP?

With a standalone Google Ads MCP server, the agents and LLMs can only access a fixed set of Google Ads tools tied to that server. However, with the Composio Tool Router, agents can dynamically load tools from Google Ads and many other apps based on the task at hand, all through a single MCP endpoint.

### Can I use Tool Router MCP with LlamaIndex?

Yes, you can. LlamaIndex fully supports MCP integration. You get structured tool calling, message history handling, and model orchestration while Tool Router takes care of discovering and serving the right Google Ads tools.

### Can I manage the permissions and scopes for Google Ads while using Tool Router?

Yes, absolutely. You can configure which Google Ads scopes and actions are allowed when connecting your account to Composio. You can also bring your own OAuth credentials or API configuration so you keep full control over what the agent can do.

### How safe is my data with Composio Tool Router?

All sensitive data such as tokens, keys, and configuration is fully encrypted at rest and in transit. Composio is SOC 2 Type 2 compliant and follows strict security practices so your Google Ads data and credentials are handled as safely as possible.

---
[See all toolkits](https://composio.dev/toolkits) · [Composio docs](https://docs.composio.dev/llms.txt)
