15. Model Context Protocol (MCP)

This blog is part of the ADK Masterclass - Hands-On Series. While we introduced MCP in the Third Party Tools module, this module provides a comprehensive exploration of the protocol's architecture, when to use it, real-world use cases, and step-by-step tutorials for production deployments.

Before MCP, connecting AI agents to external services meant writing custom integrations for each API—different authentication methods, data formats, and error handling for every service. MCP solves this by providing a universal "adapter" that works with any compatible service, similar to how USB-C replaced dozens of proprietary charging cables.

View Code on GitHub

Table of Contents

1. What is MCP?

The Model Context Protocol (MCP) is an open standard developed by Anthropic that enables AI agents to connect to external data sources and tools through a unified interface. Think of it as a "USB-C for AI"—one protocol that works with hundreds of services, eliminating the need for custom integrations.

In the traditional approach, if we wanted our agent to interact with GitHub, Slack, and a database, we'd need to write three separate integrations with different APIs, authentication methods, and error handling. With MCP, we write one integration pattern that works with any MCP-compatible server.

graph TB subgraph "Your Agent" Agent[ADK Agent] MCPClient[MCP Client] end subgraph "MCP Ecosystem" Server1[GitHub MCP] Server2[Slack MCP] Server3[Database MCP] Server4[Custom MCP] end Agent --> MCPClient MCPClient --> |"Standardized Protocol"| Server1 MCPClient --> |"Standardized Protocol"| Server2 MCPClient --> |"Standardized Protocol"| Server3 MCPClient --> |"Standardized Protocol"| Server4 style Agent fill:#e3f2fd,stroke:#1565c0,stroke-width:2px style MCPClient fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
graph LR subgraph "Traditional Approach" Agent1[Agent] --> API1[GitHub API] Agent1 --> API2[Slack API] Agent1 --> API3[DB API] API1 -.->|"Custom Auth"| Agent1 API2 -.->|"Different Format"| Agent1 API3 -.->|"Unique Errors"| Agent1 end subgraph "MCP Approach" Agent2[Agent] --> MCP[MCP Client] MCP --> S1[GitHub MCP] MCP --> S2[Slack MCP] MCP --> S3[DB MCP] end style Agent1 fill:#ffebee,stroke:#c62828 style Agent2 fill:#e8f5e9,stroke:#2e7d32 style MCP fill:#e3f2fd,stroke:#1565c0

2. Why Use MCP?

MCP provides several critical advantages for building production-ready agents:

  • Standardization: One protocol to connect to any service. Learn it once, use it everywhere.
  • Ecosystem: 100+ pre-built MCP servers available—GitHub, Slack, databases, web scrapers, and more.
  • Security: Built-in authentication and permission models. Control exactly what your agent can access.
  • Discoverability: Tools are dynamically discovered at runtime. The agent learns what's available automatically.
  • Maintainability: When an external API changes, only the MCP server needs updating—not your agent code.

3. When to Use MCP

MCP is the right choice when:

Use MCP When... Consider Alternatives When...
An MCP server already exists for your service You need a simple, one-off API call
You need to connect to multiple external services The service has no MCP server and building one is complex
You want dynamic tool discovery You need very low latency (MCP adds overhead)
Security and permission control is important You're building a simple prototype

4. Real-World Use Cases

MCP shines when agents need to orchestrate multiple services. Here are three patterns we see in production:

graph TB subgraph "DevOps Agent" D1[GitHub MCP] --> DevOps[Agent] D2[Jira MCP] --> DevOps D3[Slack MCP] --> DevOps DevOps --> |"Auto-create issues from PRs"| Output1[Automated Workflow] end subgraph "Research Agent" R1[Firecrawl MCP] --> Research[Agent] R2[Notion MCP] --> Research Research --> |"Scrape & organize research"| Output2[Knowledge Base] end subgraph "Customer Support Agent" C1[Zendesk MCP] --> Support[Agent] C2[Database MCP] --> Support Support --> |"Answer with context"| Output3[Resolved Tickets] end style DevOps fill:#e3f2fd,stroke:#1565c0 style Research fill:#f3e5f5,stroke:#7b1fa2 style Support fill:#e8f5e9,stroke:#2e7d32

DevOps Automation Agent

An agent that monitors GitHub pull requests, automatically creates Jira tickets for bug fixes, and notifies the team on Slack. By using MCP servers for each service, the agent can be extended to support GitLab or Linear without changing the core logic.

