> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xquik.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Google ADK MCP Twitter API tutorial for Python

> Build Google ADK tools for tweet search, followers, monitors, and approved X actions. Follow a tested Python MCP server guide with typed handoffs and retries.

<blockquote className="agent-llms-directive">
  For the complete documentation index, see <a href="/llms.txt">llms.txt</a>.
</blockquote>

This Google ADK tutorial uses Xquik's remote MCP server.
Build a Google ADK Twitter API agent through Xquik using this architecture.
This Twitter API for Python workflow searches tweets, profiles, and followers.
It also replays monitors and reviews approved X actions. The handoff must
keep tweet IDs, profile IDs, cursors, and job IDs.

## Why use Google ADK with a Twitter API?

Google's Agent Development Kit (ADK) manages Gemini calls, tools, sessions,
and agent handoffs. Model Context Protocol, or MCP, defines interoperable tool
schemas. Xquik exposes `docs`, `search`, and `execute` as remote MCP tools.

This Google ADK guide assigns one responsibility to each boundary. The Agent
Development Kit MCP client loads Xquik route schemas during startup.

| Boundary | Google ADK control | Twitter agent benefit |
| - | - | - |
| Remote MCP | `McpToolset` | Connect Gemini to tweet, profile, follower, and monitor routes |
| Final response | Pydantic `output_schema` | Reject malformed tweet rows and pagination state |
| Session continuity | `InMemoryRunner` or a durable session service | Resume with the same tweet IDs and cursors |
| Agent handoff | `output_key` and sub-agents | Separate research, review, and publishing |
| X actions | `require_confirmation` | Pause before the `execute` tool runs |
| Tenant credentials | `header_provider` | Resolve one Xquik key for each tenant |

Choose ADK when Gemini already powers the surrounding workflow. Choose a
direct REST client for deterministic jobs without model decisions.

## Google ADK Twitter API prerequisites

* Python 3.10 or later
* An [Xquik API key](/x-api-quickstart) beginning with `xq_`
* A Google AI API key for Gemini
* A connected X account for private reads or X write actions

Public X reads do not require X Developer credentials. Authenticate with
Xquik. Connect an X account only when the selected route requires it.

## Install Google ADK Python and MCP support

Install the current Google ADK 2.6 line with its compatible MCP client.

```bash theme={null}
python -m pip install --upgrade \
  "google-adk[mcp]>=2.6,<2.7" \
  "pydantic>=2.12,<3" \
  python-dotenv
```

Google ADK 2.6 requires MCP 1.x. Its `mcp` extra excludes MCP 2.x.

Store secrets outside source control.

```bash .env theme={null}
XQUIK_API_KEY=xq_YOUR_KEY_HERE
GOOGLE_API_KEY=YOUR_GOOGLE_AI_KEY
```

```text .gitignore theme={null}
.env
xquik-*-handoff.json
```

The environment variables isolate Xquik and Google credentials from the agent
implementation. Never place either key inside an agent instruction.

## Connect Google Agent Development Kit to an MCP server

This Google Agent Development Kit MCP server setup uses Xquik remotely. ADK
is the MCP client, while Xquik hosts the remote server.

`McpToolset` discovers `docs`, `search`, and `execute` through standard tool calling.
These Google ADK tools cover tweets, profiles, followers, monitors, and X
actions. The separation keeps agent workflows independent from route schemas.

## Build a Google ADK Python tweet search agent

Define the final handoff before creating the agent. ADK validates the final
Gemini response while still allowing MCP tool calls.

`from google.adk import Agent` imports the current agent class. This Google ADK
Python example validates final responses with Pydantic. The Twitter API
get tweets workflow calls the documented search route.

