Extending CartPilot with MCP Tools
Connect external microservices and third-party tools to ADK 2 agents using Model Context Protocol (MCP) and McpToolset.

Extending CartPilot with MCP Tools
📦 Companion Code: View the complete, runnable code on GitHub:
part13in the cartpilot-adk2-series repository.
Every tool CartPilot has used so far is code we wrote and fully control. Real systems eventually need capabilities from someone else's service — a shipping carrier's rate API, a tax calculation provider, a payments processor. The Model Context Protocol (MCP) is the standard way agents connect to those external tools, and ADK has first-class support for it via McpToolset.
A local MCP server for shipping rates
To keep this runnable without any external account, we'll build a tiny MCP server ourselves and connect to it locally — the integration pattern is identical to connecting to a real third-party provider's server.
# mcp_servers/shipping_server.py
from mcp.server import MCPServer
mcp = MCPServer("cartpilot-shipping")
_RATE_TABLE = {"0": 6.50, "1": 7.00, "2": 7.50, "3": 8.00, "4": 6.00,
"5": 6.50, "6": 7.00, "7": 7.50, "8": 8.50, "9": 9.00}
@mcp.tool()
def get_shipping_rate(destination_zip: str, weight_lbs: float) -> dict:
"""Estimates the shipping cost for a package.
Args:
destination_zip: The 5-digit destination ZIP code.
weight_lbs: Package weight in pounds.
"""
if not destination_zip or not destination_zip[0].isdigit():
return {"status": "error", "message": "Invalid ZIP code."}
base_rate = _RATE_TABLE.get(destination_zip[0], 7.50)
cost = round(base_rate + max(weight_lbs - 1, 0) * 1.25, 2)
return {"status": "success", "estimated_cost": cost, "delivery_window_days": "3-5"}
if __name__ == "__main__":
mcp.run(transport="stdio")Run it standalone first to sanity check it (python mcp_servers/shipping_server.py) — it should sit waiting for stdio input, which is expected; ADK's McpToolset will start and manage this process for you once wired into the agent.
Wiring it into cart_agent
# cartpilot/shipping.py
import os
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from mcp import StdioServerParameters
_SERVER_SCRIPT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"mcp_servers", "shipping_server.py")
shipping_toolset = McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(command="python3", args=[_SERVER_SCRIPT]),
timeout=30,
),
tool_filter=["get_shipping_rate"],
)# cartpilot/cart_agent.py (excerpt)
from .shipping import shipping_toolset
cart_agent = Agent(
# ...
instruction="""
You manage the shopper's cart. Use `add_to_cart`, `view_cart`.
When the shopper confirms they want to complete the purchase, call the
`checkout_pipeline` tool and relay its order_confirmation to them clearly.
If the shopper asks about shipping cost, use `get_shipping_rate` with
their ZIP code and the cart's total weight.
""",
tools=[add_to_cart, view_cart, checkout_tool, shipping_toolset],
)McpToolset handles connection lifecycle, tool discovery (querying the server for what it offers), and proxying calls — from the model's perspective, get_shipping_rate looks exactly like any tool we've written by hand.
Governance: what changes once a tool isn't yours
A third-party MCP server is a dependency you don't control the same way you control your own code, which means three things need deliberate handling:
- Auth. Our local demo needs none, but a real shipping provider's MCP server would require an API key, typically passed via
env={"SHIPPING_API_KEY": os.getenv("SHIPPING_API_KEY", "")}insideStdioServerParameters. Scope that credential as narrowly as the provider's auth model allows — a key that can only fetch rates, not modify shipments, if that distinction exists. - Rate limits. You don't control the third party's rate limits, and a runaway agent conversation calling their tool in a loop is now their problem too, not just yours — until they cut you off.
tool_filter=["get_shipping_rate"]above is a small piece of this discipline: only expose the specific tool CartPilot needs, even if the server offers more, so the model's tool list — and the blast radius of what a misfire could call — stays as narrow as the actual use case. - Cost attribution. Apply Part 11's audit logging to third-party tool calls exactly the way you do to internal ones. When a shipping-provider bill spikes, "which conversation, which agent, how many calls" needs to be answerable from your logs, not from guesswork.
What's next
CartPilot now has search, cart, checkout, support, refunds with approval, resilient payments, cost tracking, guardrails, an eval suite, and a real external integration. Part 14 — the finale — packages all of it and takes it to production: deployment, autoscaling, monitoring, and what to do when something goes wrong after launch.