Research Assistant

An agent that scrapes web content using Firecrawl MCP, summarizes findings, and organizes them in Notion. The standardized MCP interface means switching from Notion to Obsidian only requires changing the MCP server configuration.

Customer Support Agent

An agent that pulls customer history from a database MCP and ticket context from Zendesk MCP to provide personalized, context-aware responses to support requests.

5. MCP Architecture

MCP follows a client-server architecture where your agent acts as the client and external services run as servers:

sequenceDiagram participant Agent as ADK Agent participant Client as MCP Client participant Server as MCP Server participant Service as External Service Agent->>Client: Initialize connection Client->>Server: Connect (HTTP/Stdio) Server-->>Client: Available tools list Client-->>Agent: Tools registered Agent->>Client: Call tool(args) Client->>Server: Execute tool Server->>Service: API call Service-->>Server: Response Server-->>Client: Tool result Client-->>Agent: Result

The MCPToolset in ADK handles this entire flow automatically:

from google.adk.agents import Agent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset

# MCPToolset handles:
# 1. Connection management
# 2. Tool discovery
# 3. Request/response serialization
# 4. Error handling

root_agent = Agent(
    model="gemini-2.5-flash",
    name="mcp_agent",
    instruction="You have access to external tools via MCP.",
    tools=[
        MCPToolset(connection_params=...)  # Tools auto-discovered
    ],
)

5.1. Connection Types

MCP supports two primary connection types:

HTTP Connection (StreamableHTTPServerParams)

For cloud-hosted MCP servers with HTTP endpoints:

from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPServerParams
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset

# HTTP connection for remote MCP servers
mcp_tools = MCPToolset(
    connection_params=StreamableHTTPServerParams(
        url="https://api.example.com/mcp/",
        headers={
            "Authorization": f"Bearer {API_TOKEN}",
            "X-Custom-Header": "value"
        },
    ),
)

# Best for:
# - Cloud-hosted services (GitHub Copilot, hosted APIs)
# - Production deployments
# - Services requiring authentication headers

Stdio Connection (StdioConnectionParams)

For local MCP servers that run as child processes:

from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from mcp import StdioServerParameters

# Stdio connection for local MCP servers
mcp_tools = MCPToolset(
    connection_params=StdioConnectionParams(
        server_params=StdioServerParameters(
            command="npx",
            args=["-y", "firecrawl-mcp"],
            env={"API_KEY": API_KEY}
        ),
        timeout=30,
    ),
)

# Best for:
# - NPM-based MCP servers
# - Local development
# - Servers without hosted endpoints

5.2. MCP Primitives

MCP defines three core primitives that servers can expose:

Primitive Description Example
Tools Executable functions the agent can call search_repos, create_issue
Resources Read-only data the agent can access File contents, database schemas
Prompts Pre-defined prompt templates Code review template, summary format

6. Tutorial

Let's build two practical MCP-powered agents step by step.

Prerequisites

Step 1: Setup Environment

# Create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install dependencies
pip install google-adk python-dotenv

# Set environment variables
export GOOGLE_API_KEY=your_google_api_key
export GITHUB_TOKEN=your_github_token

6.1. Building a GitHub Code Review Agent

We'll build an agent that can review pull requests, analyze code changes, and suggest improvements using the GitHub MCP server.

sequenceDiagram participant User participant Agent participant MCP as GitHub MCP participant GitHub as GitHub API User->>Agent: "Review PR #123 in repo X" Agent->>MCP: get_pull_request(repo, 123) MCP->>GitHub: GET /repos/X/pulls/123 GitHub-->>MCP: PR details + diff MCP-->>Agent: Structured PR data Agent->>Agent: Analyze code changes Agent-->>User: Code review with suggestions

Step 2: Create the Agent

import os
import asyncio
from dotenv import load_dotenv
from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPServerParams
from google.genai import types

load_dotenv()

GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")

# Create the GitHub MCP-powered agent
github_agent = Agent(
    model="gemini-2.5-flash",
    name="github_reviewer",
    instruction="""You are an expert code reviewer with access to GitHub.
    
When asked to review a PR:
1. Fetch the PR details and diff
2. Analyze the code changes for:
   - Potential bugs or errors
   - Code style and best practices
   - Security vulnerabilities
   - Performance issues
3. Provide constructive feedback with specific line references
4. Suggest improvements with code examples when helpful

Be thorough but constructive. Focus on significant issues first.""",
    tools=[
        MCPToolset(
            connection_params=StreamableHTTPServerParams(
                url="https://api.githubcopilot.com/mcp/",
                headers={
                    "Authorization": f"Bearer {GITHUB_TOKEN}",
                    "X-MCP-Toolsets": "repos,pulls,issues",
                    "X-MCP-Readonly": "true"
                },
            ),
        )
    ],
)

