SDG 3.1 Maternal Health AI Agent
Build a multi-agent AI system analyzing WHO maternal mortality data.
Introduction
30 Second Summary
Every year, hundreds of thousands of women die from preventable causes during pregnancy and childbirth. The United Nations set a target (SDG 3.1) to reduce this number dramatically by 2030, but tracking progress across 183 countries requires sifting through massive datasets.
In this project, you will build a multi-agent AI system using Google's Agent Development Kit 2.0 that fetches, analyzes, and generates policy recommendations from real WHO maternal mortality data.
What You'll Build
A three-agent pipeline powered by ADK 2.0 Workflows and a custom MCP server that queries the WHO Global Health Observatory API, performs statistical analysis, and produces actionable health policy insights.
By the end of this project, you'll have:
- A custom MCP server exposing WHO maternal mortality data as structured tools via the Model Context Protocol.
- A graph-based Workflow with three specialized agents (data retrieval, analysis, insight generation) running in a deterministic pipeline.
- Full deployment scaffolding for Cloud Run, an Antigravity SKILL.md for IDE integration, and comprehensive documentation for hackathon submission.
- Secret Mission: Add a trend forecasting tool that uses linear regression to predict whether countries will meet the SDG 3.1 target by 2030.
Are there any prerequisites?
This is an advanced project. You should have Python 3.10+, uv, Node.js, and gcloud CLI installed and authenticated against a Google Cloud project with billing enabled. You also need a Gemini API key from Google AI Studio and the Antigravity IDE.
Scaffold the Project
Before you can build agents that reason about maternal health data, you need a solid project foundation. A well-organized directory structure separates concerns from the start: one place for MCP tools, another for agent logic, and a third for IDE integration.
In this step, you will scaffold the entire project directory, install all Python dependencies, verify that both the ADK and Agents CLI tools work, and configure Antigravity IDE to recognize your project.
In this step, get ready to:
- Create the project directory structure and install all dependencies.
- Verify that the ADK CLI and Agents CLI are operational.
- Configure Antigravity IDE to recognize the project and its skills directory.
Create the project structure and install dependencies
The project uses a clean separation: the mcp_server/ directory holds data tools, agent/ holds ADK agent code, and .agent/skills/ stores the Antigravity IDE skill definitions.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project with uv by running this command:
uv init sdg31-health-agent
What does this command do?
The uv init command creates a new Python project directory with a basic pyproject.toml, a .python-version file, and a sample main.py. You will replace the generated pyproject.toml contents with the project's actual dependencies shortly.
- Move into the project and create the subdirectories by running these commands:
cd sdg31-health-agent
mkdir -p mcp_server agent .agent/skills tests/eval
- Confirm the directories exist by running:
ls -a
You should see mcp_server, agent, .agent, and tests listed alongside the files uv created.
Don't see .agent in the output?
Directories starting with a dot are hidden by default. The -a flag in ls -a shows them. If you ran ls without the flag, try again with ls -a.
help me troubleshoot my project directory structure
Now you will replace the auto-generated pyproject.toml with the project's actual configuration.
- Open pyproject.toml in your editor and replace its entire contents with the following:
[project]
name = "sdg31-health-agent"
version = "0.1.0"
description = "SDG 3.1 Maternal Health Intelligence Agent - Multi-agent system for analyzing global maternal mortality progress"
requires-python = ">=3.10"
dependencies = [
"google-adk==2.3.0",
"mcp>=1.0.0",
"httpx>=0.27.0",
"numpy>=1.26.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
What does this configuration do?
The [project] table names the project and pins four key dependencies. google-adk==2.3.0 is the Agent Development Kit that provides the Agent and Workflow classes plus the adk CLI.
The mcp package provides the Model Context Protocol server framework. httpx handles async HTTP requests to the WHO API, and numpy powers the statistical forecasting in the Secret Mission.
- Save pyproject.toml.
- Install all dependencies by running:
uv sync
You should see uv resolve and install the packages, ending with a summary of installed dependencies. It also creates a .venv virtual environment automatically.
Seeing a resolution error?
If uv sync fails to resolve dependencies, make sure your requires-python is set to >=3.10 and that you are running Python 3.10 or higher. Check with python3 --version.
help me fix uv sync dependency resolution failures
Next, create the environment file that stores your Gemini API key.
- Create the agent/.env file by running:
echo "GOOGLE_API_KEY=your-api-key-here" > agent/.env
- Open agent/.env in your editor and replace your-api-key-here with the actual Gemini API key you obtained from Google AI Studio.
Why is the .env inside the agent/ folder?
The ADK CLI looks for a .env file in the same directory it runs from. Since you will run adk run agent/ later, placing .env inside agent/ ensures the key is available to your agents at runtime.
Finally, create a .gitignore to keep secrets and generated files out of version control.
- Create a .gitignore file in the project root and paste the following:
# Environment and secrets
.env
*.env.local
# Python
__pycache__/
*.py[cod]
*$py.class
*.egg-info/
dist/
build/
# Virtual environments
.venv/
venv/
# IDE
.idea/
.vscode/
*.swp
# OS
.DS_Store
Thumbs.db
# Deployment artifacts
.gcloudignore
What does this protect?
The .env line prevents your API key from ever being committed to Git. The Python and virtual environment patterns keep generated bytecode and local dependencies out of your repository.
- Save .gitignore.
✔️ Awesome, I've got everything!
Great. Double check you have saved both pyproject.toml and .gitignore.
ⓧ I'd like to double check the full code
Here is the full pyproject.toml:
[project]
name = "sdg31-health-agent"
version = "0.1.0"
description = "SDG 3.1 Maternal Health Intelligence Agent - Multi-agent system for analyzing global maternal mortality progress"
requires-python = ">=3.10"
dependencies = [
"google-adk==2.3.0",
"mcp>=1.0.0",
"httpx>=0.27.0",
"numpy>=1.26.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Here is agent/.env:
GOOGLE_API_KEY=your-api-key-here
Here is the full .gitignore:
# Environment and secrets
.env
*.env.local
# Python
__pycache__/
*.py[cod]
*$py.class
*.egg-info/
dist/
build/
# Virtual environments
.venv/
venv/
# IDE
.idea/
.vscode/
*.swp
# OS
.DS_Store
Thumbs.db
# Deployment artifacts
.gcloudignore
Verify your toolchain
With dependencies installed, you now have access to two CLIs: the ADK CLI for running agents locally, and the Agents CLI for deployment and evaluation.
- Confirm the ADK CLI is available by running:
adk --version
✔️ I see a version number
You should see output similar to adk, version 2.3.0. The ADK CLI is ready.
ⓧ Command not found
The adk binary lives inside the virtual environment that uv sync created. Make sure you are running the command from inside the sdg31-health-agent/ directory.
- Try activating the virtual environment first:
source .venv/bin/activate
adk --version
On Windows, use .venv\Scripts\activate instead. If you still see an error, re-run uv sync and check for installation failures.
- Set up the Agents CLI by running:
uvx google-agents-cli setup
- Verify the Agents CLI is operational by running:
agents-cli info
You should see information about your project and the available Agents CLI commands.
Agents CLI command not recognized?
Make sure uvx is available on your PATH (it comes with uv). If agents-cli info fails, try running it through uvx directly: uvx google-agents-cli info.
help me get agents-cli working in my environment
Your API key will be fully validated when you run the agent workflow in a later step. For now, confirm the agent/.env file contains your real key (not the placeholder text).
- Open agent/.env and verify it contains your actual Gemini API key from Google AI Studio.
Configure Antigravity IDE
Antigravity IDE discovers your agent skills through a specific directory convention and a global MCP configuration file. Confirming both paths are in place now prevents confusing errors later.
- Switch to Antigravity IDE (it should already be open from earlier).
- Open the sdg31-health-agent folder in Antigravity IDE using **File** then **Open Folder**.
- In the file explorer sidebar, expand .agent/skills/ and confirm the directory appears.
Where does Antigravity look for MCP config?
Antigravity IDE reads MCP server definitions from ~/.gemini/antigravity/mcp_config.json. You do not need to edit this file yet. In the next step, you will build the MCP server that this config will eventually point to.
- Verify the global MCP configuration path exists by running this in your terminal:
ls ~/.gemini/antigravity/mcp_config.json
✔️ I see the file listed
The MCP config file is in place. Antigravity IDE is ready to discover your MCP server once you build it.
ⓧ No such file or directory
The config directory may not exist yet. Create it by running:
mkdir -p ~/.gemini/antigravity
echo '{}' > ~/.gemini/antigravity/mcp_config.json
This creates an empty JSON config that Antigravity IDE will populate as you register MCP servers.
Your project scaffold is complete. Next up, you will build the custom MCP server that exposes WHO maternal health data as structured tools for your agents.
Build the MCP Server
Your project is scaffolded and all dependencies are installed. Now you need a way for your agents to actually access real-world health data.
In this step, you will build a custom MCP server that wraps the WHO Global Health Observatory API. This server becomes the data layer that your agents call through the Model Context Protocol, cleanly separating reasoning (agents) from tooling (data access).
In this step, get ready to:
- Implement the WHO data fetching module with async API calls.
- Build the MCP server with three registered tools.
- Test the MCP server standalone via stdio.
Implement the WHO data fetching module
The data layer starts with a module that knows how to talk to the WHO GHO OData API. This module handles all HTTP requests and returns structured Python data that the MCP tools will wrap.
- Create the file mcp_server/who_data.py and add the module docstring, imports, and constants by pasting this code:
"""WHO Global Health Observatory data fetching module.
Fetches maternal mortality ratio (MMR) data from the WHO GHO OData API.
Indicator code MDG_0000000026 corresponds to SDG 3.1.1 - Maternal mortality
ratio (per 100,000 live births).
"""
import httpx
from typing import Optional
# WHO GHO OData API base endpoint
GHO_API_BASE = "https://ghoapi.azureedge.net/api"
# Indicator code for Maternal Mortality Ratio (per 100,000 live births)
MMR_INDICATOR_CODE = "MDG_0000000026"
# SDG 3.1 target: less than 70 maternal deaths per 100,000 live births by 2030
SDG_TARGET_MMR = 70
What does this code do?
GHO_API_BASE points to the WHO's public OData endpoint. No authentication is required.
MMR_INDICATOR_CODE is the WHO's internal code for Maternal Mortality Ratio data. SDG_TARGET_MMR is 70 deaths per 100,000 live births, the target that all countries should reach by 2030.
- Below the constants, add the fetch_mmr_by_country function that queries MMR data for a single country:
async def fetch_mmr_by_country(country_code: str) -> list[dict]:
url = f"{GHO_API_BASE}/{MMR_INDICATOR_CODE}"
params = {"$filter": f"SpatialDim eq '{country_code}'"}
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(url, params=params)
response.raise_for_status()
data = response.json()
results = []
for item in data.get("value", []):
if item.get("NumericValue") is not None:
results.append({"country": item.get("SpatialDim", country_code), "year": item.get("TimeDim"), "mmr": item.get("NumericValue"), "above_target": item.get("NumericValue", 0) > SDG_TARGET_MMR})
results.sort(key=lambda x: x["year"])
return results
What does this function do?
It builds a filtered OData query using the country's ISO 3166-1 alpha-3 code (like IND for India or NGA for Nigeria). The response is parsed into a list of dictionaries with the year, the MMR value, and whether that value exceeds the SDG target.
- Below fetch_mmr_by_country, add the broader query functions that fetch data for all countries and filter by target:
async def fetch_mmr_all_countries(year: Optional[int] = None) -> list[dict]:
url = f"{GHO_API_BASE}/{MMR_INDICATOR_CODE}"
params = {}
if year:
params["$filter"] = f"TimeDim eq {year}"
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.get(url, params=params)
response.raise_for_status()
data = response.json()
results = []
for item in data.get("value", []):
if item.get("NumericValue") is not None:
results.append({"country": item.get("SpatialDim"), "year": item.get("TimeDim"), "mmr": item.get("NumericValue"), "above_target": item.get("NumericValue", 0) > SDG_TARGET_MMR})
return results
async def get_countries_above_target(year: int = 2020) -> list[dict]:
all_data = await fetch_mmr_all_countries(year=year)
above_target = [d for d in all_data if d["above_target"]]
above_target.sort(key=lambda x: x["mmr"], reverse=True)
return above_target
What do these functions do?
fetch_mmr_all_countries retrieves MMR data globally, optionally filtered to a specific year. The longer timeout (60 seconds) accounts for the larger response payload.
get_countries_above_target is a helper that filters the global data to only countries exceeding the SDG 3.1 target of 70, sorted from highest to lowest MMR.
- Save mcp_server/who_data.py.
Seeing a syntax error?
Make sure you have a blank line between fetch_mmr_all_countries and get_countries_above_target. Python requires separation between top-level function definitions.
help me fix a syntax error in my who_data.py file
✔️ Awesome, I've got everything!
Great. Make sure you have saved mcp_server/who_data.py before moving on.
ⓧ I'd like to double check the full code
"""WHO Global Health Observatory data fetching module.
Fetches maternal mortality ratio (MMR) data from the WHO GHO OData API.
Indicator code MDG_0000000026 corresponds to SDG 3.1.1 - Maternal mortality
ratio (per 100,000 live births).
"""
import httpx
from typing import Optional
# WHO GHO OData API base endpoint
GHO_API_BASE = "https://ghoapi.azureedge.net/api"
# Indicator code for Maternal Mortality Ratio (per 100,000 live births)
MMR_INDICATOR_CODE = "MDG_0000000026"
# SDG 3.1 target: less than 70 maternal deaths per 100,000 live births by 2030
SDG_TARGET_MMR = 70
async def fetch_mmr_by_country(country_code: str) -> list[dict]:
url = f"{GHO_API_BASE}/{MMR_INDICATOR_CODE}"
params = {"$filter": f"SpatialDim eq '{country_code}'"}
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(url, params=params)
response.raise_for_status()
data = response.json()
results = []
for item in data.get("value", []):
if item.get("NumericValue") is not None:
results.append({"country": item.get("SpatialDim", country_code), "year": item.get("TimeDim"), "mmr": item.get("NumericValue"), "above_target": item.get("NumericValue", 0) > SDG_TARGET_MMR})
results.sort(key=lambda x: x["year"])
return results
async def fetch_mmr_all_countries(year: Optional[int] = None) -> list[dict]:
url = f"{GHO_API_BASE}/{MMR_INDICATOR_CODE}"
params = {}
if year:
params["$filter"] = f"TimeDim eq {year}"
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.get(url, params=params)
response.raise_for_status()
data = response.json()
results = []
for item in data.get("value", []):
if item.get("NumericValue") is not None:
results.append({"country": item.get("SpatialDim"), "year": item.get("TimeDim"), "mmr": item.get("NumericValue"), "above_target": item.get("NumericValue", 0) > SDG_TARGET_MMR})
return results
async def get_countries_above_target(year: int = 2020) -> list[dict]:
all_data = await fetch_mmr_all_countries(year=year)
above_target = [d for d in all_data if d["above_target"]]
above_target.sort(key=lambda x: x["mmr"], reverse=True)
return above_target
Implement the MCP server with tool handlers
Now you will wrap those data functions in an MCP server. The server advertises tools to any MCP client and dispatches calls to the correct handler.
- Create the file mcp_server/server.py and add the module docstring, imports, and server initialization:
"""MCP Server for WHO Maternal Health Data."""
import asyncio
import json
from mcp.server import Server
from mcp import types as mcp_types
from who_data import (
fetch_mmr_by_country,
fetch_mmr_all_countries,
get_countries_above_target,
SDG_TARGET_MMR,
)
app = Server("sdg31-maternal-health-mcp")
What does this code do?
Server from the mcp library creates an MCP-compliant server instance. The string "sdg31-maternal-health-mcp" is the server's name that MCP clients will see during discovery.
The imports from who_data bring in the async functions you just wrote. The server will delegate all actual data fetching to those functions.
- Below the app initialization, add the tool listing handler that advertises your three tools to MCP clients:
@app.list_tools()
async def list_tools() -> list[mcp_types.Tool]:
return [
mcp_types.Tool(name="get_maternal_mortality_data", description="Fetch maternal mortality ratio (MMR) time series data for a specific country from the WHO Global Health Observatory. Returns yearly MMR values per 100,000 live births.", inputSchema={"type": "object", "properties": {"country_code": {"type": "string", "description": "ISO 3166-1 alpha-3 country code (e.g., IND, NGA, BRA)"}}, "required": ["country_code"]}),
mcp_types.Tool(name="get_countries_above_sdg_target", description="Get all countries with maternal mortality ratio above the SDG 3.1 target of 70 per 100,000 live births for a given year.", inputSchema={"type": "object", "properties": {"year": {"type": "integer", "description": "Year to check (e.g., 2020). Defaults to 2020.", "default": 2020}}, "required": []}),
mcp_types.Tool(name="get_global_mmr_snapshot", description="Get a global snapshot of maternal mortality ratios for all countries in a given year.", inputSchema={"type": "object", "properties": {"year": {"type": "integer", "description": "Year for the snapshot (e.g., 2020)."}}, "required": ["year"]}),
]
What does this function do?
The @app.list_tools() decorator registers this function as the handler for the MCP tools/list request. When a client connects and asks "what tools do you have?", this function runs.
Each tool has a name, a human-readable description, and a JSON Schema describing its parameters. The agent uses these descriptions to decide which tool to call.
- Below the list_tools function, add the tool call handler that dispatches incoming requests to the correct data function:
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[mcp_types.TextContent]:
if name == "get_maternal_mortality_data":
country_code = arguments["country_code"].upper()
data = await fetch_mmr_by_country(country_code)
result = {"country": country_code, "indicator": "Maternal Mortality Ratio (per 100,000 live births)", "sdg_target": SDG_TARGET_MMR, "data_points": data, "total_records": len(data)}
elif name == "get_countries_above_sdg_target":
year = arguments.get("year", 2020)
data = await get_countries_above_target(year=year)
result = {"year": year, "sdg_target": SDG_TARGET_MMR, "countries_above_target": len(data), "top_10_highest": data[:10]}
elif name == "get_global_mmr_snapshot":
year = arguments["year"]
data = await fetch_mmr_all_countries(year=year)
above = [d for d in data if d["above_target"]]
below = [d for d in data if not d["above_target"]]
mmr_values = [d["mmr"] for d in data]
result = {"year": year, "total_countries": len(data), "above_target": len(above), "below_target": len(below), "global_average_mmr": round(sum(mmr_values) / len(mmr_values), 1) if mmr_values else 0, "highest_mmr": max(mmr_values) if mmr_values else 0, "lowest_mmr": min(mmr_values) if mmr_values else 0}
else:
result = {"error": f"Unknown tool: {name}"}
return [mcp_types.TextContent(type="text", text=json.dumps(result, indent=2))]
What does this function do?
The @app.call_tool() decorator registers this as the handler for the MCP tools/call request. It receives the tool name and arguments from the agent, runs the appropriate data function, and returns a JSON-serialized result as TextContent.
The get_global_mmr_snapshot handler does inline aggregation (counting countries above/below target, computing averages). This gives the analysis agent pre-computed statistics to work with.
- At the bottom of the file, add the entry point that runs the server over stdio:
async def main():
from mcp.server.stdio import stdio_server
async with stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream, app.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())
Why stdio?
The stdio_server transport lets the MCP server communicate through standard input/output. When ADK spawns this server as a subprocess, it sends JSON-RPC messages over stdin and reads responses from stdout. This is the standard local transport for MCP.
- Save mcp_server/server.py.
Import error from who_data?
The import from who_data import ... works because the MCP server will be run from inside the mcp_server/ directory. Both files live at the same level, so relative imports resolve correctly.
help me fix an import error in my MCP server
✔️ Awesome, I've got everything!
Great. Make sure you have saved mcp_server/server.py before moving on.
ⓧ I'd like to double check the full code
"""MCP Server for WHO Maternal Health Data."""
import asyncio
import json
from mcp.server import Server
from mcp import types as mcp_types
from who_data import (
fetch_mmr_by_country,
fetch_mmr_all_countries,
get_countries_above_target,
SDG_TARGET_MMR,
)
app = Server("sdg31-maternal-health-mcp")
@app.list_tools()
async def list_tools() -> list[mcp_types.Tool]:
return [
mcp_types.Tool(name="get_maternal_mortality_data", description="Fetch maternal mortality ratio (MMR) time series data for a specific country from the WHO Global Health Observatory. Returns yearly MMR values per 100,000 live births.", inputSchema={"type": "object", "properties": {"country_code": {"type": "string", "description": "ISO 3166-1 alpha-3 country code (e.g., IND, NGA, BRA)"}}, "required": ["country_code"]}),
mcp_types.Tool(name="get_countries_above_sdg_target", description="Get all countries with maternal mortality ratio above the SDG 3.1 target of 70 per 100,000 live births for a given year.", inputSchema={"type": "object", "properties": {"year": {"type": "integer", "description": "Year to check (e.g., 2020). Defaults to 2020.", "default": 2020}}, "required": []}),
mcp_types.Tool(name="get_global_mmr_snapshot", description="Get a global snapshot of maternal mortality ratios for all countries in a given year.", inputSchema={"type": "object", "properties": {"year": {"type": "integer", "description": "Year for the snapshot (e.g., 2020)."}}, "required": ["year"]}),
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[mcp_types.TextContent]:
if name == "get_maternal_mortality_data":
country_code = arguments["country_code"].upper()
data = await fetch_mmr_by_country(country_code)
result = {"country": country_code, "indicator": "Maternal Mortality Ratio (per 100,000 live births)", "sdg_target": SDG_TARGET_MMR, "data_points": data, "total_records": len(data)}
elif name == "get_countries_above_sdg_target":
year = arguments.get("year", 2020)
data = await get_countries_above_target(year=year)
result = {"year": year, "sdg_target": SDG_TARGET_MMR, "countries_above_target": len(data), "top_10_highest": data[:10]}
elif name == "get_global_mmr_snapshot":
year = arguments["year"]
data = await fetch_mmr_all_countries(year=year)
above = [d for d in data if d["above_target"]]
below = [d for d in data if not d["above_target"]]
mmr_values = [d["mmr"] for d in data]
result = {"year": year, "total_countries": len(data), "above_target": len(above), "below_target": len(below), "global_average_mmr": round(sum(mmr_values) / len(mmr_values), 1) if mmr_values else 0, "highest_mmr": max(mmr_values) if mmr_values else 0, "lowest_mmr": min(mmr_values) if mmr_values else 0}
else:
result = {"error": f"Unknown tool: {name}"}
return [mcp_types.TextContent(type="text", text=json.dumps(result, indent=2))]
async def main():
from mcp.server.stdio import stdio_server
async with stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream, app.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())
Test the MCP server standalone
Before connecting the server to any agent, you should verify that it starts correctly and advertises its tools. The MCP Inspector is the recommended tool for this.
- Open a terminal in your project root (sdg31-health-agent/) and launch the MCP Inspector against your server by running:
npx @modelcontextprotocol/inspector python mcp_server/server.py
What does this command do?
The MCP Inspector spawns your server as a subprocess over stdio and opens a web UI where you can browse the advertised tools, send test calls, and inspect responses. It is the official developer tool for testing MCP servers.
- Once the Inspector UI opens in your browser, click the Tools tab.
- Verify you see three tools listed: get_maternal_mortality_data, get_countries_above_sdg_target, and get_global_mmr_snapshot.
- Select get_maternal_mortality_data and enter IND as the country_code parameter.
- Click Run and confirm the response contains a JSON object with data_points, total_records, and sdg_target fields.
You should see yearly MMR values for India going back to the early 2000s. Each data point includes a year, mmr value, and an above_target boolean.
Inspector not connecting?
Make sure you are running the command from the project root directory (sdg31-health-agent/), not from inside mcp_server/. The path mcp_server/server.py is relative to where you run the command.
If you see a connection timeout, check that Node.js is installed (required for npx) and that your Python environment has the mcp and httpx packages installed.
help me debug my MCP server connection in the Inspector
- Close the Inspector when you are done testing (press Ctrl+C in the terminal).
Your data layer is complete. The MCP server exposes three structured tools that any MCP-compatible client can discover and call. Next up, you will build the ADK agents that use this server to analyze maternal health trends.
Build the ADK Multi-Agent Workflow
Your MCP server can now fetch and serve WHO maternal mortality data. The next challenge is building the reasoning layer that actually uses that data to answer questions.
In this step, you will create three specialized ADK agents and wire them into a sequential Workflow that processes user queries through a deterministic pipeline. Each agent has a distinct role: one fetches data, one analyzes it, and one generates policy recommendations.
In this step, get ready to:
- Define three specialized agents with distinct instructions.
- Wire the agents into a graph-based Workflow with MCP server integration.
- Test the full multi-agent pipeline locally with real WHO data queries.
Create the agent definitions
ADK discovers agents by directory convention. Your agent/ folder needs an __init__.py to be importable as a Python package and an agent.py that defines a root_agent.
- Create the agent/__init__.py file with this module docstring:
"""SDG 3.1 Maternal Health Intelligence Agent.
This module defines the multi-agent ADK 2.0 workflow for analyzing
maternal mortality data. The architecture uses three specialized agents
in a sequential graph workflow, connected to a custom MCP server for
WHO data access.
"""
Why does ADK need __init__.py?
The ADK CLI uses Python's package discovery system to find your agents. When you run adk run agent/, it looks for an importable module containing a root_agent variable. Without __init__.py, Python won't recognize agent/ as a package.
- Now create agent/agent.py and add the imports and MCP server path configuration:
"""ADK 2.0 Multi-Agent Workflow for SDG 3.1 Analysis.
Architecture:
- data_retrieval_agent: Queries MCP tools to gather maternal health data
- analysis_agent: Performs statistical analysis and SDG target comparison
- insight_agent: Synthesizes findings into policy recommendations
The Workflow class creates a deterministic execution graph that ensures
agents run in sequence, with each agent building on the previous output.
"""
import os
from google.adk import Agent, Workflow
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from mcp import StdioServerParameters
# Path to the MCP server script (must be absolute)
MCP_SERVER_PATH = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"..",
"mcp_server",
"server.py",
)
What does this code do?
The imports bring in the two core ADK classes: Agent (defines an AI's instructions and tools) and Workflow (orchestrates agents in a graph-based flow).
McpToolset and StdioConnectionParams let your agent communicate with the MCP server over stdio. MCP_SERVER_PATH constructs an absolute path to your MCP server script so ADK can spawn it as a subprocess.
- Below the path configuration, add the first agent definition. This agent connects to your MCP server and fetches WHO data:
# Agent 1: Data Retrieval - Calls MCP tools to fetch WHO data
data_retrieval_agent = Agent(
name="data_retrieval_agent",
model="gemini-2.5-flash",
instruction="""You are a data retrieval specialist for maternal health indicators.
Your role is to use the available MCP tools to fetch relevant data from the WHO
Global Health Observatory based on the user's query.
Guidelines:
- For country-specific queries, use get_maternal_mortality_data with the ISO 3166-1
alpha-3 country code (e.g., IND for India, NGA for Nigeria, BRA for Brazil).
- For global comparisons, use get_countries_above_sdg_target or get_global_mmr_snapshot.
- Always retrieve data before passing to the next stage.
- Report the raw data clearly and completely.
""",
tools=[
McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command="python",
args=[os.path.abspath(MCP_SERVER_PATH)],
),
),
)
],
)
What does this agent do?
The data_retrieval_agent is the only agent with direct access to tools. It uses McpToolset to spawn your MCP server as a subprocess and call its tools (get_maternal_mortality_data, get_countries_above_sdg_target, get_global_mmr_snapshot) over stdio.
The instruction field tells the LLM how to interpret user queries and which tool to call for different question types. The command="python" setting launches the MCP server script as a Python process.
Why separate reasoning from tooling?
By putting data access in an MCP server and reasoning in ADK agents, you get a clean separation of concerns. The MCP server is reusable by any MCP-compatible client. The agents focus purely on interpreting, analyzing, and recommending.
This architecture also means you can swap out the data source (a different API, a local database) without touching the agent logic.
- Add the second and third agent definitions below data_retrieval_agent. These agents handle analysis and insight generation:
# Agent 2: Analysis - Computes statistics and SDG progress assessment
analysis_agent = Agent(
name="analysis_agent",
model="gemini-2.5-flash",
instruction="""You are a statistical analyst specializing in maternal health metrics.
Analyze the data provided by the data retrieval agent.
Your analysis must include:
1. Trend identification: Is the MMR increasing, decreasing, or stagnant?
2. SDG 3.1 target comparison: The target is less than 70 maternal deaths per
100,000 live births by 2030. How far is the country/region from this target?
3. Rate of change: Calculate the average annual reduction rate.
4. Contextual factors: Note any significant inflection points or anomalies.
Present your analysis with specific numbers and percentages.
Do not make recommendations - that is the next agent's role.
""",
)
# Agent 3: Insight Generation - Produces policy-level recommendations
insight_agent = Agent(
name="insight_agent",
model="gemini-2.5-flash",
instruction="""You are a global health policy advisor specializing in maternal health.
Based on the statistical analysis provided, generate actionable insights and
recommendations.
Your response must include:
1. Executive Summary: 2-3 sentence overview of the maternal health situation.
2. Key Findings: Bullet points of the most important data insights.
3. SDG 3.1 Progress Assessment: On track, at risk, or off track for 2030?
4. Policy Recommendations: 3-5 specific, evidence-based recommendations
for improving maternal health outcomes.
5. Priority Actions: What should be done immediately vs. medium-term?
Be specific and actionable. Reference the actual MMR numbers in your response.
""",
)
What do these agents do?
The analysis_agent receives the raw data from the first agent and performs statistical analysis. It identifies trends, computes reduction rates, and compares against the SDG 3.1 target of 70 per 100,000.
The insight_agent takes the analysis and produces actionable policy recommendations. Neither of these agents has tools. They rely purely on the LLM's reasoning capabilities applied to the structured data passed through the workflow.
You won't see a change yet. The next chunk wires these agents into a Workflow that defines the execution order.
Wire agents into a Workflow with MCP integration
ADK's Workflow class creates a deterministic execution graph. Instead of letting agents decide who speaks next (which can be unpredictable), you define fixed edges that guarantee a specific execution order.
- Add the Workflow definition at the bottom of agent/agent.py:
# Root agent: Sequential workflow connecting all three agents
root_agent = Workflow(
name="sdg31_health_analyst",
edges=[
("START", data_retrieval_agent, analysis_agent, insight_agent),
],
)
What does this Workflow do?
The edges tuple defines a sequential pipeline: START flows to data_retrieval_agent, then to analysis_agent, then to insight_agent. Each agent receives the output of the previous agent as context.
The variable root_agent is the magic name ADK looks for. When you run adk run agent/, it discovers and executes this Workflow.
- Save agent/__init__.py and agent/agent.py.
✔️ Awesome, I've got everything!
Great. Double check you have saved both files in the agent/ directory.
ⓧ I'd like to double check the full code
Here is the complete agent/agent.py for comparison:
"""ADK 2.0 Multi-Agent Workflow for SDG 3.1 Analysis.
Architecture:
- data_retrieval_agent: Queries MCP tools to gather maternal health data
- analysis_agent: Performs statistical analysis and SDG target comparison
- insight_agent: Synthesizes findings into policy recommendations
The Workflow class creates a deterministic execution graph that ensures
agents run in sequence, with each agent building on the previous output.
"""
import os
from google.adk import Agent, Workflow
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from mcp import StdioServerParameters
# Path to the MCP server script (must be absolute)
MCP_SERVER_PATH = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"..",
"mcp_server",
"server.py",
)
# Agent 1: Data Retrieval - Calls MCP tools to fetch WHO data
data_retrieval_agent = Agent(
name="data_retrieval_agent",
model="gemini-2.5-flash",
instruction="""You are a data retrieval specialist for maternal health indicators.
Your role is to use the available MCP tools to fetch relevant data from the WHO
Global Health Observatory based on the user's query.
Guidelines:
- For country-specific queries, use get_maternal_mortality_data with the ISO 3166-1
alpha-3 country code (e.g., IND for India, NGA for Nigeria, BRA for Brazil).
- For global comparisons, use get_countries_above_sdg_target or get_global_mmr_snapshot.
- Always retrieve data before passing to the next stage.
- Report the raw data clearly and completely.
""",
tools=[
McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command="python",
args=[os.path.abspath(MCP_SERVER_PATH)],
),
),
)
],
)
# Agent 2: Analysis - Computes statistics and SDG progress assessment
analysis_agent = Agent(
name="analysis_agent",
model="gemini-2.5-flash",
instruction="""You are a statistical analyst specializing in maternal health metrics.
Analyze the data provided by the data retrieval agent.
Your analysis must include:
1. Trend identification: Is the MMR increasing, decreasing, or stagnant?
2. SDG 3.1 target comparison: The target is less than 70 maternal deaths per
100,000 live births by 2030. How far is the country/region from this target?
3. Rate of change: Calculate the average annual reduction rate.
4. Contextual factors: Note any significant inflection points or anomalies.
Present your analysis with specific numbers and percentages.
Do not make recommendations - that is the next agent's role.
""",
)
# Agent 3: Insight Generation - Produces policy-level recommendations
insight_agent = Agent(
name="insight_agent",
model="gemini-2.5-flash",
instruction="""You are a global health policy advisor specializing in maternal health.
Based on the statistical analysis provided, generate actionable insights and
recommendations.
Your response must include:
1. Executive Summary: 2-3 sentence overview of the maternal health situation.
2. Key Findings: Bullet points of the most important data insights.
3. SDG 3.1 Progress Assessment: On track, at risk, or off track for 2030?
4. Policy Recommendations: 3-5 specific, evidence-based recommendations
for improving maternal health outcomes.
5. Priority Actions: What should be done immediately vs. medium-term?
Be specific and actionable. Reference the actual MMR numbers in your response.
""",
)
# Root agent: Sequential workflow connecting all three agents
root_agent = Workflow(
name="sdg31_health_analyst",
edges=[
("START", data_retrieval_agent, analysis_agent, insight_agent),
],
)
Seeing an import error?
If you see an error about google.adk not being found, make sure your virtual environment is activated and that you installed google-adk==2.3.0 via uv in Step 1.
If the MCP-related imports fail, confirm that mcp is installed (it should be in your pyproject.toml dependencies).
help me debug an import error in my ADK agent
Test the full workflow locally
With all three agents wired into a Workflow, you can test the entire pipeline end-to-end. ADK's CLI spawns the Workflow, which in turn spawns the MCP server subprocess to fetch live WHO data.
- Run the agent workflow interactively by running this command from your sdg31-health-agent/ project root:
adk run agent/
- When the interactive prompt appears, type this test query:
What is India's maternal mortality trend from 2000 to 2022?
What should I see?
The workflow executes all three agents in sequence. You should see the data_retrieval_agent call the MCP server's get_maternal_mortality_data tool with country code IND. Then the analysis_agent produces a statistical breakdown. Finally, the insight_agent delivers policy recommendations with specific MMR numbers.
The full response takes 10-30 seconds as each agent processes in turn. India's MMR has declined significantly since 2000, so expect the analysis to note a downward trend.
- Exit the interactive session by pressing Ctrl+C.
- Also test via the web UI by running:
adk web agent/
- Open the URL shown in your terminal (typically http://localhost:8000) in your browser.
- Try the same query in the web chat interface and verify the three-agent pipeline produces a coherent analysis with data, statistics, and recommendations.
- Stop the web server with Ctrl+C when done.
Agent not producing output?
Make sure your agent/.env file contains a valid GOOGLE_API_KEY value (not the placeholder). The key must be from Google AI Studio with Gemini API access enabled.
If you see a timeout or connection error from the MCP server, verify that mcp_server/server.py runs successfully on its own (you tested this in the previous step).
help me troubleshoot my ADK workflow not producing output
Your multi-agent workflow is running. All three agents execute in a deterministic sequence, and the MCP server provides clean data access without coupling the reasoning logic to the API layer. Next up, you will create the Antigravity SKILL.md and evaluation dataset to make your agent discoverable and testable.
Create the SKILL.md and Evaluation Dataset
Your multi-agent workflow is running and producing coherent analyses. The next step is making it discoverable by the Antigravity IDE and verifiable through automated evaluation.
Antigravity uses a special file called SKILL.md to understand what your agent can do and when to invoke it. You will also create an evaluation dataset that lets you systematically test your agent's quality across different query types.
In this step, get ready to:
- Author an Antigravity SKILL.md that registers your agent workflow as an IDE-invocable skill.
- Create an evaluation dataset with structured test cases for agent quality testing.
- Run evaluation commands to generate traces and grade your agent's responses.
Author the SKILL.md
Antigravity loads skills using progressive disclosure. It reads the YAML frontmatter (name, description, and which tools the skill needs) first, then loads the markdown body only when the skill is actually invoked.
- Create the skills directory structure by running this command:
mkdir -p .agent/skills/sdg31-analyst
- Create a new file called SKILL.md inside the .agent/skills/sdg31-analyst/ directory.
- Paste the following content into the file:
---
name: sdg31-analyst
description: Analyze global maternal health progress toward SDG 3.1 using WHO data. Fetches maternal mortality ratios, computes trends, and generates policy recommendations.
tools:
- mcp_sdg31-maternal-health-mcp_*
---
# SDG 3.1 Maternal Health Analyst
## When to Use
Invoke this skill when the user asks about:
- Maternal mortality trends for any country or region
- Progress toward SDG 3.1 (reducing MMR below 70 per 100,000)
- Comparisons of maternal health between countries
- Policy recommendations for improving maternal health outcomes
## Execution Steps
1. Parse the user query to identify the country/region and time period
2. Use the MCP tools to fetch data from the WHO Global Health Observatory
3. Run the three-agent analysis pipeline (data retrieval, analysis, insights)
4. Present findings with specific numbers, trend analysis, and recommendations
## Example Prompts
- "Analyze India's maternal mortality progress since 2000"
- "Which countries are furthest from the SDG 3.1 target?"
- "Compare maternal health outcomes in Sub-Saharan Africa vs South Asia"
- "Will Brazil meet the SDG 3.1 target by 2030?"
What does this file do?
The YAML frontmatter tells Antigravity three things: the skill's name (sdg31-analyst), a description of what it does, and which tools it needs. The mcp_sdg31-maternal-health-mcp_* wildcard references all tools from your MCP server.
The markdown body describes when the skill should activate, the execution pipeline, and example prompts. Antigravity uses this to match user intent to your agent workflow.
- Save SKILL.md.
- Confirm the file is in the right location by running:
ls .agent/skills/sdg31-analyst/
You should see SKILL.md listed in the output.
Don't see SKILL.md?
Make sure you created the full nested directory path. The mkdir -p flag creates all intermediate directories. If you're missing a level, re-run the mkdir command above.
Also check that the file is named exactly SKILL.md (all caps for SKILL) and not skill.md.
Help me debug my SKILL.md file placement
Why progressive disclosure?
Antigravity loads the frontmatter (metadata) first and only reads the body when the skill is triggered. This keeps the IDE responsive when many skills are installed, because it only parses the full instructions for the skill it actually needs.
Create the evaluation dataset
Evaluation datasets let you systematically test how your agent handles different query types. Each test case defines a prompt and the expected behavior, so you can catch regressions when you change agent instructions or tools.
- Create the evaluation directory structure by running this command:
mkdir -p tests/eval/evalsets
- Create a new file called sdg31.evalset.json inside tests/eval/evalsets/.
- Paste the following evaluation dataset into the file:
[
{
"name": "india_mmr_trend",
"prompt": "What is India's maternal mortality trend from 2000 to 2022?",
"expected_behavior": "Agent should retrieve India MMR data, identify the declining trend, and provide policy recommendations."
},
{
"name": "countries_above_target",
"prompt": "Which countries have the highest maternal mortality ratios and are furthest from the SDG 3.1 target?",
"expected_behavior": "Agent should use get_countries_above_sdg_target tool and identify top countries with highest MMR."
},
{
"name": "global_snapshot_2020",
"prompt": "Give me a global overview of maternal mortality in 2020. How many countries have met the SDG target?",
"expected_behavior": "Agent should use get_global_mmr_snapshot for 2020 and provide counts of countries above/below target with policy context."
}
]
What does this evaluation dataset cover?
- india_mmr_trend tests a single-country query. The agent should call get_maternal_mortality_data with country code IND.
- countries_above_target tests a global comparison. The agent should call get_countries_above_sdg_target and rank the results.
- global_snapshot_2020 tests an overview query. The agent should call get_global_mmr_snapshot for 2020 and contextualize the numbers.
- Save sdg31.evalset.json.
Now use the Agents CLI to run the evaluation. This sends each test prompt through your agent and records the traces.
- Generate evaluation traces by running this command:
agents-cli eval generate
What does this command do?
This sends each prompt from your evalset through the agent workflow, recording the full execution trace (which tools were called, what data came back, and what the final output was). The traces are stored locally for grading.
- Grade the evaluation results by running:
agents-cli eval grade
You should see a quality report showing how each test case performed. The grading assesses whether the agent used the correct tools and produced relevant output for each prompt.
Eval command not found?
Make sure you installed the Agents CLI earlier with uvx google-agents-cli setup. Verify it's available by running agents-cli info.
Help me troubleshoot the agents-cli eval command
✔️ Awesome, I've got everything!
Great. Double check you have saved both files before moving on.
ⓧ I'd like to double check the full code
Here is the full SKILL.md file:
---
name: sdg31-analyst
description: Analyze global maternal health progress toward SDG 3.1 using WHO data. Fetches maternal mortality ratios, computes trends, and generates policy recommendations.
tools:
- mcp_sdg31-maternal-health-mcp_*
---
# SDG 3.1 Maternal Health Analyst
## When to Use
Invoke this skill when the user asks about:
- Maternal mortality trends for any country or region
- Progress toward SDG 3.1 (reducing MMR below 70 per 100,000)
- Comparisons of maternal health between countries
- Policy recommendations for improving maternal health outcomes
## Execution Steps
1. Parse the user query to identify the country/region and time period
2. Use the MCP tools to fetch data from the WHO Global Health Observatory
3. Run the three-agent analysis pipeline (data retrieval, analysis, insights)
4. Present findings with specific numbers, trend analysis, and recommendations
## Example Prompts
- "Analyze India's maternal mortality progress since 2000"
- "Which countries are furthest from the SDG 3.1 target?"
- "Compare maternal health outcomes in Sub-Saharan Africa vs South Asia"
- "Will Brazil meet the SDG 3.1 target by 2030?"
And here is the full sdg31.evalset.json file:
[
{
"name": "india_mmr_trend",
"prompt": "What is India's maternal mortality trend from 2000 to 2022?",
"expected_behavior": "Agent should retrieve India MMR data, identify the declining trend, and provide policy recommendations."
},
{
"name": "countries_above_target",
"prompt": "Which countries have the highest maternal mortality ratios and are furthest from the SDG 3.1 target?",
"expected_behavior": "Agent should use get_countries_above_sdg_target tool and identify top countries with highest MMR."
},
{
"name": "global_snapshot_2020",
"prompt": "Give me a global overview of maternal mortality in 2020. How many countries have met the SDG target?",
"expected_behavior": "Agent should use get_global_mmr_snapshot for 2020 and provide counts of countries above/below target with policy context."
}
]
Your agent is now discoverable by Antigravity and has a repeatable quality gate. Next up, you will scaffold deployment infrastructure and write documentation for hackathon submission.
Deploy and Document
Your agents are tested, evaluated, and ready for production. The final piece of any hackathon submission is showing judges that your system can actually ship.
In this step, you will generate deployment infrastructure targeting Cloud Run, write comprehensive documentation with an architecture diagram, and run a final end-to-end test to confirm everything works together.
In this step, get ready to:
- Generate Cloud Run deployment scaffolding with Agents CLI.
- Write a comprehensive README.md with architecture diagram and setup instructions.
- Run a final end-to-end test confirming the full agent pipeline works.
Add Cloud Run deployment scaffolding
The Agents CLI can generate production deployment infrastructure for your agent project. This includes Terraform configurations, CI/CD pipelines, and container definitions that target Cloud Run.
- Generate the deployment scaffold by running this command:
agents-cli scaffold enhance --deployment-target cloud_run --yes
What does this command do?
The scaffold enhance command adds deployment infrastructure to your existing project without modifying your agent code.
The --deployment-target cloud_run flag tells it to generate Cloud Run-specific configuration. The --yes flag accepts all defaults automatically.
- Confirm the new files exist by running:
ls deployment/terraform/
You should see directories like dev/, staging/, prod/, and a variables.tf file.
- Verify the deployment configuration is valid by running a dry run:
agents-cli deploy --dry-run
This validates your configuration without actually deploying anything. You should see a summary of what would be deployed without any errors.
Seeing a configuration error?
Make sure you have a Google Cloud project configured with gcloud. The dry-run validates that your project settings are accessible.
If you see a permissions error, confirm your gcloud CLI is authenticated to your project. Help me troubleshoot the agents-cli deploy dry-run error I'm seeing.
Write comprehensive README.md
Hackathon judges evaluate documentation quality heavily. A clear README with an architecture diagram shows that you understand your own system design.
- Create a README.md file in the root of your sdg31-health-agent folder, then paste the following content:
# SDG 3.1 Maternal Health Intelligence Agent
A multi-agent AI system using Google ADK 2.0 to analyze WHO maternal mortality data toward SDG 3.1 progress.
## Problem Statement
SDG 3.1 aims to reduce global maternal mortality to less than 70 per 100,000
live births by 2030. Monitoring progress requires analyzing complex time-series
data from 183 countries. This agent automates that analysis through a
multi-agent pipeline that retrieves, analyzes, and generates policy insights
from WHO Global Health Observatory data.
## Architecture
```
+-----------------------------------------------------------+
| Cloud Run Service |
+-----------------------------------------------------------+
| |
| +-----------------------------------------------------+ |
| | ADK 2.0 Workflow (root_agent) | |
| | | |
| | START -> data_retrieval_agent | |
| | -> analysis_agent | |
| | -> insight_agent | |
| +----------+------------------------------------------+ |
| | MCP (stdio) |
| +----------v------------------------------------------+ |
| | MCP Server (sdg31-maternal-health-mcp) | |
| | | |
| | Tools: get_maternal_mortality_data | |
| | get_countries_above_sdg_target | |
| | get_global_mmr_snapshot | |
| +----------+------------------------------------------+ |
| | HTTPS |
+-------------+---------------------------------------------+
v
+-----------------------------------------------------------+
| WHO Global Health Observatory (OData API) |
| https://ghoapi.azureedge.net/api/ |
+-----------------------------------------------------------+
```
## Design Decisions
- **MCP for data access**: Separates reasoning (agents) from tooling (MCP),
making the data layer reusable by other agents without code changes.
- **Graph Workflow over delegation**: Deterministic sequential execution
ensures each agent builds on verified output from the previous stage.
- **Three-agent pipeline**: Mirrors the analytical process (gather, analyze,
recommend) with clear separation of concerns.
## Setup
### Prerequisites
- Python 3.10+
- uv package manager
- Google Cloud project with billing enabled
- Gemini API key from Google AI Studio
- gcloud CLI authenticated
### Installation
1. Clone this repository
2. Install dependencies: `uv sync`
3. Add your Gemini API key to `agent/.env`:
```
GOOGLE_API_KEY=your-key-here
```
4. Verify the setup: `adk run agent/`
## Usage
Run the agent interactively:
```bash
adk run agent/
```
Or use the web UI:
```bash
adk web agent/
```
### Example Prompts
- "What is India's maternal mortality trend from 2000 to 2022?"
- "Which countries are furthest from the SDG 3.1 target?"
- "Give me a global overview of maternal mortality in 2020."
## Security
- API keys are stored in `agent/.env`, which is excluded via `.gitignore`.
- Never commit `.env` files or hardcode API keys in source code.
- The MCP server communicates via local stdio, not over the network.
- Cloud Run deployment uses IAM for access control.
- See `.gitignore` for the full list of excluded paths.
What does this README cover?
The README covers five areas that hackathon judges look for: a clear problem statement, a visual architecture diagram, design rationale explaining your choices, setup instructions for reproducibility, and a security section showing you handle credentials responsibly.
The ASCII architecture diagram gives judges an immediate mental model of your system without needing to read every source file.
- Save README.md in the root of your project folder.
README not rendering correctly?
Make sure the file is saved as README.md (with the .md extension) in the root sdg31-health-agent/ directory, not inside a subdirectory.
If the ASCII diagram looks misaligned, check that your editor uses a monospace font for markdown preview. Help me fix my README formatting issue.
✔️ Awesome, I've got everything!
Great. Double check you have saved README.md in the project root.
ⓧ I'd like to double check the full code
Here is the full README.md for comparison:
# SDG 3.1 Maternal Health Intelligence Agent
A multi-agent AI system using Google ADK 2.0 to analyze WHO maternal mortality data toward SDG 3.1 progress.
## Problem Statement
SDG 3.1 aims to reduce global maternal mortality to less than 70 per 100,000
live births by 2030. Monitoring progress requires analyzing complex time-series
data from 183 countries. This agent automates that analysis through a
multi-agent pipeline that retrieves, analyzes, and generates policy insights
from WHO Global Health Observatory data.
## Architecture
```
+-----------------------------------------------------------+
| Cloud Run Service |
+-----------------------------------------------------------+
| |
| +-----------------------------------------------------+ |
| | ADK 2.0 Workflow (root_agent) | |
| | | |
| | START -> data_retrieval_agent | |
| | -> analysis_agent | |
| | -> insight_agent | |
| +----------+------------------------------------------+ |
| | MCP (stdio) |
| +----------v------------------------------------------+ |
| | MCP Server (sdg31-maternal-health-mcp) | |
| | | |
| | Tools: get_maternal_mortality_data | |
| | get_countries_above_sdg_target | |
| | get_global_mmr_snapshot | |
| +----------+------------------------------------------+ |
| | HTTPS |
+-------------+---------------------------------------------+
v
+-----------------------------------------------------------+
| WHO Global Health Observatory (OData API) |
| https://ghoapi.azureedge.net/api/ |
+-----------------------------------------------------------+
```
## Design Decisions
- **MCP for data access**: Separates reasoning (agents) from tooling (MCP),
making the data layer reusable by other agents without code changes.
- **Graph Workflow over delegation**: Deterministic sequential execution
ensures each agent builds on verified output from the previous stage.
- **Three-agent pipeline**: Mirrors the analytical process (gather, analyze,
recommend) with clear separation of concerns.
## Setup
### Prerequisites
- Python 3.10+
- uv package manager
- Google Cloud project with billing enabled
- Gemini API key from Google AI Studio
- gcloud CLI authenticated
### Installation
1. Clone this repository
2. Install dependencies: `uv sync`
3. Add your Gemini API key to `agent/.env`:
```
GOOGLE_API_KEY=your-key-here
```
4. Verify the setup: `adk run agent/`
## Usage
Run the agent interactively:
```bash
adk run agent/
```
Or use the web UI:
```bash
adk web agent/
```
### Example Prompts
- "What is India's maternal mortality trend from 2000 to 2022?"
- "Which countries are furthest from the SDG 3.1 target?"
- "Give me a global overview of maternal mortality in 2020."
## Security
- API keys are stored in `agent/.env`, which is excluded via `.gitignore`.
- Never commit `.env` files or hardcode API keys in source code.
- The MCP server communicates via local stdio, not over the network.
- Cloud Run deployment uses IAM for access control.
- See `.gitignore` for the full list of excluded paths.
Final cleanup and submission preparation
Before submitting, you need to verify your security settings, confirm your code is well-commented, and run one final integration test.
- Open .gitignore and confirm these entries are present:
cat .gitignore | grep -E '\.env|__pycache__'
You should see .env and __pycache__/ in the output, confirming that secrets and bytecode are excluded from version control.
Why does .gitignore matter for hackathons?
Judges check whether you handle credentials responsibly. A .gitignore that excludes .env shows you understand production security practices. Committing API keys to a public repo is a common disqualifier.
- Review your code files (agent/agent.py, mcp_server/server.py, mcp_server/who_data.py) and confirm each file has:
- A module-level docstring explaining the file's purpose.
- Inline comments above each logical section explaining design decisions.
- Comments on key variables describing what they hold.
Code comments win hackathon points
Judges often skim code rather than running it. Comments like # Separation of concerns: MCP handles data, agents handle reasoning show architectural thinking at a glance.
- Run the final end-to-end test to confirm your full agent pipeline works:
adk run agent/
- When prompted, type this test query:
What is India's maternal mortality trend from 2000 to 2022?
You should see all three agents execute in sequence: the data retrieval agent fetches WHO data, the analysis agent computes statistics, and the insight agent produces policy recommendations.
Agent not producing a full response?
If only one or two agents respond, check that your root_agent Workflow in agent/agent.py has all three agents in the edges tuple.
If you see a connection error, confirm your GOOGLE_API_KEY is set correctly in agent/.env. Help me debug why my ADK workflow isn't executing all three agents.
Secret mission
SDG 3.1 Trend Forecasting Tool
Your agent can analyze historical data, but can it predict the future? Add a trend forecasting tool that uses linear regression to project whether countries will meet the SDG 3.1 target by 2030. This transforms your system from a retrospective reporter into a forward-looking policy intelligence engine.
Clean Up Your Resources
Clean Up Your Resources
Most of this project runs locally, so there are no ongoing costs unless you deployed to Cloud Run beyond the dry-run step. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Cost warning
If you deployed your agent to Cloud Run (beyond the dry-run), the service is live and could incur costs after the free tier of 2 million requests per month. Cloud Run scales to zero when idle, so you will not be charged for a service with no traffic.
If you only ran the dry-run command, no cloud resources were created and there is nothing to clean up on GCP.
Resources you used:
- Local project folder sdg31-health-agent/ on your Desktop.
- Cloud Run service sdg31-health-agent (only if you deployed beyond the dry-run).
- Google Cloud APIs enabled (Cloud Run, Cloud Build, Artifact Registry) in your GCP project (only if deployed).
Keep everything running
No action needed. Choose this if you plan to continue developing the agent, demo it at the hackathon, or extend it further.
- Your local project files stay on your Desktop in the sdg31-health-agent/ folder.
- If deployed, Cloud Run scales to zero automatically when idle. No traffic means no charges.
- Your Gemini API key remains in agent/.env (already excluded from version control by .gitignore).
Pause - I'll come back to this later
Cloud Run auto-scales to zero when idle, so there is no separate "pause" action for the cloud service. For local cleanup, you can stop any running processes.
- Stop the ADK web server if it is still running by pressing Ctrl+C in the terminal.
- Your project files remain on your Desktop. You can reopen the project at any time.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step that applies to you.
- Delete the local project folder by running this command (macOS/Linux):
rm -rf ~/Desktop/sdg31-health-agent
On Windows, run:
Remove-Item -Recurse -Force ~\Desktop\sdg31-health-agent
- If you deployed to Cloud Run, delete the service by running:
gcloud run services delete sdg31-health-agent --region=us-west1
What does this command do?
This removes the Cloud Run service and its associated revisions. It does not delete your GCP project or disable the APIs. If you want to disable the enabled APIs (Cloud Run, Cloud Build, Artifact Registry), you can do so from the APIs & Services page in the Google Cloud Console.
- Delete your Gemini API key from Google AI Studio if you no longer need it. Visit the Google AI Studio API keys page and delete the key you created for this project.
Nice Work!
Nice Work!
You just built a production-grade multi-agent AI system that analyzes real WHO maternal health data using Google's Agent Development Kit 2.0. This is a complete hackathon-ready submission with architecture, tooling, evaluation, and deployment all wired together.
Here's what you learned:
- Built a custom MCP server that exposes WHO Global Health Observatory data as structured tools, demonstrating proper separation of reasoning (agents) from tooling (data layer).
- Created a multi-agent ADK 2.0 Workflow with three specialized agents (data retrieval, analysis, and insight generation) wired into a deterministic graph pipeline using McpToolset and StdioConnectionParams.
- Generated Cloud Run deployment scaffolding via Agents CLI, authored an Antigravity SKILL.md for IDE integration, and wrote comprehensive documentation with architecture diagrams for hackathon submission.
- Secret Mission: Added a trend forecasting tool using numpy linear regression to predict whether countries will meet the SDG 3.1 target by 2030.
Ready to quiz yourself?