Lab 34: Deploying the "Shopping Cart" Server Challenge
Goal
In this lab, you will take the "Shopping Cart" MCP server from Module 28, re-architect it to be stateless, containerize it with a Dockerfile, and deploy it to Google Cloud Run. You will then configure an ADK agent to connect to this live, cloud-hosted tool.
Note: This is an advanced lab. For simplicity, we will simulate an external state store with a file written to a temporary directory. In a real production system, you would replace this with a connection to a service like Redis or Memorystore.
Prerequisites
- A Google Cloud Project with billing enabled.
- Google Cloud CLI installed and authenticated.
- Docker running on your local machine.
- Required APIs: Ensure the following APIs are enabled in your project:
- Cloud Run API
- Cloud Build API
- Artifact Registry API
- Vertex AI API
- Set GCP Project: Before starting, ensure your
gcloudCLI is configured to the correct project:gcloud config set project YOUR_PROJECT_ID
Step 1: Create the Stateless MCP Server
We need to modify our server so it doesn't store the shopping carts in memory.
-
Create a new project directory:
cd /path/to/your/adk-training
mkdir cloud_mcp_server
cd cloud_mcp_server -
Create the
stateless_cart_server.pyfile: This is the modified server code, usingmcp.server.fastmcp.FastMCP-- the MCP SDK's high-level API for building HTTP-based servers (much less boilerplate than the low-levelServerclass from Module 28, which is stdio-only). Instead of a single global list shared by everyone, it reads and writes each session's cart to its own separate JSON file in a/tmp/cartsdirectory. This simulates an external persistence layer.A real MCP session is still tracked over the wire (via the
Mcp-Session-IdHTTP header) -- what's "stateless" here is the server process, not the protocol: any container instance can handle any request, because the actual cart data lives outside the process.# Filename: stateless_cart_server.py
import json
import os
from mcp.server.fastmcp import FastMCP, Context
# --- Configuration ---
# In a serverless environment, we can use the temporary filesystem for a simple demo.
# In a real production system, this would be a connection to a dedicated external
# persistence service like Redis or Memorystore.
STATE_STORAGE_PATH = "/tmp/carts"
if not os.path.exists(STATE_STORAGE_PATH):
os.makedirs(STATE_STORAGE_PATH)
# --- MCP Server Setup ---
mcp = FastMCP("stateless_shopping_cart_server")
def get_session_id(ctx: Context) -> str:
# The MCP session ID arrives as a standard HTTP header, tracked by the
# transport itself -- this is what ties requests to the same cart,
# regardless of which container instance handles each one.
return ctx.request_context.request.headers.get("mcp-session-id", "unknown")
# Helper functions to simulate external state
def get_cart(session_id: str) -> list:
cart_file = os.path.join(STATE_STORAGE_PATH, f"{session_id}.json")
if os.path.exists(cart_file):
with open(cart_file, 'r') as f:
return json.load(f)
return []
def save_cart(session_id: str, cart: list):
cart_file = os.path.join(STATE_STORAGE_PATH, f"{session_id}.json")
with open(cart_file, 'w') as f:
json.dump(cart, f)
@mcp.tool()
def add_item_to_cart(item: str, ctx: Context) -> dict:
"""Adds an item to the cart."""
session_id = get_session_id(ctx)
print(f"[Server]: Handling add_item_to_cart for session '{session_id}'")
cart = get_cart(session_id)
cart.append(item)
save_cart(session_id, cart)
return {"status": "success", "message": f"Added '{item}'."}
@mcp.tool()
def view_cart(ctx: Context) -> dict:
"""Views items in the cart."""
session_id = get_session_id(ctx)
print(f"[Server]: Handling view_cart for session '{session_id}'")
cart = get_cart(session_id)
return {"status": "success", "cart": cart}
# --- ASGI app for HTTP, suitable for Cloud Run ---
# FastMCP mounts the MCP endpoint at /mcp by default.
app = mcp.streamable_http_app()
Step 2: Containerize the MCP Server
-
Create a
requirements.txtfile: Our server needs themcplibrary anduvicornto actually serve the ASGI app it produces. Pinmcpto a1.xrelease:mcp>=2.0renamedFastMCPtoMCPServerand removed themcp.server.fastmcpmodule entirely, which breaks both this server'sfrom mcp.server.fastmcp import FastMCPimport andgoogle-adk's ownMcpToolsetclient code (it targets the pre-2.0 API too).printf "mcp<2\nuvicorn\n" > requirements.txt -
Create the
Dockerfile:FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY stateless_cart_server.py .
# Cloud Run provides the PORT env var (defaults to 8080 locally).
# Shell form (not exec/JSON-array form) is required here so $PORT expands.
CMD python -m uvicorn stateless_cart_server:app --host 0.0.0.0 --port ${PORT:-8080}
Step 3: Build and Deploy the Server to Cloud Run
-
Set environment variables:
export GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
export GOOGLE_CLOUD_LOCATION=us-central1 -
Build and push the image:
gcloud builds submit \
--tag ${GOOGLE_CLOUD_LOCATION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/adk-images/mcp-cart-server:v1(If you completed Module 33's cleanup, the
adk-imagesrepository no longer exists -- create it first withgcloud artifacts repositories create adk-images --repository-format=docker --location=$GOOGLE_CLOUD_LOCATION.) -
Deploy to Cloud Run:
gcloud run deploy mcp-cart-server \
--image=${GOOGLE_CLOUD_LOCATION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/adk-images/mcp-cart-server:v1 \
--region=$GOOGLE_CLOUD_LOCATION \
--allow-unauthenticatedThis command will deploy your server and give you a public Service URL. Copy this URL.
Step 4: Configure the ADK Client Agent
Now, create an ADK agent that connects to your newly deployed server.
-
Create an
agent.pyfile in the samecloud_mcp_serverdirectory. -
Add the following code, replacing
YOUR_CLOUD_RUN_SERVICE_URLwith the URL you copied. Note the/mcpsuffix -- that's whereFastMCPmounted the endpoint in Step 1, and the client needs to hit that exact path, not just the bare service URL.# Filename: agent.py
from google.adk import Agent
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
# The URL of your deployed MCP server -- note the /mcp path suffix
MCP_SERVER_URL = "YOUR_CLOUD_RUN_SERVICE_URL/mcp"
root_agent = Agent(
model='gemini-3.5-flash',
name='cloud_shopping_agent',
instruction='You are a shopping assistant. Help the user by adding items to their cart and showing them their cart contents.',
tools=[
McpToolset(
# Use StreamableHTTPConnectionParams for remote servers
connection_params=StreamableHTTPConnectionParams(
url=MCP_SERVER_URL,
),
)
],
) -
Create
__init__.pyand.envfiles: Create an empty__init__.pyfile in thecloud_mcp_serverdirectory. Create a.envfile in thecloud_mcp_serverdirectory with your Vertex AI configuration:GOOGLE_GENAI_USE_VERTEXAI=1
GOOGLE_CLOUD_PROJECT=<your_gcp_project>
GOOGLE_CLOUD_LOCATION=us-central1Replace
<your_gcp_project>with your actual Google Cloud Project ID.
Step 5: Test the Full Cloud-Based System
-
Start the ADK web server locally: This will run your client agent from the parent directory.
uv run adk web cloud_shopping_agent -
Interact with the agent:
- Open the Dev UI.
- Have the same conversation as in the previous lab: add 'apples', then add 'bread', then view the cart.
- It should work exactly the same! But this time, the state is being managed by your serverless application running on Cloud Run.
Lab Summary
You have successfully deployed a stateful service to a stateless platform by externalizing the state, and connected your ADK agent to it.
You have learned to:
- Modify an application to be stateless by moving its state to an external store (simulated with the filesystem).
- Containerize a Python application with a
Dockerfile. - Deploy a custom server to Google Cloud Run.
- Configure the
McpToolsetto connect to a remote HTTP-based MCP server usingStreamableHTTPConnectionParams.
Cleanup (Important!)
Cloud Run services and Artifact Registry repositories can incur costs if left running. It is crucial to delete the resources you created after completing the lab.
-
Delete the Cloud Run Service:
gcloud run services delete mcp-cart-server \
--region=$GOOGLE_CLOUD_LOCATION \
--async # Runs in background -
Delete the Artifact Registry Repository:
gcloud artifacts repositories delete adk-images \
--location=$GOOGLE_CLOUD_LOCATION \
--async # Runs in background -
Delete the
cloud_mcp_serverdirectory:cd ..
rm -rf cloud_mcp_server
Self-Reflection Questions
- Our stateless server uses the
/tmpdirectory for storage. Why is this approach not truly persistent, and what could happen to a user's shopping cart if the Cloud Run service scales down and then back up? - What are the advantages of using a managed service like Google Cloud Memorystore (Redis) for storing session state compared to the file-based approach used in this lab?
- The
McpToolseton the client side doesn't need to know how the server is storing its state. Why is this separation of concerns a key benefit of the MCP architecture?
🕵️ Hidden Solution 🕵️
Looking for the solution? Here's a hint (Base64 decode me):
L2RvYy1hZGstdHJhaW5pbmcvbW9kdWxlMzQtZGVwbG95aW5nLW1jcC1zZXJ2ZXItY2xvdWQtcnVuL2xhYi1zb2x1dGlvbg==
The direct link is: Lab Solution