Skip to main content
Glama
sauravkumar329

Weather MCP Server

README.md
# Weather MCP Server

A [Model Context Protocol](https://modelcontextprotocol.io/) server that provides real-time weather data, secured with **AWS Cognito OAuth 2.1** Bearer token authentication.

Implements the full [MCP Authorization Specification (2025-11-25)](https://modelcontextprotocol.io/specification/draft/basic/authorization):

- **RFC 9728** — Protected Resource Metadata (PRM) discovery
- **RFC 6750** — Bearer token usage
- **RFC 7591** — Dynamic Client Registration (DCR) bridged to Cognito

---

## Architecture

```
Client / AI Agent
  │
  ├─ GET  /.well-known/oauth-protected-resource   → discover auth server
  ├─ POST /register                               → dynamic client registration (optional)
  ├─ POST Cognito /oauth2/token                   → exchange credentials for JWT
  └─ POST /mcp   Authorization: Bearer <token>    → call MCP tools
```

Weather data is sourced from [Open-Meteo](https://open-meteo.com/) — free, no API key required.

---

## Project Structure

```
weather-mcp/
├── weather_mcp/
│   ├── __init__.py
│   ├── config.py       # Env var loading (COGNITO_REGION/USER_POOL_ID/DOMAIN_PREFIX, SERVER_URL)
│   ├── auth.py         # JWT validation, middleware, PRM + DCR handlers
│   ├── tools.py        # MCP instance + weather tools
│   └── main.py         # Starlette app factory + uvicorn entrypoint
├── infra/
│   └── cognito.yaml    # CloudFormation — Cognito User Pool, IAM role
├── docs/
│   └── deploy-ecs-express.md  # ECS Express Mode deployment guide
├── pyproject.toml
├── Dockerfile
├── .env.example
└── README.md
```

---

## Prerequisites

- Python 3.13+ and [uv](https://docs.astral.sh/uv/)
- AWS account with CLI configured (`aws configure`)
- Docker (optional, for containerised deployment)

---

## Quick Start

### 1 — Deploy AWS Cognito

```bash
aws cloudformation deploy \
  --template-file infra/cognito.yaml \
  --stack-name weather-mcp \
  --region us-east-1 \
  --capabilities CAPABILITY_NAMED_IAM
```

Get the output values:

```bash
aws cloudformation describe-stacks \
  --stack-name weather-mcp \
  --query "Stacks[0].Outputs" \
  --output table
```

### 2 — Configure environment

```bash
cp .env.example .env
# Fill in COGNITO_REGION, COGNITO_USER_POOL_ID, COGNITO_DOMAIN_PREFIX from CloudFormation Outputs
```

### 3 — Run

**Locally:**

```bash
uv sync
uv run python -m weather_mcp.main
```

**Docker:**

```bash
docker build -t weather-mcp:local .
docker run --env-file .env -p 8000:8000 weather-mcp:local
```

Server starts at `http://0.0.0.0:8000`.

**Deploy to AWS ECS Express Mode:**

See [docs/deploy-ecs-express.md](docs/deploy-ecs-express.md) for the full guide — builds the image, pushes to ECR, and creates a public HTTPS service with auto scaling.

---

## API Endpoints

| Endpoint                                    | Auth         | Description                          |
| ------------------------------------------- | ------------ | ------------------------------------ |
| `GET /health`                               | None         | Health check                         |
| `GET /.well-known/oauth-protected-resource` | None         | RFC 9728 discovery document          |
| `POST /register`                            | None         | RFC 7591 Dynamic Client Registration |
| `POST /mcp`                                 | Bearer token | MCP tools (streamable HTTP)          |

---

## MCP Tools

| Tool                  | Description                                            |
| --------------------- | ------------------------------------------------------ |
| `get_current_weather` | Current weather for any location by latitude/longitude |

---

## Usage

### Dynamic Client Registration (zero pre-configuration)

```bash
# 1. Register a new client
curl -s -X POST http://localhost:8000/register \
  -H "Content-Type: application/json" \
  -d '{"client_name":"my-agent","grant_types":["client_credentials"],"scope":"weather-mcp/read"}'

# 2. Get a token
curl -s -X POST https://weather-mcp-auth.auth.us-east-1.amazoncognito.com/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>&scope=weather-mcp/read"

# 3. Call MCP
curl -s -X POST http://localhost:8000/mcp \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
```

---

## Environment Variables

| Variable                | Description                                                  |
| ----------------------- | ------------------------------------------------------------ |
| `COGNITO_REGION`        | AWS region (e.g. `us-east-1`)                                |
| `COGNITO_USER_POOL_ID`  | Cognito User Pool ID                                         |
| `COGNITO_DOMAIN_PREFIX` | Hosted-UI domain prefix                                      |
| `SERVER_URL`            | Public URL of this server (default: `http://localhost:8000`) |

---

## How Authentication Works

1. A request arrives at `/mcp` without a token → server responds with `401` and a `WWW-Authenticate` header pointing to `/.well-known/oauth-protected-resource`
2. The client fetches the discovery document to find the Cognito authorization server
3. The client obtains a JWT access token from Cognito (via `client_credentials` or Dynamic Client Registration)
4. The client includes `Authorization: Bearer <token>` on subsequent requests
5. The middleware validates the JWT signature against Cognito's JWKS endpoint (RS256, cached 1 hour)

---

## Notes

- The MCP server binds to `0.0.0.0:8000` with DNS rebinding protection disabled (`host="0.0.0.0"`). This is required when running behind a load balancer (e.g. ECS Express Mode ALB) where the `Host` header is the public domain, not `localhost`.
- When deploying to ECS Express Mode, the image is pinned by digest — see the [deployment guide](docs/deploy-ecs-express.md#redeploy-after-an-image-update) for how to update it.

---