Skip to main content

Lab 34: Deploying the "Shopping Cart" Server Challenge

Goal

In this lab, you will take the "Shopping Cart" MCP server from Module 30, 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 gcloud CLI 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.

  1. Create a new project directory:

    cd /path/to/your/adk-training
    mkdir cloud-mcp-server
    cd cloud-mcp-server
  2. Create the stateless_cart_server.py file: This is the modified server code. Instead of a global dictionary, it reads and writes the cart for each session to a separate JSON file in a /tmp/carts directory. This simulates an external persistence layer.

    # Filename: stateless_cart_server.py
    import asyncio
    import json
    import os
    from mcp import types as mcp_types
    from mcp.server.lowlevel import Server
    import mcp.server.http_stream

    # --- 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 ---
    app = Server("stateless_shopping_cart_server")

    # 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)

    @app.list_tools()
    async def list_mcp_tools() -> list[mcp_types.Tool]:
    # (Same as Module 30's list_tools function)
    add_item_tool = mcp_types.Tool(name="add_item_to_cart", description="Adds an item to the cart.", inputSchema={"type": "object", "properties": {"item": {"type": "string"}}, "required": ["item"]})
    view_cart_tool = mcp_types.Tool(name="view_cart", description="Views items in the cart.", inputSchema={"type": "object", "properties": {}})
    return [add_item_tool, view_cart_tool]

    @app.call_tool()
    async def call_mcp_tool(name: str, arguments: dict, session_id: str) -> list[mcp_types.Content]:
    print(f"[Server]: Handling '{name}' for session '{session_id}'")
    cart = get_cart(session_id)

    if name == "add_item_to_cart":
    item = arguments.get("item")
    cart.append(item)
    save_cart(session_id, cart)
    response_text = json.dumps({"status": "success", "message": f"Added '{item}'."})
    return [mcp_types.TextContent(type="text", text=response_text)]

    elif name == "view_cart":
    response_text = json.dumps({"status": "success", "cart": cart})
    return [mcp_types.TextContent(type="text", text=response_text)]

    else:
    return [mcp_types.TextContent(type="text", text=json.dumps({"status": "error", "message": "Unknown tool."}))]

    # --- Server Runner for HTTP ---
    # This uses the HTTP stream runner, suitable for Cloud Run
    main = mcp.server.http_stream.create_main(app)

    if __name__ == "__main__":
    main()

Step 2: Containerize the MCP Server

  1. Create a requirements.txt file: Our server needs the mcp library.

    echo "mcp" > requirements.txt
  2. 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.
    # The http_stream runner automatically uses it.
    CMD ["python", "stateless_cart_server.py"]

Step 3: Build and Deploy the Server to Cloud Run

  1. Set environment variables:

    export GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
    export GOOGLE_CLOUD_LOCATION=us-central1
  2. Build and push the image:

    gcloud builds submit \
    --tag ${GOOGLE_CLOUD_LOCATION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/adk-images/mcp-cart-server:v1

    (This assumes you created the adk-images repository in Module 19. If not, you may need to run gcloud artifacts repositories create ... first.)

  3. 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-unauthenticated

    This 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.

  1. Create an agent.py file in the same cloud-mcp-server directory.

  2. Add the following code, replacing YOUR_CLOUD_RUN_SERVICE_URL with the URL you copied.

    # Filename: agent.py
    from google.adk.agents import LlmAgent
    from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
    from mcp import StreamableHTTPConnectionParams # Import this for remote connections

    # The URL of your deployed MCP server
    MCP_SERVER_URL = "YOUR_CLOUD_RUN_SERVICE_URL"

    root_agent = LlmAgent(
    model='gemini-2.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,
    ),
    )
    ],
    )
  3. Create __init__.py and .env files: Create an empty __init__.py file in the cloud-mcp-server directory. Create a .env file in the cloud-mcp-server directory with the following content:

    MODEL="gemini-2.5-flash"

Step 5: Test the Full Cloud-Based System

  1. Start the ADK web server locally: This will run your client agent from the parent directory.

    adk web cloud_shopping_agent
  2. 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 MCPToolset to connect to a remote HTTP-based MCP server using StreamableHTTPConnectionParams.

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.

  1. Delete the Cloud Run Service:

    gcloud run services delete mcp-cart-server \
    --region=$GOOGLE_CLOUD_LOCATION \
    --async # Runs in background
  2. Delete the Artifact Registry Repository:

    gcloud artifacts repositories delete adk-images \
    --location=$GOOGLE_CLOUD_LOCATION \
    --async # Runs in background
  3. Delete the cloud-mcp-server directory:

    cd ..
    rm -rf cloud-mcp-server

Self-Reflection Questions

  • Our stateless server uses the /tmp directory 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 MCPToolset on 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