```python theme={null}
import asyncio
import os
from pathlib import Path
from typing import Literal

from dotenv import load_dotenv
from google.adk import Agent
from google.adk.runners import InMemoryRunner
from google.adk.tools.mcp_tool.mcp_session_manager import (
    StreamableHTTPConnectionParams,
)
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
from google.genai import types
from pydantic import BaseModel


class TweetRow(BaseModel):
    tweet_id: str
    text: str
    author_username: str | None = None
    created: int | None = None
    url: str | None = None


class TweetSearchHandoff(BaseModel):
    query: str
    route_used: str
    tweets: list[TweetRow]
    has_more: bool
    next_cursor: str | None = None
    pages_fetched: int
    stop_reason: Literal["complete", "requested_limit", "cursor_stalled"]


async def main() -> None:
    load_dotenv()

    xquik_tools = McpToolset(
        connection_params=StreamableHTTPConnectionParams(
            url="https://xquik.com/mcp",
            headers={"x-api-key": os.environ["XQUIK_API_KEY"]},
            timeout=15,
            sse_read_timeout=120,
        ),
        tool_filter=["docs", "search", "execute"],
    )

    agent = Agent(
        model="gemini-3.5-flash",
        name="xquik_tweet_search_agent",
        instruction=(
            "Use Xquik for Twitter API requests. "
            "Use GET /api/v1/x/tweets/search. "
            "Preserve exact tweet IDs, created timestamps, and cursors. "
            "Never invent missing tweet fields. "
            "Stop when the requested limit is reached. "
            "Stop if the server repeats a cursor."
        ),
        tools=[xquik_tools],
        output_schema=TweetSearchHandoff,
    )

    async with InMemoryRunner(
        agent=agent,
        app_name="xquik_adk_app",
    ) as runner:
        session = await runner.session_service.create_session(
            app_name="xquik_adk_app",
            user_id="user-1",
        )

        final_text = ""
        async for event in runner.run_async(
            user_id="user-1",
            session_id=session.id,
            new_message=types.Content(
                role="user",
                parts=[
                    types.Part(
                        text=(
                            "Search 50 latest tweets about Google ADK MCP. "
                            "Return compact validated JSON."
                        )
                    )
                ],
            ),
        ):
            if (
                event.is_final_response()
                and event.content
                and event.content.parts
            ):
                final_text = "".join(
                    part.text or "" for part in event.content.parts
                )

        handoff = TweetSearchHandoff.model_validate_json(final_text)
        Path("xquik-adk-handoff.json").write_text(
            handoff.model_dump_json(indent=2),
            encoding="utf-8",
        )


asyncio.run(main())
```

The runner closes `McpToolset` when its context exits. Reuse one runner across
related turns. Repeated setup adds avoidable MCP handshakes.

`xquik.request()` returns normalized snake\_case fields from the MCP runtime.
It normalizes `createdAt` to the Unix-second field `created`. Align the Pydantic
schema with this response format.

## Search tweets with useful X operators

Send the search route a focused `q`. Keep the exact query in the handoff.

| Search intent | Example `q` |
| - | - |
| Latest topic tweets | `"Google ADK" MCP` |
| One account's timeline window | `from:googlecloud since:2026-07-01 until:2026-08-01` |
| Popular English posts | `"agent development kit" lang:en min_faves:25` |
| Questions without reposts | `"Twitter API agent" ? -filter:retweets` |
| Exact tweet | A numeric Tweet ID or X status URL |

This Twitter API integration accepts standard X search operators. Use
`queryType=Latest` for chronological monitoring. Use `Top` for engagement
ranking. See the complete [tweet search API contract](/api-reference/x/search-tweets).

The search query `twitter api get tweets` maps to this route.

Pass the returned `next_cursor` unchanged. Never reconstruct a cursor. Stop
when `has_more` is false, the requested limit is met, or a cursor repeats.

## Separate tweet research from X actions

Xquik exposes one `execute` tool by design. MCP tool-name filters
cannot distinguish GET, POST, and DELETE requests inside that tool.

This Google ADK multi agent pattern separates research from publishing. Use
separate credentials and confirmation rules across multi agent systems.

Use separate ADK agents. Let the research agent read tweets and profiles. Make
the publishing agent confirm every `execute` call.

```python theme={null}
import os

from google.adk import Agent
from google.adk.tools.mcp_tool.mcp_session_manager import (
    StreamableHTTPConnectionParams,
)
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset


def connection() -> StreamableHTTPConnectionParams:
    return StreamableHTTPConnectionParams(
        url="https://xquik.com/mcp",
        headers={"x-api-key": os.environ["XQUIK_API_KEY"]},
    )


research_tools = McpToolset(
    connection_params=connection(),
    tool_filter=["docs", "search", "execute"],
)

approved_write_tools = McpToolset(
    connection_params=connection(),
    tool_filter=["execute"],
    require_confirmation=True,
)

researcher = Agent(
    name="tweet_researcher",
    model="gemini-3.5-flash",
    instruction=(
        "Search tweets and profiles. Preserve IDs, created timestamps, "
        "has_more, and next_cursor. Never perform X write actions."
    ),
    tools=[research_tools],
    output_key="tweet_research",
)

publisher = Agent(
    name="approved_x_publisher",
    model="gemini-3.5-flash",
    instruction=(
        "Use only reviewed tweet text and the selected X account. "
        "Never change approved text. Never retry a pending write."
    ),
    tools=[approved_write_tools],
)

coordinator = Agent(
    name="twitter_campaign_coordinator",
    model="gemini-3.5-flash",
    instruction=(
        "Delegate tweet research to tweet_researcher. "
        "Delegate approved X actions to approved_x_publisher."
    ),
    sub_agents=[researcher, publisher],
)
```

ADK CLI and ADK Web can present confirmation requests. A custom interface must
return ADK's confirmation `FunctionResponse`. Build confirmation into the product
workflow. A prompt sentence is not enough.

