Climate MCP Server
Climate MCP Server
A Model Context Protocol (MCP) server that exposes 26 structured analytical tools over Climate Finance Update (CFU) datasets. Designed for use with Claude Desktop (STDIO) or any MCP-compatible client.
Overview
The server enables structured queries against three Climate Finance datasets (DuckDB-backed) — funds, pledges, and projects — without requiring the client to write any data access code. All tools return a consistent JSON envelope and emit audit events for full reproducibility.
Key capabilities:
Fund-level financial analysis (stage totals, conversion ratios, gaps)
Portfolio-wide aggregations by fund type, sector, adaptation/mitigation
Contributor/donor ranking across pledge and deposit stages
Data quality diagnostics (missing value reports)
Schema contract introspection
Run-level audit trail with Markdown export
Project Report
For a narrative project description (problem statement, architecture, deployment, and validation), see:
docs/PROJECT_REPORT.md
Project Structure
climate_mcp_server/
├── data/ # Optional compatibility exports (legacy CSV aliases)
│ ├── fund.csv
│ ├── pledges.csv
│ └── projects.csv
│
├── ingestion_layer/ # CFU download → DuckDB → CSV (runs at MCP startup unless CLIMATE_MCP_SKIP_CFU_PIPELINE=1)
│ ├── cfu_pipeline.py # Main pipeline: scrape dashboard, download Excel, load DuckDB, export CSVs
│ ├── transform.py # Transform / analytics steps used by the pipeline
│ ├── Requirements.txt # Standalone ingestion dependencies (optional)
│ ├── notification developer.py # Optional notifications (email, etc.)
│ └── data/
│ ├── climate_funds.duckdb # Primary store: raw.* + analytics.* tables
│ ├── manifest.json # Last run: URLs, checksums, paths to exports
│ ├── raw_change_report.json # Diff-style report between ingested versions
│ ├── transform_schema_review.json
│ ├── archive/ # Timestamped Excel workbooks (*.xlsx)
│ ├── raw/ # latest.xlsx + latest.sha256.txt (working copy)
│ ├── raw_snapshots/ # Per-sheet CSV snapshots (*_latest.csv)
│ └── exports/ # Versioned CSVs for inspection / non-DuckDB fallback
│ ├── fund/fund1.csv
│ ├── pledges/pledges1.csv
│ └── projects/projects1.csv
│
├── docs/
│ ├── schema_contract.md # Human-readable governed schema contract
│ └── schema_contract_summary.json # Machine-readable contract summary (LLM validation)
│
├── runs/ # Auto-generated audit run logs
│ └── *.md # One Markdown file per run_id
│
├── mcp_server/
│ ├── finance/
│ │ └── server.py # MCP server entry point (FastMCP initialisation)
│ │
│ ├── tools/ # All 26 MCP tools
│ │ ├── registry.py # Central tool registration (single entry point)
│ │ ├── context.py # Shared singleton context: paths, table loader, audit state, and shared helpers
│ │ ├── audit_tools.py # start_run / get_audit_trail / finalize_run
│ │ ├── contract_tools.py # get_schema_contract / get_schema_contract_summary
│ │ ├── discovery_tools.py # list_datasets / describe_dataset / preview_dataset / list_unique_values / resolve_entity
│ │ ├── summary_tools.py # search_funds / get_fund_details / fund_summary / portfolio_summary / sector_summary /
│ │ │ # fund_type_distribution / adaptation_vs_mitigation_summary / compare_funds / fund_stage_totals
│ │ ├── finance_tools.py # fund_conversion_ratios / rank_entities / rank_by_column / filter_funds_by_threshold / fund_stage_gap
│ │ ├── aggregation_tools.py # avg_projects_per_fund_by_type
│ │ └── quality_tools.py # missing_report
│ │
│ └── data/
│ └── datasets.py # Dataset URI registry (cfu://funds → analytics.fund, etc.)
│
├── mcp_client/ # MCP client package
│ ├── CLIENT_ARCHITECTURE.md
│ ├── __init__.py
│ ├── __main__.py
│ ├── agent.py
│ ├── api.py
│ ├── config.py
│ ├── erasmus.py
│ ├── errors.py
│ └── mcp_tools.py
│
├── tests/
│ └── run_tests.py # Self-contained test runner (111 tests, no pytest required)
│
├── pyproject.toml # Project metadata and dependencies
├── uv.lock # Locked dependency versions
├── .python-version # Python version pin
└── .gitignore
Architecture
MCP Client (ClimateGPT)
│
│ JSON-RPC tool request
▼
MCP Server — FastMCP ("finance")
│
├── Tool Layer (7 modules, 26 tools)
│ audit_tools → run lifecycle management
│ contract_tools → schema governance
│ discovery_tools → dataset exploration
│ summary_tools → fund profiles and aggregations
│ finance_tools → ratios, rankings, gaps, thresholds
│ aggregation_tools→ cross-dataset aggregations
│ quality_tools → data completeness diagnostics
│
├── Shared Context (context.py)
│ Singleton ToolContext — thread-safe, mtime-cached dataset loading
│ Shared helpers: _to_num, _records, _load, _norm
│ Audit state: active_run_id, run_events, log_tool_event()
│ Uniform output envelope: tool_result()
│
├── Dataset Registry (datasets.py)
│ URI resolution: cfu://funds → analytics.fund
│ cfu://pledges → analytics.pledges
│ cfu://projects → analytics.projects
│
└── DuckDB Dataset Tables
analytics.fund · analytics.pledges · analytics.projectsDesign principles:
Every tool returns the same JSON envelope:
{"ok": bool, "data": {...}, "meta": {...}, "error": null | "message"}Dataset tables are loaded once and cached by DuckDB file mtime; no redundant reads across tools
All string comparisons are case-insensitive via
_norm()(lowercase + strip)NaN values are replaced with
nullbefore serialisation via_records()The singleton context is protected by a double-checked lock for thread safety
Datasets
URI | Database Table | Legacy Alias | Primary Key | Grain |
|
|
|
| One row per fund |
|
|
|
| One row per pledge record |
|
|
|
| One row per project |
Financial stages (in pipeline order): pledge → deposit → approval → disbursement
Tools Reference
Audit Tools (3)
Tool | Description |
| Begin a new audit run; clears previous events and returns a |
| Compact view of tools called in the current run with datasets and columns used |
| Write the full run log to |
Contract Tools (2)
Tool | Description |
| Machine-readable JSON schema contract (call before cross-dataset analysis) |
| Full schema contract as Markdown text |
Discovery Tools (5)
Tool | Description |
| All registered datasets with URI, primary key, grain, and description |
| Row count, column names, dtypes, and null counts |
| First N rows (max 25) |
| Sorted unique non-null values in a column (max 200) |
| Fuzzy-match a user string against real column values — call before passing names to other tools |
Summary Tools (9)
Tool | Description |
| Keyword search across fund name, type, focus, and sector |
| Full dataset record for a single fund |
| Profile card: metadata, stage metrics, portfolio share, and conversion ratios |
| Portfolio-wide totals: fund count, four-stage sums, adaptation/mitigation counts |
| Totals and percentage share by |
| Totals and percentage share by |
| Funds bucketed into adaptation-only / mitigation-only / both / neither |
| Side-by-side comparison of multiple funds |
| Stage totals for portfolio / single fund / fund_type / sector |
Finance Tools (5)
Tool | Description |
| Four pipeline ratios for one fund (deposit/pledge, approval/deposit, disbursement/approval, disbursement/pledge) |
| Rank a grouping column by a finance stage; supports wide and long dataset layouts |
| Rank by any numeric column (non-stage columns, ascending order, any dataset) |
| Return funds where a ratio metric falls below a threshold |
| Absolute gaps between consecutive stages (pledge→deposit, deposit→approval, approval→disbursement) |
Aggregation Tools (1)
Tool | Description |
| Average approved projects per fund grouped by |
Quality Tools (1)
Tool | Description |
| Missing-value counts grouped by a column — audit data completeness before analysis |
Setup
Prerequisites
Python 3.12+
uvpackage manager
Install
git clone <YOUR_REPO_URL>
cd climate_mcp_server
uv syncVerify the environment:
uv run python -c "import pandas; print('pandas ok')"
uv run python -c "from mcp.server.fastmcp import FastMCP; print('mcp ok')"Verify the database exists:
ls "ingestion_layer/data"
# climate_funds.duckdb manifest.json archive/ exports/ raw/ raw_snapshots/ ...Running the Server
Local / STDIO (default)
uv run python mcp_server/finance/server.pyThe process stays running and waits for an MCP client connection over STDIO. Stop with Ctrl+C.
HTTP instead of STDIO
The server uses STDIO by default. For a VM or remote client, set CLIMATE_MCP_TRANSPORT:
Transport |
| Client protocol |
STDIO (default) | unset or | Process stdin/stdout (Claude Desktop, local Cursor) |
SSE |
| HTTP: GET event stream + POST messages (classic MCP remote) |
Streamable HTTP |
| Single HTTP MCP endpoint (newer transport) |
Optional:
CLIMATE_MCP_HOST— bind address (default0.0.0.0for HTTP modes so the VM accepts remote connections;127.0.0.1for STDIO).CLIMATE_MCP_PORT— listen port (default 8000).CLIMATE_MCP_MOUNT_PATH— URL prefix (default/). Non-root mounts apply to every path below.
SSE example (reachable from your laptop on the lab network):
export CLIMATE_MCP_TRANSPORT=sse
export CLIMATE_MCP_HOST=0.0.0.0
export CLIMATE_MCP_PORT=8000
uv run python mcp_server/finance/server.pyOn startup, stderr prints the exact GET (SSE) and POST (messages) URLs. Defaults from FastMCP: /sse and /messages/ under the mount path.
Streamable HTTP example:
export CLIMATE_MCP_TRANSPORT=streamable-http
export CLIMATE_MCP_HOST=0.0.0.0
export CLIMATE_MCP_PORT=8000
uv run python mcp_server/finance/server.pyDefault MCP path: /mcp (see stderr line for the full URL).
Security: there is no authentication on these HTTP endpoints unless you add a reverse proxy (TLS, firewall, API keys). Do not expose them to the public internet without hardening.
You can still use FastMCP’s FASTMCP_* settings (see library docs) for fine-grained paths like FASTMCP_SSE_PATH if needed.
Connecting with Claude Desktop
Find your repo root path:
pwdIn Claude Desktop → Settings → MCP Servers, add a new server:
Name:
financeTransport:
stdioCommand:
uvArgs:
run,python,mcp_server/finance/server.pyWorking directory: your repo root path
Restart Claude Desktop. The 26 tools will appear in the tool picker.
Running Tests
uv run python tests/run_tests.py --allThe test runner requires no external test framework. It runs two layers:
Layer 1 — Unit tests for all 26 tools (tool behaviour, error paths, output envelope)
Layer 2 — Smoke tests for real-world research questions and use cases (UC1–UC4)
Expected output: 111 PASS | 0 FAIL
Run individual layers:
uv run python tests/run_tests.py --layer1 # unit tests only
uv run python tests/run_tests.py --layer2 # smoke tests onlyDependencies
Package | Version | Purpose |
| ≥ 1.26.0 | FastMCP server framework |
| ≥ 3.0.0 | DataFrame processing and aggregations |
| ≥ 1.4.0 | Primary dataset storage and table queries |
| ≥ 0.28.1 | HTTP transport support |
Python ≥ 3.12 required.