captain-drawdown-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+
2
+ # Refactor pause flag — touched manually, never committed
3
+ .analytics-refactor-in-progress
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.5
2
+ Name: captain-drawdown-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for Captain Drawdown's open carbon dioxide removal data: companies, researchers, shops.
5
+ Project-URL: Homepage, https://captaindrawdown.com/api/
6
+ Project-URL: Data, https://captaindrawdown.com/data/
7
+ Author-email: Captain Drawdown / Carbon Drawdown Initiative <info@carbon-drawdown.de>
8
+ License: MIT
9
+ Keywords: CDR,carbon removal,climate,mcp,open data
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: mcp<2,>=1.2
12
+ Description-Content-Type: text/markdown
13
+
14
+ # captain-drawdown-mcp
15
+
16
+ An MCP server that gives Claude, ChatGPT, Cursor and other MCP clients direct access to Captain Drawdown's open data on carbon dioxide removal (CDR):
17
+
18
+ - **Companies**: 969 published CDR companies with pathway, country, liveliness tier, EU SME size class, registries and evidence dates (CC BY-NC-SA 4.0).
19
+ - **Researchers**: 95,000+ researchers with CDR papers since 2021, aggregated by country and pathway, plus top-100 lists per pathway (CC BY 4.0).
20
+ - **Shops**: shops where individuals buy certified removal, with prices (CC BY 4.0).
21
+
22
+ It reads the public static API at `https://captaindrawdown.com/api/v1/` (no key, CORS open) and caches responses for six hours. Every result carries `version`, `as_of`, `license` and `cite_as`.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install captain-drawdown-mcp # or: uvx captain-drawdown-mcp
28
+ ```
29
+
30
+ Claude Desktop / Claude Code (`claude mcp add captain-drawdown -- captain-drawdown-mcp`) or `claude_desktop_config.json`:
31
+
32
+ ```json
33
+ {"mcpServers": {"captain-drawdown": {"command": "captain-drawdown-mcp"}}}
34
+ ```
35
+
36
+ Cursor (`.cursor/mcp.json`): same `command`. Any client that speaks MCP over stdio works.
37
+
38
+ ## Tools
39
+
40
+ | Tool | What it returns |
41
+ |---|---|
42
+ | `stats()` | headline numbers of the newest release |
43
+ | `search_companies(query, country_code, pathway, size_class, region, limit)` | matching companies with slugs |
44
+ | `company(slug)` | one company |
45
+ | `countries()`, `pathways()` | companies per country / per pathway |
46
+ | `researchers_by_country()`, `researchers_by_pathway()` | listed researchers with the calibrated estimate |
47
+ | `top_researchers(pathway, region, limit)` | top 100 worldwide or top 50 in Europe for a pathway |
48
+ | `shops()` | shops with prices |
49
+ | `cite()` | citation and licences |
50
+
51
+ ## Data and licences
52
+
53
+ Pages: https://captaindrawdown.com/data/ · API: https://captaindrawdown.com/api/ · Corrections: info@carbon-drawdown.de. The server code is MIT; the data keeps its own licences (companies CC BY-NC-SA 4.0 because the directory is seeded from Grant Faber's list; researchers and shops CC BY 4.0).
54
+
55
+ Captain Drawdown is a project of the Carbon Drawdown Initiative (Fürth, Germany).
@@ -0,0 +1,42 @@
1
+ # captain-drawdown-mcp
2
+
3
+ An MCP server that gives Claude, ChatGPT, Cursor and other MCP clients direct access to Captain Drawdown's open data on carbon dioxide removal (CDR):
4
+
5
+ - **Companies**: 969 published CDR companies with pathway, country, liveliness tier, EU SME size class, registries and evidence dates (CC BY-NC-SA 4.0).
6
+ - **Researchers**: 95,000+ researchers with CDR papers since 2021, aggregated by country and pathway, plus top-100 lists per pathway (CC BY 4.0).
7
+ - **Shops**: shops where individuals buy certified removal, with prices (CC BY 4.0).
8
+
9
+ It reads the public static API at `https://captaindrawdown.com/api/v1/` (no key, CORS open) and caches responses for six hours. Every result carries `version`, `as_of`, `license` and `cite_as`.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install captain-drawdown-mcp # or: uvx captain-drawdown-mcp
15
+ ```
16
+
17
+ Claude Desktop / Claude Code (`claude mcp add captain-drawdown -- captain-drawdown-mcp`) or `claude_desktop_config.json`:
18
+
19
+ ```json
20
+ {"mcpServers": {"captain-drawdown": {"command": "captain-drawdown-mcp"}}}
21
+ ```
22
+
23
+ Cursor (`.cursor/mcp.json`): same `command`. Any client that speaks MCP over stdio works.
24
+
25
+ ## Tools
26
+
27
+ | Tool | What it returns |
28
+ |---|---|
29
+ | `stats()` | headline numbers of the newest release |
30
+ | `search_companies(query, country_code, pathway, size_class, region, limit)` | matching companies with slugs |
31
+ | `company(slug)` | one company |
32
+ | `countries()`, `pathways()` | companies per country / per pathway |
33
+ | `researchers_by_country()`, `researchers_by_pathway()` | listed researchers with the calibrated estimate |
34
+ | `top_researchers(pathway, region, limit)` | top 100 worldwide or top 50 in Europe for a pathway |
35
+ | `shops()` | shops with prices |
36
+ | `cite()` | citation and licences |
37
+
38
+ ## Data and licences
39
+
40
+ Pages: https://captaindrawdown.com/data/ · API: https://captaindrawdown.com/api/ · Corrections: info@carbon-drawdown.de. The server code is MIT; the data keeps its own licences (companies CC BY-NC-SA 4.0 because the directory is seeded from Grant Faber's list; researchers and shops CC BY 4.0).
41
+
42
+ Captain Drawdown is a project of the Carbon Drawdown Initiative (Fürth, Germany).
@@ -0,0 +1,2 @@
1
+ """Captain Drawdown MCP server package."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ """`python -m captain_drawdown_mcp` starts the stdio server."""
2
+ from .server import main
3
+
4
+ main()
@@ -0,0 +1,111 @@
1
+ """Data access for the Captain Drawdown MCP server: reads the static JSON API
2
+ (https://captaindrawdown.com/api/v1/) and filters in memory. No mcp import
3
+ here so the logic is testable without the SDK."""
4
+ import json
5
+ import time
6
+ from typing import Callable, Optional
7
+ from urllib.request import Request, urlopen
8
+
9
+ API_BASE = "https://captaindrawdown.com/api/v1"
10
+ CACHE_TTL_S = 6 * 3600
11
+
12
+
13
+ def _http_get(url: str) -> dict:
14
+ req = Request(url, headers={"User-Agent": "captain-drawdown-mcp/0.1 (+https://captaindrawdown.com/api/)"})
15
+ with urlopen(req, timeout=30) as r:
16
+ return json.loads(r.read().decode("utf-8"))
17
+
18
+
19
+ class Api:
20
+ """Small cached client. `fetch` is injectable for tests."""
21
+
22
+ def __init__(self, base: str = API_BASE, fetch: Optional[Callable[[str], dict]] = None,
23
+ ttl_s: int = CACHE_TTL_S):
24
+ self.base = base.rstrip("/")
25
+ self.fetch = fetch or _http_get
26
+ self.ttl = ttl_s
27
+ self._cache = {}
28
+
29
+ def get(self, path: str) -> dict:
30
+ now = time.time()
31
+ hit = self._cache.get(path)
32
+ if hit and now - hit[0] < self.ttl:
33
+ return hit[1]
34
+ data = self.fetch(f"{self.base}/{path.lstrip('/')}")
35
+ self._cache[path] = (now, data)
36
+ return data
37
+
38
+ # ── tools ──────────────────────────────────────────────────────────
39
+ def stats(self) -> dict:
40
+ return self.get("stats.json")
41
+
42
+ def company(self, slug: str) -> dict:
43
+ slug = (slug or "").strip().lower()
44
+ if not slug:
45
+ return {"error": "slug required"}
46
+ try:
47
+ return self.get(f"companies/{slug}.json")
48
+ except Exception:
49
+ return {"error": f"unknown slug {slug!r}; use search_companies to find the slug"}
50
+
51
+ def search_companies(self, query: str = "", country_code: str = "", pathway: str = "",
52
+ size_class: str = "", region: str = "", limit: int = 25) -> dict:
53
+ env = self.get("companies.json")
54
+ rows = env.get("data", [])
55
+ q = (query or "").strip().lower()
56
+ cc = (country_code or "").strip().upper()
57
+ pw = (pathway or "").strip().lower()
58
+ sc = (size_class or "").strip().lower()
59
+ rg = (region or "").strip().lower()
60
+ out = []
61
+ for c in rows:
62
+ if q and q not in f"{c.get('name', '')} {c.get('description', '')} {c.get('pathway_primary', '')}".lower():
63
+ continue
64
+ if cc and (c.get("country_code") or "").upper() != cc:
65
+ continue
66
+ if pw and pw not in (c.get("pathway_primary") or "").lower():
67
+ continue
68
+ if sc and sc not in (c.get("size_class") or "").lower():
69
+ continue
70
+ if rg and rg not in (c.get("region") or "").lower():
71
+ continue
72
+ out.append({k: c.get(k) for k in ("name", "slug", "country", "country_code", "region",
73
+ "pathway_primary", "liveliness_tier", "size_class",
74
+ "url", "profile_url")})
75
+ limit = max(1, min(int(limit or 25), 200))
76
+ return {"version": env.get("version"), "as_of": env.get("as_of"), "license": env.get("license"),
77
+ "cite_as": env.get("cite_as"), "matches": len(out), "data": out[:limit]}
78
+
79
+ def countries(self) -> dict:
80
+ return self.get("countries.json")
81
+
82
+ def pathways(self) -> dict:
83
+ return self.get("pathways.json")
84
+
85
+ def researchers_by_country(self) -> dict:
86
+ return self.get("researchers/by-country.json")
87
+
88
+ def researchers_by_pathway(self) -> dict:
89
+ return self.get("researchers/by-pathway.json")
90
+
91
+ def top_researchers(self, pathway: str, region: str = "global", limit: int = 25) -> dict:
92
+ slug = (pathway or "").strip().lower().replace(" ", "-")
93
+ try:
94
+ env = self.get(f"researchers/top/{slug}.json")
95
+ except Exception:
96
+ idx = self.get("index.json")
97
+ return {"error": f"unknown pathway {pathway!r}", "pathways": idx.get("pathway_slugs", [])}
98
+ key = "europe_top50" if (region or "").lower().startswith("eu") else "global_top100"
99
+ rows = env.get("data", {}).get(key, [])
100
+ limit = max(1, min(int(limit or 25), 100))
101
+ return {"version": env.get("version"), "as_of": env.get("as_of"), "license": env.get("license"),
102
+ "cite_as": env.get("cite_as"), "pathway": env.get("data", {}).get("pathway"),
103
+ "ranking": key, "data": rows[:limit]}
104
+
105
+ def shops(self) -> dict:
106
+ return self.get("shops.json")
107
+
108
+ def cite(self) -> dict:
109
+ idx = self.get("index.json")
110
+ return {"version": idx.get("version"), "as_of": idx.get("as_of"), "cite_as": idx.get("cite_as"),
111
+ "licenses": idx.get("licenses"), "data_pages": idx.get("data_pages")}
@@ -0,0 +1,87 @@
1
+ """Captain Drawdown MCP server: carbon removal companies, researchers and shops
2
+ for Claude, ChatGPT, Cursor and other MCP clients. Reads the public static API
3
+ at https://captaindrawdown.com/api/v1/ (no key). Run: `captain-drawdown-mcp`."""
4
+ from mcp.server.fastmcp import FastMCP
5
+
6
+ from .data import Api
7
+
8
+ mcp = FastMCP("captain-drawdown",
9
+ instructions=("Open data on carbon dioxide removal (CDR): a directory of CDR companies, "
10
+ "a census of CDR researchers and shops that sell certified removal. Every result "
11
+ "carries version, as_of, license and cite_as: quote them when you use the numbers. "
12
+ "Companies are CC BY-NC-SA 4.0, researchers and shops CC BY 4.0."))
13
+ api = Api()
14
+
15
+
16
+ @mcp.tool()
17
+ def stats() -> dict:
18
+ """Headline numbers of the newest Captain Drawdown data release (companies, researchers, shops)."""
19
+ return api.stats()
20
+
21
+
22
+ @mcp.tool()
23
+ def search_companies(query: str = "", country_code: str = "", pathway: str = "", size_class: str = "",
24
+ region: str = "", limit: int = 25) -> dict:
25
+ """Search published carbon removal companies. Filters: free text (name, description), ISO country
26
+ code (e.g. DE), pathway (e.g. DAC, Biochar, BECCS, Enhanced Weathering), size class (micro, small,
27
+ medium, large), region (EU27, Europe, United States, China). Returns slugs for `company`."""
28
+ return api.search_companies(query, country_code, pathway, size_class, region, limit)
29
+
30
+
31
+ @mcp.tool()
32
+ def company(slug: str) -> dict:
33
+ """One company by slug (from search_companies): pathway, country, liveliness tier, size class,
34
+ registries, evidence dates, description and profile URL."""
35
+ return api.company(slug)
36
+
37
+
38
+ @mcp.tool()
39
+ def countries() -> dict:
40
+ """Published carbon removal companies per country (ISO 3166-1 alpha-2)."""
41
+ return api.countries()
42
+
43
+
44
+ @mcp.tool()
45
+ def pathways() -> dict:
46
+ """Published carbon removal companies per primary pathway."""
47
+ return api.pathways()
48
+
49
+
50
+ @mcp.tool()
51
+ def researchers_by_country() -> dict:
52
+ """CDR researchers (at least one CDR paper since 2021) per country, with the calibrated estimate."""
53
+ return api.researchers_by_country()
54
+
55
+
56
+ @mcp.tool()
57
+ def researchers_by_pathway() -> dict:
58
+ """CDR researchers per primary pathway."""
59
+ return api.researchers_by_pathway()
60
+
61
+
62
+ @mcp.tool()
63
+ def top_researchers(pathway: str, region: str = "global", limit: int = 25) -> dict:
64
+ """Top researchers for a pathway (biochar, soil-carbon, general-cdr, dac, ocean-cdr,
65
+ enhanced-weathering, beccs), ranked by CDR papers since 2021 with a citation rank; region
66
+ 'global' (top 100) or 'europe' (top 50)."""
67
+ return api.top_researchers(pathway, region, limit)
68
+
69
+
70
+ @mcp.tool()
71
+ def shops() -> dict:
72
+ """Shops where individuals can buy certified carbon removal, with prices, methods and certifiers."""
73
+ return api.shops()
74
+
75
+
76
+ @mcp.tool()
77
+ def cite() -> dict:
78
+ """How to cite the datasets: version, as_of, cite_as per dataset, licences, data pages."""
79
+ return api.cite()
80
+
81
+
82
+ def main() -> None:
83
+ mcp.run()
84
+
85
+
86
+ if __name__ == "__main__":
87
+ main()
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "captain-drawdown-mcp"
7
+ version = "0.1.0"
8
+ description = "MCP server for Captain Drawdown's open carbon dioxide removal data: companies, researchers, shops."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Captain Drawdown / Carbon Drawdown Initiative", email = "info@carbon-drawdown.de" }]
13
+ keywords = ["mcp", "carbon removal", "CDR", "climate", "open data"]
14
+ dependencies = ["mcp>=1.2,<2"]
15
+
16
+ [project.urls]
17
+ Homepage = "https://captaindrawdown.com/api/"
18
+ Data = "https://captaindrawdown.com/data/"
19
+
20
+ [project.scripts]
21
+ captain-drawdown-mcp = "captain_drawdown_mcp.server:main"
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["captain_drawdown_mcp"]