Confirm every `execute` write call. A code-string inspection does not authorize
an action.

For public research, a guest `paid_reads` key allows eligible GET routes only.
[Guest wallets](/guides/guest-wallets) document the exact scope.

## Discovery-only tool filtering

Expose only `search` when an agent should inspect endpoint schemas.

```python theme={null}
discovery_tools = McpToolset(
    connection_params=connection(),
    tool_filter=["search"],
)
```

This setup blocks Twitter API execution. Adding `execute` enables key-authorized
reads and writes. Because `execute` wraps methods, name filtering cannot enforce
read-only access.

Use `search` before unfamiliar operations. It returns routes, methods,
parameters, and response fields without calling X.

## Store tweet IDs and cursors in ADK state

Keep compact identifiers in session state. Exclude complete tweet pages from
ADK prompts and stored session state.

Plain state keys belong to one conversation. `user:` keys last across a
user's sessions. `app:` keys hold shared configuration. `temp:` keys expire
after one invocation.

Recommended state entries include:

* `query` and `query_type`
* `last_tweet_id` and `selected_tweet_ids`
* `next_cursor` and `pages_fetched`
* `monitor_id` and `last_event_id`
* `extraction_id`, `status`, and `poll`
* `write_action_id`, `status`, and `charged_credits`

Never store Xquik keys in ADK state. Let `header_provider` resolve credentials
from your secret manager.

## Use tenant-specific Xquik API keys

`header_provider` runs when ADK opens the MCP session. Read a non-secret tenant
identifier from `ReadonlyContext`. Resolve the key outside the prompt.

```python theme={null}
from google.adk.agents.readonly_context import ReadonlyContext


def tenant_headers(context: ReadonlyContext) -> dict[str, str]:
    tenant_id = str(context.state["tenant_id"])
    api_key = secret_store.get_xquik_key(tenant_id)
    return {"x-api-key": api_key}


tenant_tools = McpToolset(
    connection_params=StreamableHTTPConnectionParams(
        url="https://xquik.com/mcp",
    ),
    header_provider=tenant_headers,
    tool_filter=["docs", "search", "execute"],
)
```

`secret_store` represents an existing secret manager. Keep its returned keys
outside Gemini prompts and ADK events.

## Handle tweet search errors and rate limits

Handle every documented status with its matching recovery step.

| Status | Meaning | Agent action |
| - | - | - |
| `400` | The search query is missing or invalid | Fix `q`. Do not retry unchanged |
| `401` | Guest authentication cannot complete this request | Provide an Xquik API key or connect X |
| `402` | The account lacks credits | Stop and request account action |
| `424` | The X dependency failed | Retry with bounded backoff |
| `429` | The Twitter API rate limit applies | Wait for reset guidance, then resume the cursor |
| `502` | The X dependency returned an invalid response | Retry later without changing IDs |

Do not restart pagination after `429`. Retain `next_cursor` and completed
result identifiers. Deduplicate recovered results through the exact `tweet_id`.

POST and DELETE routes document different statuses. Read each route before
building retry logic. See [error handling](/guides/error-handling).

## Handoff checklist

<CardGroup cols={2}>
  <Card title="Tweet search rows" icon="search">
    Store `tweet_id`, `text`, `author_username`, `created`, `url`, `has_more`, `next_cursor`, and the original `q`.
  </Card>

  <Card title="User profile rows" icon="users">
    Store source `id` as `user_id`, plus `username`, `name`, `followers`, `verified`, `profile_picture`, `has_more`, `next_cursor`, and the source lookup or search query.
  </Card>

  <Card title="Trend rows" icon="trending-up">
    Store each trend `name`, `rank`, `query`, and `description`. Keep response `count`, `woeid`, and the requested region with the run checkpoint.
  </Card>

  <Card title="Monitor and webhook setup" icon="radio">
    Store the returned monitor `id` as `monitor_id`, `event_types`, `next_billing_at`, the returned webhook `id` as `webhook_id`, `url`, and the one-time `secret` in a secret manager. On production deliveries, store `delivery_id` for receiver retry deduplication. Store `stream_event_id` when one monitor event should process once across endpoint changes.
  </Card>

  <Card title="Stored event replay" icon="activity">
    Store `event_id`, `type`, `monitor_id`, `monitor_type`, `occurred_at`, `has_more`, `next_cursor`, and the `cursor` query for the next page.
  </Card>

  <Card title="Extraction jobs" icon="database">
    Store `extraction_id`, `status`, `poll`, and `export_after_complete`. Poll before loading CSV, JSON, or XLSX rows.
  </Card>

  <Card title="Writes" icon="send">
    Store `tweet_id` or `write_action_id`, `reply_to_tweet_id`, `status`, `charged_credits`, and `poll`. Do not resend pending writes.
  </Card>

  <Card title="Media attachments" icon="image">
    For tweets or replies, pass public URLs in `media` and store `tweet_id` or `write_action_id`. For DMs, upload first, pass one `media_id` in `media_ids`, store `message_id`, and leave `reply_to_message_id` unset.
  </Card>