async def review_pr(repo: str, pr_number: int):
    """Review a GitHub pull request."""
    session_service = InMemorySessionService()
    runner = Runner(
        agent=github_agent,
        app_name="github_reviewer",
        session_service=session_service,
    )
    
    session = await session_service.create_session(
        app_name="github_reviewer",
        user_id="developer"
    )
    
    message = types.Content(
        role="user",
        parts=[types.Part(text=f"Please review PR #{pr_number} in the {repo} repository. Focus on code quality, potential bugs, and security issues.")]
    )
    
    print(f"Reviewing PR #{pr_number} in {repo}...")
    print("-" * 50)
    
    async for event in runner.run_async(
        session_id=session.id,
        user_id="developer",
        new_message=message
    ):
        if hasattr(event, 'content') and event.content:
            for part in event.content.parts:
                if hasattr(part, 'text') and part.text:
                    print(part.text, end="", flush=True)
    
    print("\n" + "-" * 50)

if __name__ == "__main__":
    # Example: Review a PR
    asyncio.run(review_pr("google/adk-python", 42))

Step 3: Run the Agent

# Run the code review agent
python github_reviewer.py

# Or use ADK web interface
adk web

6.2. Building a Multi-MCP Research Agent

Now let's build a more advanced agent that combines multiple MCP servers—GitHub for code analysis and Firecrawl for web research.

graph TB User[User Query] --> Agent[Research Agent] Agent --> Decision{What info needed?} Decision -->|"Code/Repo info"| GitHub[GitHub MCP] Decision -->|"Web content"| Firecrawl[Firecrawl MCP] GitHub --> Combine[Combine Results] Firecrawl --> Combine Combine --> Agent Agent --> Response[Comprehensive Answer] style Agent fill:#e3f2fd,stroke:#1565c0,stroke-width:2px style GitHub fill:#f3e5f5,stroke:#7b1fa2 style Firecrawl fill:#fff9c4,stroke:#fbc02d
import os
import asyncio
from dotenv import load_dotenv
from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import (
    StreamableHTTPServerParams,
    StdioConnectionParams
)
from mcp import StdioServerParameters
from google.genai import types

load_dotenv()

GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
FIRECRAWL_API_KEY = os.environ.get("FIRECRAWL_API_KEY", "")

# GitHub MCP for code and repository access
github_mcp = MCPToolset(
    connection_params=StreamableHTTPServerParams(
        url="https://api.githubcopilot.com/mcp/",
        headers={
            "Authorization": f"Bearer {GITHUB_TOKEN}",
            "X-MCP-Toolsets": "repos,code",
            "X-MCP-Readonly": "true"
        },
    ),
)

# Firecrawl MCP for web scraping
firecrawl_mcp = MCPToolset(
    connection_params=StdioConnectionParams(
        server_params=StdioServerParameters(
            command="npx",
            args=["-y", "firecrawl-mcp"],
            env={"FIRECRAWL_API_KEY": FIRECRAWL_API_KEY}
        ),
        timeout=60,
    ),
)

# Multi-MCP Research Agent
research_agent = Agent(
    model="gemini-2.5-flash",
    name="research_agent",
    instruction="""You are a technical research assistant with access to:

1. **GitHub** - For code repositories, documentation, and open source projects
2. **Firecrawl** - For scraping and analyzing web content

When researching a topic:
1. First, search GitHub for relevant repositories and code examples
2. Then, scrape relevant documentation or blog posts for context
3. Synthesize findings into a comprehensive answer
4. Include code examples and links to sources

Always cite your sources and provide actionable insights.""",
    tools=[github_mcp, firecrawl_mcp],
)

