Wellington Transport Assistant
Provides tools to search stops, get next departures, and retrieve service alerts from Metlink's real-time public transport data, enabling natural language queries about Wellington transit.
README
Wellington Transport Assistant
A model-powered agent that answers real-time questions about Wellington's public transport. The agent reads live data from Metlink (Wellington's public transport authority), reasons over multiple tool calls, and replies in plain English.
Built with FastMCP, Microsoft Agent Framework, Microsoft Foundry, and Azure Container Apps.
This is a personal project. The code is open source under the MIT license and is designed so anyone can clone the repository and deploy the same setup in their own Azure subscription.
What it does
Ask questions in plain English and get answers from live Metlink data. Some examples:
- "When is the next bus to Karori from Lambton Quay?"
- "Is the Johnsonville train line running normally?"
- "Any disruptions on the Eastbourne ferry today?"
- "I have a meeting at Te Papa in 25 minutes. Walk or bus from Wellington Station?"
The agent decides which tools to call, in what order, and combines the results into a single readable answer.
Architecture

The system has three parts:
- An MCP server running on Azure Container Apps. It wraps the Metlink Open Data API and exposes three tools over HTTPS:
search_stops,next_departures, andservice_alerts. - A language model deployed in Microsoft Foundry. It does the reasoning and the conversation.
- An agent that runs on your local machine. It connects to both the model and the MCP server, orchestrates the tool calls, and presents the answer in a chat window or a command line script.
The agent and the MCP server only talk to each other over HTTPS. The MCP server has no knowledge of the model. The model has no knowledge of the underlying Metlink API. That separation means you can swap any piece without touching the others.
Data source
The agent reads its data from Metlink's Open Data API, the official public API for Wellington's bus, train, ferry, and cable car services. The API is free, requires no payment details, and is rate limited rather than billed.
To use it, sign up at https://opendata.metlink.org.nz, fill in a short form describing your use case, and your API key arrives in your dashboard within a few minutes.
The MCP server in this project uses four Metlink endpoints:
| Endpoint | What it provides |
|---|---|
GET /gtfs/stops |
All stops in the Wellington network (around 3,000 records) |
GET /gtfs/routes |
All routes across bus, train, ferry, and cable car |
GET /stop-predictions?stop_id=... |
Real-time predicted departures for a specific stop |
GET /gtfs-rt/servicealerts |
Current service disruptions and alerts |
The static reference data (stops and routes) is cached in memory for 24 hours because it changes rarely. Real-time data (predictions and alerts) is fetched on every request to keep it accurate.
Prerequisites
You need the following before you start:
| What | Why | Where to get it |
|---|---|---|
| Azure subscription | Hosts the MCP server and the language model | https://azure.microsoft.com |
| Azure CLI installed | Used to deploy resources | https://learn.microsoft.com/cli/azure/install-azure-cli |
| Python 3.11 or newer | Runs the agent and the MCP server locally | https://www.python.org/downloads |
uv package manager |
Manages Python dependencies | https://docs.astral.sh/uv/ |
| Metlink API key | Lets the MCP server call the Metlink data API. Free. | https://opendata.metlink.org.nz |
| Docker (optional) | Only needed if you want to build the image locally. The deploy script builds in the cloud, so you can skip this. | https://www.docker.com |
You also need a Foundry account with a language model deployed. See the Foundry setup section.
Running the commands
Every command in this README runs in a terminal. If you use Visual Studio Code, open one by clicking Terminal > New Terminal in the top menu (or press Ctrl+`). Any other terminal works too: the macOS Terminal app, Windows Terminal, PowerShell, or your favourite. The Azure CLI behaves the same way in all of them.
Getting started
This is the full step by step walkthrough. Follow the seven steps below in order and you will have a working chat window on your laptop. The whole process takes about 30 minutes the first time, most of which is waiting for Azure to provision resources.
Step 1: Install the prerequisites
Install everything listed in the Prerequisites table above. Then sign in to Azure from your terminal:
az login
Step 2: Clone the repository
git clone https://github.com/sdhilip200/metlink-mcp.git
cd metlink-mcp
All the commands below run from the metlink-mcp folder.
Step 3: Deploy the MCP server to Azure
The repository includes a deploy.sh script that runs the four Azure CLI commands needed to create the resource group, build the Docker image in the cloud, deploy the Container App, and store your Metlink API key as a secret.
# Set your Metlink API key as an environment variable so deploy.sh can read it
export METLINK_API_KEY=your_metlink_key_here
# Run the deploy script
./deploy.sh
This takes about five minutes the first time. When it finishes, it prints a URL similar to:
https://ca-metlink-mcp.<random>.australiaeast.azurecontainerapps.io/mcp
Copy this URL. You will need it in Step 5.
Step 4: Set up the model in Microsoft Foundry
The agent uses a language model deployed in Foundry. Follow the three commands in the Foundry setup section below. They create a Foundry account, deploy a model, and write the endpoint and key into your .env file automatically.
Step 5: Configure your local .env file
cp .env.example .env
Open .env in your editor and fill in the five values:
| Variable | What to put |
|---|---|
METLINK_API_KEY |
The Metlink API key from Step 1 |
FOUNDRY_ENDPOINT |
Filled in automatically by Step 4 |
FOUNDRY_API_KEY |
Filled in automatically by Step 4 |
FOUNDRY_DEPLOYMENT |
The deployment name you chose in Step 4 |
MCP_URL |
The URL you copied at the end of Step 3 |
Step 6: Install Python dependencies
uv sync --group foundry --prerelease=allow
The --prerelease=allow flag is needed because the Anthropic provider package is still in beta.
Step 7: Run the agent
You have two ways to run the agent.
Option A: Browser chat window (recommended for hand testing)
uv run --group foundry --prerelease=allow streamlit run examples/streamlit_app.py
The chat window opens at http://localhost:8501. Type a question and watch the agent answer.
Option B: Command line with the six demo queries
uv run --group foundry --prerelease=allow python examples/agent.py
The agent runs through the six demo queries one after another and saves the transcript to examples/demo_transcript.md.
That is it. The agent is now answering Wellington transport questions on your laptop using a model deployed in your Azure subscription.
Running the MCP server locally without Azure
If you want to try the agent before deploying anything to Azure, you can run the MCP server on your own laptop:
uv run python -m src.server
Then set MCP_URL=http://localhost:8000/mcp in your .env instead of the Azure URL, and run Step 7. Everything else works the same.
Repository structure
metlink-mcp/
├── README.md Main documentation, this file
├── LICENSE MIT license
├── .gitignore Files excluded from version control
├── .env.example Template for your local environment file
├── pyproject.toml Python dependencies and project metadata
├── uv.lock Locked dependency versions for reproducibility
├── Dockerfile Builds the MCP server container image
├── .dockerignore Files excluded from the Docker build context
├── deploy.sh One-command Azure deployment script
│
├── src/ MCP server source code
│ ├── __init__.py
│ ├── server.py FastMCP entry point and tool definitions
│ ├── metlink_client.py HTTP client wrapping the Metlink API
│ ├── models.py Pydantic models for parsing Metlink responses
│ ├── cache.py In-memory cache for static reference data
│ └── formatters.py Converts parsed data into readable text
│
├── examples/ Client examples
│ ├── agent.py Command-line agent that runs six demo queries
│ └── streamlit_app.py Local chat user interface
│
├── tests/ Test suite
│ ├── conftest.py Shared pytest configuration
│ ├── test_smoke.py Live API tests
│ ├── test_formatters.py Unit tests for the formatting functions
│ └── fixtures/ Sample Metlink API responses for testing
│
└── docs/ Documentation assets
└── architecture.jpg Architecture diagram
Deployment
The MCP server runs in Azure Container Apps and scales to zero when idle. It costs nothing when nobody is using it.
Option 1: Use the deploy script (recommended)
The repository includes a deploy.sh script that runs the four required Azure CLI commands in order. To use it:
# Sign in to Azure
az login
# Export your Metlink API key
export METLINK_API_KEY=your_metlink_key_here
# Run the script
./deploy.sh
The script takes about five minutes the first time. When it finishes, it prints the public URL of your MCP server. Copy this URL into your local .env file as the MCP_URL value.
Option 2: Run the commands manually
If you prefer to run each step yourself, the four commands are:
# 1. Create the resource group
az group create --name rg-metlink-mcp --location australiaeast
# 2. Build the image and deploy the container app
az containerapp up \
--name ca-metlink-mcp \
--resource-group rg-metlink-mcp \
--location australiaeast \
--environment cae-metlink-mcp \
--source . \
--ingress external \
--target-port 8000
# 3. Store the Metlink API key as a secret
az containerapp secret set \
--name ca-metlink-mcp \
--resource-group rg-metlink-mcp \
--secrets metlink-api-key=$METLINK_API_KEY
# 4. Bind the secret and set the scaling rules
az containerapp update \
--name ca-metlink-mcp \
--resource-group rg-metlink-mcp \
--set-env-vars METLINK_API_KEY=secretref:metlink-api-key \
--min-replicas 0 --max-replicas 1
Step 2 takes about five minutes the first time. It creates an Azure Container Registry, builds the Docker image in the cloud, pushes the image, creates a Container Apps environment, and deploys the app with a public HTTPS URL.
After deployment your MCP server is reachable at a URL similar to:
https://ca-metlink-mcp.<random>.australiaeast.azurecontainerapps.io/mcp
Foundry setup
The agent connects to a language model deployed in Microsoft Foundry. Foundry hosts model families from Anthropic, OpenAI, Meta, Mistral, and others behind a single Azure interface.
To set up a model:
# 1. Create a Foundry account (a Cognitive Services account of kind AIServices)
az cognitiveservices account create \
--name my-foundry-account \
--resource-group rg-metlink-mcp \
--kind AIServices \
--sku S0 \
--location eastus2
# 2. Deploy a model on the account. Replace the model details below with the
# model family you want to use. The Foundry catalogue lists every model
# available in your region, with their model-name, model-version, and
# model-format values.
az cognitiveservices account deployment create \
--name my-foundry-account \
--resource-group rg-metlink-mcp \
--deployment-name my-model \
--model-name <model-name-from-catalogue> \
--model-version <model-version-from-catalogue> \
--model-format <model-format-from-catalogue> \
--sku-name GlobalStandard \
--sku-capacity 250
# 3. Get the endpoint and key and add them to your .env
ENDPOINT=$(az cognitiveservices account show \
--name my-foundry-account \
--resource-group rg-metlink-mcp \
--query properties.endpoint -o tsv)
KEY=$(az cognitiveservices account keys list \
--name my-foundry-account \
--resource-group rg-metlink-mcp \
--query key1 -o tsv)
echo "FOUNDRY_ENDPOINT=$ENDPOINT" >> .env
echo "FOUNDRY_API_KEY=$KEY" >> .env
echo "FOUNDRY_DEPLOYMENT=my-model" >> .env
A quick note on the bash inside step 3 for anyone new to shell scripting. The command az cognitiveservices account show normally returns a big JSON object. The --query properties.endpoint part tells the CLI to return only that one field, and -o tsv strips the surrounding quotes so you get a clean string. The $(...) wrapper runs the command and captures the result into a variable. The same pattern grabs the API key. The >> then appends each value to your .env file, where the agent reads them later.
The --deployment-name is a name you choose. The --model-name, --model-version, and --model-format values must match an entry in the Foundry catalogue exactly. To see what is available in your region, run az cognitiveservices model list --location eastus2 -o table and pick a model.
Configuration
All configuration lives in your local .env file. The repository includes a .env.example template you can copy.
| Variable | What it is | Example |
|---|---|---|
METLINK_API_KEY |
Your Metlink Open Data API key | (40 character string) |
FOUNDRY_ENDPOINT |
The endpoint URL of your Foundry account | https://my-account.cognitiveservices.azure.com |
FOUNDRY_API_KEY |
The API key for your Foundry account | (long string) |
FOUNDRY_DEPLOYMENT |
The name of your model deployment in Foundry | my-model |
MCP_URL |
The public URL of your deployed MCP server | https://ca-metlink-mcp.<random>.australiaeast.azurecontainerapps.io/mcp |
The .env file is excluded from version control. It only exists on your local machine.
Testing
The repository includes a small test suite. To run it:
# Unit tests against the fixture data
uv run pytest
# Live tests against the real Metlink API
# (requires METLINK_API_KEY to be set)
uv run pytest -m live
Troubleshooting
Cold start is slow on the first request. Azure Container Apps scales the MCP server to zero when nobody is calling it. The first request after an idle period takes around 10 to 30 seconds to wake the container up. Every request after that is fast.
Streamlit shows a long blank page on first load. The chat window itself takes a few seconds to initialise. If you do not see the input box after 30 seconds, check the terminal for errors.
Agent does not respond and returns a 529 error. This is an "overloaded" response from the upstream model provider. It usually clears within a few minutes. Try again, or switch to a different model in Foundry.
Tool calls fail with a 401 or 403. Your Metlink API key is missing or incorrect. Check the value in your Container App secret and in your local .env.
Deployment fails with "provider not registered". Run these two commands once per subscription to enable the required Azure providers, then try the deployment again:
az provider register -n Microsoft.App
az provider register -n Microsoft.ContainerRegistry
License
MIT. See the LICENSE file for the full text.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.