</CardGroup>

## Choose Google ADK MCP or the REST API

| Requirement | Google ADK with MCP | Direct REST API |
| - | - | - |
| Natural-language tweet research | Strong fit | Write query logic yourself |
| Gemini multi-agent handoffs | Native fit | Add an orchestrator |
| Strict scheduled follower export | Extra model step | Strong fit |
| Human-approved tweet posting | ADK confirmation flow | Build approval state yourself |
| Predictable latency and cost | Less predictable | More predictable |
| Typed final handoff | `output_schema` | SDK or Pydantic model |

Use MCP when Gemini chooses among related Twitter API operations. Use REST for
fixed routes, scheduled exports, or latency-sensitive services.

## Migrate Google ADK 1.x MCP code

Google ADK 2.x keeps compatibility aliases. New code should use current API
symbols.

| Older pattern | Current pattern |
| - | - |
| `from google.adk.agents import LlmAgent` | `from google.adk import Agent` |
| `MCPToolset` | `McpToolset` |
| `StreamableHTTPServerParams` | `StreamableHTTPConnectionParams` |
| Manual toolset cleanup | `async with InMemoryRunner(...)` |
| Prompt-only JSON | Pydantic `output_schema` |

`MCPToolset` now emits a deprecation warning. The current class uses the
`McpToolset` capitalization.

## Verified Google ADK package versions

Xquik checked these versions on August 2, 2026.

| Package | Checked compatible version | Compatible range used here |
| - | - | - |
| `google-adk` | 2.6.1 | `>=2.6,<2.7` |
| `mcp` | 1.29.0 | `>=1.24,<2` through the `mcp` extra |
| `pydantic` | 2.13.4 | `>=2.12,<3` |

Pin a tested minor range. Review ADK 2.x release notes before widening it.

## Google ADK Twitter API questions

### Does Google ADK support MCP?

Yes. Python ADK connects to remote Streamable HTTP servers through
`McpToolset`. Xquik publishes `docs`, `search`, and `execute` at
`https://xquik.com/mcp`.

### How does Google Agent Development Kit differ from MCP?

ADK orchestrates Gemini agents. MCP standardizes external tool discovery and
invocation. Xquik serves the tools, while `McpToolset` connects the agent.

### Does Google ADK MCP require Google Cloud or Cloud Run?

No. The agent code can run wherever Python 3.10 runs. Google Cloud and Cloud
Run are optional deployment targets. Xquik requires no local MCP server.

### How do I connect Google ADK to a Twitter API?

Create `StreamableHTTPConnectionParams` with the Xquik MCP URL. Send the
Xquik API key in the `x-api-key` header. Pass `McpToolset` to `Agent.tools`.

### How do I search tweets with a gemini agent?

Ask the agent to call `GET /api/v1/x/tweets/search`. Supply an exact `q`,
`queryType`, and limit. Keep `tweet_id`, `created`, and `next_cursor`.

This Twitter API integration can get tweets without X Developer credentials.
Private routes still require a connected X account.

### Can Google ADK post tweets and replies?

Yes. It requires a connected X account. Route every `execute` call through
ADK confirmation. Validate the selected account, text, reply ID, and media.

See [create tweet](/api-reference/x-write/create-tweet) for the exact contract.

### How do I handle Twitter API rate limits in Python?

Treat `429` separately from dependency errors. Save the current cursor and
completed tweet IDs. Resume after the reset guidance. Never retry in a tight
loop.

### Can a Google ADK agent export Twitter followers?

Yes. The [followers API](/api-reference/x/followers) paginates follower profiles
through response cursors. Extraction jobs produce larger CSV, JSON, or XLSX exports.
Load exported rows only after polling confirms completion.

### Can Google ADK monitor tweets without repeated searches?

Yes. Create an account or keyword monitor. Replay stored events by cursor.
Connect a webhook when the receiver can verify signatures and deduplicate
deliveries. Monitors use scheduled checks and do not promise real time delivery.

### Google ADK or LangChain for a Twitter agent?

Choose ADK for Gemini-centered sessions and native agent handoffs. LangGraph
supports more model providers and graph orchestration. Xquik supports
both frameworks.

### Why does a remote MCP connection close between calls?

Keep one runner active during related turns. Its context manager closes each
`McpToolset` instance. Increase connection and SSE timeouts for long operations.
Do not create one `McpToolset` per prompt.

### Does tool filtering make the Twitter API read-only?

Only `search` without `execute` blocks execution. The `execute` tool can run every
operation authorized by its key. Separate agents and approvals protect X
actions.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.