async def research(query: str):
    """Conduct research using multiple MCP servers."""
    session_service = InMemorySessionService()
    runner = Runner(
        agent=research_agent,
        app_name="research_agent",
        session_service=session_service,
    )
    
    session = await session_service.create_session(
        app_name="research_agent",
        user_id="researcher"
    )
    
    message = types.Content(
        role="user",
        parts=[types.Part(text=query)]
    )
    
    print(f"Researching: {query}")
    print("=" * 60)
    
    async for event in runner.run_async(
        session_id=session.id,
        user_id="researcher",
        new_message=message
    ):
        if hasattr(event, 'content') and event.content:
            for part in event.content.parts:
                if hasattr(part, 'text') and part.text:
                    print(part.text, end="", flush=True)
    
    print("\n" + "=" * 60)

if __name__ == "__main__":
    asyncio.run(research(
        "What are the best practices for building AI agents with ADK? "
        "Include code examples and links to official documentation."
    ))

7. Best Practices

Building production MCP agents requires attention to security, performance, and reliability. Here's a quick reference:

from google.adk.agents import Agent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import (
    StreamableHTTPServerParams,
    StdioConnectionParams
)
from mcp import StdioServerParameters

# GitHub MCP (HTTP)
github_mcp = MCPToolset(
    connection_params=StreamableHTTPServerParams(
        url="https://api.githubcopilot.com/mcp/",
        headers={"Authorization": f"Bearer {GITHUB_TOKEN}"},
    ),
)

# Firecrawl MCP (Stdio)
firecrawl_mcp = MCPToolset(
    connection_params=StdioConnectionParams(
        server_params=StdioServerParameters(
            command="npx",
            args=["-y", "firecrawl-mcp"],
            env={"FIRECRAWL_API_KEY": FIRECRAWL_KEY}
        ),
    ),
)

# Agent with multiple MCP servers
root_agent = Agent(
    model="gemini-2.5-flash",
    name="multi_mcp_agent",
    instruction="""You have access to:
    - GitHub for code and repository operations
    - Firecrawl for web scraping and content extraction
    Choose the appropriate tool based on the user's request.""",
    tools=[github_mcp, firecrawl_mcp],
)
graph TB subgraph "Security Best Practices" S1[Use Read-Only Mode] S2[Environment Variables] S3[Token Scoping] end subgraph "Performance Best Practices" P1[Set Timeouts] P2[Tool Filtering] P3[Connection Pooling] end subgraph "Reliability Best Practices" R1[Error Handling] R2[Rate Limiting] R3[Logging & Monitoring] end style S1 fill:#e8f5e9,stroke:#2e7d32 style S2 fill:#e8f5e9,stroke:#2e7d32 style S3 fill:#e8f5e9,stroke:#2e7d32 style P1 fill:#e3f2fd,stroke:#1565c0 style P2 fill:#e3f2fd,stroke:#1565c0 style P3 fill:#e3f2fd,stroke:#1565c0 style R1 fill:#fff9c4,stroke:#fbc02d style R2 fill:#fff9c4,stroke:#fbc02d style R3 fill:#fff9c4,stroke:#fbc02d

Security

  • Use read-only mode: Set X-MCP-Readonly: true when agents don't need write access. This prevents accidental modifications.
  • Secure credentials: Use environment variables for API keys, never hardcode them in your source code.
  • Scope tokens minimally: Create API tokens with only the permissions your agent needs.

Performance

  • Set appropriate timeouts: Stdio connections can hang; always set a timeout (30-60 seconds is typical).
  • Filter tools: Only expose the tools your agent needs. Fewer tools = faster discovery and clearer agent behavior.
  • Reuse connections: When possible, reuse MCP connections across requests rather than creating new ones.

Reliability

  • Handle errors gracefully: MCP tools can fail due to network issues, rate limits, or API errors. Your agent should handle these gracefully.
  • Respect rate limits: Be aware of rate limits on external services. Consider implementing backoff strategies.
  • Log MCP interactions: Log tool calls and responses for debugging and monitoring in production.

Tool Filtering Example

# Only expose specific tools from the MCP server
mcp_tools = MCPToolset(
    connection_params=StreamableHTTPServerParams(
        url="https://api.githubcopilot.com/mcp/",
        headers={
            "Authorization": f"Bearer {GITHUB_TOKEN}",
            "X-MCP-Toolsets": "repos,issues",  # Only repos and issues tools
            "X-MCP-Readonly": "true"  # Read-only mode
        },
    ),
    # Optional: filter tools by name pattern
    tool_filter=lambda tool: tool.name.startswith("get_")
)

Next Steps

With MCP covered, we now move to Core Components—the internal mechanisms that power ADK agents, starting with Session, State & Memory.

Resources

Comments