Lab 28: Building a "Shopping Cart" MCP Server Challenge
Goal
In this lab, you will build your own simple, standalone MCP server from scratch. This server will manage a stateful shopping cart, allowing clients to add items and view the cart's contents. You will then connect an ADK agent to your custom server.
Step 1: Install MCP and Create Project
-
Install the
mcplibrary (via the ADK's compatible extra):uv add "google-adk[mcp]"Installing
mcpas a separate, unconstrained package (e.g.pip install mcp) can resolve a newermcprelease than the one ADK expects. Installing it through ADK's[mcp]extra guarantees a compatible version. -
Create a new project directory:
mkdir custom_mcp_server
cd custom_mcp_server
Step 2: Implement the MCP Server
Exercise: Create a file named cart_server.py. Inside this file, your task is to implement the list_tools and call_tool handlers for the shopping cart server. Use the # TODO comments as your guide.
# In cart_server.py (Starter Code)
import asyncio
import json
from mcp import types as mcp_types
from mcp.server.lowlevel import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
# --- Server State ---
# In a real application, this would be a database. For this lab, a simple
# in-memory list is enough to demonstrate statefulness.
CART = []
# --- MCP Server Setup ---
app = Server("shopping_cart_mcp_server")
@app.list_tools()
async def list_mcp_tools() -> list[mcp_types.Tool]:
"""Defines the 'menu' of tools our server offers."""
print("[Server]: Client asked for the list of tools.")
# TODO: 1. Define the `add_item_to_cart` tool. It needs a name, a
# description, and an inputSchema for a required string property "item".
add_item_tool = mcp_types.Tool(...)
# TODO: 2. Define the `view_cart` tool. It needs a name, a description,
# and an empty inputSchema since it takes no arguments.
view_cart_tool = mcp_types.Tool(...)
return [add_item_tool, view_cart_tool]
@app.call_tool()
async def call_mcp_tool(name: str, arguments: dict) -> list[mcp_types.Content]:
"""Handles the execution of our tools."""
print(f"[Server]: Client called tool '{name}'.")
# TODO: 3. Implement the logic for the "add_item_to_cart" tool.
# - Get the "item" from the `arguments`.
# - Append it to `CART`.
# - Return a success message.
# TODO: 4. Implement the logic for the "view_cart" tool.
# - Return the current contents of `CART`.
# Remember to return your response as a JSON string inside a
# `mcp_types.TextContent` object.
response_text = json.dumps({"status": "error", "message": "Not implemented."})
return [mcp_types.TextContent(type="text", text=response_text)]
# --- MCP Server Runner (Provided for you) ---
async def run_mcp_stdio_server():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
print("[Server]: Waiting for a client to connect...")
await app.run(
read_stream,
write_stream,
InitializationOptions(
server_name=app.name,
server_version="0.1.0",
capabilities=app.get_capabilities(NotificationOptions(), {}),
),
)
if __name__ == "__main__":
print("[Server]: Starting Shopping Cart MCP Server...")
try:
asyncio.run(run_mcp_stdio_server())
except KeyboardInterrupt:
print("\n[Server]: Shutting down.")
Step 3: Create the ADK Client Agent
Create an agent.py file and complete the code to connect to your server using the ADK 2.0 Agent class and an McpToolset.
# In agent.py
import pathlib
from google.adk import Agent
from google.adk.tools.mcp_tool import McpToolset, StdioConnectionParams
from mcp import StdioServerParameters
# TODO: Get the absolute path to your 'cart_server.py'.
# Base it on this file's own location (pathlib.Path(__file__).parent), not on
# os.getcwd() — the process's working directory won't be this folder when
# 'adk web'/'adk run' is launched from the parent directory in Step 4.
PATH_TO_SERVER = ...
# TODO: Define the root Agent node and configure the McpToolset
# - command: 'python3'
# - args: [PATH_TO_SERVER]
root_agent = Agent(
model='gemini-3.5-flash',
name='shopping_agent',
instruction='You are a shopping assistant.',
tools=[
McpToolset(...)
],
)
Also create an empty __init__.py and a .env file with your Vertex AI configuration:
GOOGLE_GENAI_USE_VERTEXAI=1
GOOGLE_CLOUD_PROJECT=<your_gcp_project>
GOOGLE_CLOUD_LOCATION=us-central1
Step 4: Test the Full System
- Navigate to the parent directory and start the ADK web server, pointing it at the
custom_mcp_serverfolder you created in Step 1:cd ..
uv run adk web custom_mcp_server - Check the console logs: You should see logs from your
cart_server.pyas it starts up. - Interact with the agent in the Dev UI:
- "Please add 'milk' to my cart."
- "Also add 'eggs'."
- "What is in my shopping cart?"
- Examine the server logs in the console to see the
call_toolrequests being received and processed by your custom server.
Having Trouble?
If you get stuck, you can find the complete, working code in the lab-solution.md file.
Lab Summary
You have successfully built and consumed your own stateful MCP tool. You have learned to:
- Implement the
@app.list_tools()handler to define a server's tool schema. - Implement the
@app.call_tool()handler to provide tool logic. - Manage state on the server side across multiple tool calls.
- Connect an ADK agent to your custom-built MCP server.
Self-Reflection Questions
- In our
cart_server.py, we used a global listCARTto store the state. Why is this approach not suitable for a production environment with multiple server instances or multiple concurrent users? What would be a better solution? - The server declares
capabilitiesin itsInitializationOptions. What role does this capability negotiation play during the MCP handshake, and what might happen if a client expects a capability the server never declared? - By building an MCP server, you have decoupled your tool's logic from the agent. What are the long-term benefits of this separation for maintaining and scaling your application?
🕵️ Hidden Solution 🕵️
Looking for the solution? Here's a hint (Base64 decode me):
L2RvYy1hZGstdHJhaW5pbmcvbW9kdWxlMjgtYnVpbGRpbmctbWNwLXRvb2xzL2xhYi1zb2x1dGlvbg==
The direct link is: Lab Solution