africa-api-mcp 0.1.1 → 0.3.1

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.
Files changed (3) hide show
  1. package/README.md +22 -24
  2. package/dist/index.js +102 -46
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -8,25 +8,17 @@ An [MCP](https://modelcontextprotocol.io) server that gives Claude direct access
8
8
 
9
9
  Sign up at [africa-api.com](https://africa-api.com) and create an API key from your dashboard.
10
10
 
11
- ### 2. Connect to Claude
11
+ ### 2. Connect your client
12
12
 
13
- **Claude Desktop** add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
13
+ The server runs on demand via `npx` **nothing to install or host**. Pick your client:
14
14
 
15
- ```json
16
- {
17
- "mcpServers": {
18
- "africa-api": {
19
- "command": "npx",
20
- "args": ["-y", "africa-api-mcp"],
21
- "env": {
22
- "AFRICA_API_KEY": "your-api-key-here"
23
- }
24
- }
25
- }
26
- }
15
+ **Claude Code** (one command):
16
+
17
+ ```bash
18
+ claude mcp add africa-api -e AFRICA_API_KEY=your-api-key-here -- npx -y africa-api-mcp
27
19
  ```
28
20
 
29
- **Claude Code** — add to `~/.claude/settings.json`:
21
+ **Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows), then restart:
30
22
 
31
23
  ```json
32
24
  {
@@ -34,15 +26,21 @@ Sign up at [africa-api.com](https://africa-api.com) and create an API key from y
34
26
  "africa-api": {
35
27
  "command": "npx",
36
28
  "args": ["-y", "africa-api-mcp"],
37
- "env": {
38
- "AFRICA_API_KEY": "your-api-key-here"
39
- }
29
+ "env": { "AFRICA_API_KEY": "your-api-key-here" }
40
30
  }
41
31
  }
42
32
  }
43
33
  ```
44
34
 
45
- Restart Claude and you're ready to go. No install step needed `npx` handles it automatically.
35
+ **Cursor** add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project) with the same `mcpServers` block as above.
36
+
37
+ **VS Code** (Copilot agent mode) — one command:
38
+
39
+ ```bash
40
+ code --add-mcp '{"name":"africa-api","command":"npx","args":["-y","africa-api-mcp"],"env":{"AFRICA_API_KEY":"your-api-key-here"}}'
41
+ ```
42
+
43
+ Any other MCP client works with the same `npx -y africa-api-mcp` command plus the `AFRICA_API_KEY` environment variable. The server is also listed in the [MCP Registry](https://registry.modelcontextprotocol.io) as `io.github.africa-api/africa-api-mcp`.
46
44
 
47
45
  ## What You Can Ask Claude
48
46
 
@@ -60,21 +58,21 @@ Once connected, Claude can answer questions like:
60
58
 
61
59
  ## Available Tools
62
60
 
63
- **40 tools** across 9 domains:
61
+ **41 tools** across 9 domains:
64
62
 
65
63
  | Domain | Tools | What It Covers |
66
64
  |--------|-------|----------------|
67
65
  | **Countries** | 4 | Country details, profiles, real-time signals for all 54 nations |
68
- | **Indicators & Data** | 4 | 127+ indicators (GDP, population, health, education, etc.), time-series queries, country rankings |
66
+ | **Indicators & Data** | 4 | 200+ indicators (GDP, population, health, energy, trade, etc.), time-series queries, country rankings |
69
67
  | **Government** | 6 | Heads of state, cabinets, leadership terms — current and historical |
70
68
  | **Elections** | 5 | Election results, upcoming elections, country overviews |
71
- | **Markets** | 7 | Stock exchanges, listed securities, price history, FX rates |
69
+ | **Markets** | 8 | Stock exchanges, index levels, listed securities, price history, FX rates |
72
70
  | **Trade** | 4 | Bilateral trade flows, top partners, product breakdowns |
73
71
  | **Policies** | 6 | Laws, regulations, policy timelines, lifecycle events |
74
- | **Sources** | 2 | Data provenance — World Bank, UN, central banks, etc. |
72
+ | **Sources & platform** | 3 | Data provenance and platform info — World Bank, IMF, UN, Ember, central banks |
75
73
  | **Geographies** | 1 | Continent / region / subregion hierarchy |
76
74
 
77
- All tools are **read-only** with proper MCP safety annotations.
75
+ All tools are **read-only** (annotated `readOnly` + `idempotent`) and return both human-readable text and **structured JSON output** for programmatic clients.
78
76
 
79
77
  ## Configuration
80
78
 
package/dist/index.js CHANGED
@@ -15,10 +15,11 @@ const API_KEY = process.env.AFRICA_API_KEY || "";
15
15
  if (!API_KEY) {
16
16
  console.error("Warning: AFRICA_API_KEY not set. Authenticated endpoints will fail.");
17
17
  }
18
- // Safety annotations — all tools are read-only
18
+ // Safety annotations — every tool is a read-only, idempotent GET against the API.
19
19
  const READ_ONLY = {
20
20
  readOnlyHint: true,
21
21
  destructiveHint: false,
22
+ idempotentHint: true,
22
23
  openWorldHint: true,
23
24
  };
24
25
  // ---------------------------------------------------------------------------
@@ -37,27 +38,69 @@ async function apiGet(path, params) {
37
38
  if (API_KEY) {
38
39
  headers["Authorization"] = `Bearer ${API_KEY}`;
39
40
  }
40
- const resp = await fetch(url.toString(), { headers });
41
+ // Throw on failure so the SDK surfaces it as a tool error (isError: true)
42
+ // rather than the agent mistaking an error payload for a successful result.
43
+ let resp;
44
+ try {
45
+ resp = await fetch(url.toString(), { headers });
46
+ }
47
+ catch (err) {
48
+ const detail = err instanceof Error ? err.message : String(err);
49
+ throw new Error(`Could not reach the Africa API at ${BASE_URL}: ${detail}. Check network connectivity.`);
50
+ }
41
51
  if (resp.status === 401 || resp.status === 403) {
42
- return JSON.stringify({ error: "Authentication failed. Check that AFRICA_API_KEY is set and valid." }, null, 2);
52
+ throw new Error(`Authentication failed (HTTP ${resp.status}). Set a valid AFRICA_API_KEY get a free key at https://africa-api.com/dashboard.`);
43
53
  }
44
54
  if (!resp.ok) {
45
- return JSON.stringify({ error: `API returned ${resp.status}: ${resp.statusText}` }, null, 2);
55
+ let body = "";
56
+ try {
57
+ body = (await resp.text()).slice(0, 300);
58
+ }
59
+ catch {
60
+ // ignore: body is best-effort context for the error message
61
+ }
62
+ throw new Error(`Africa API returned HTTP ${resp.status} ${resp.statusText} for ${path}${body ? `: ${body}` : ""}`);
46
63
  }
64
+ // Compact JSON keeps responses token-efficient for the model's context.
47
65
  const data = await resp.json();
48
- return JSON.stringify(data, null, 2);
66
+ return JSON.stringify(data);
49
67
  }
50
68
  // ---------------------------------------------------------------------------
51
69
  // Server setup
52
70
  // ---------------------------------------------------------------------------
53
71
  const server = new McpServer({
54
72
  name: "Africa API",
55
- version: "0.1.0",
73
+ version: "0.3.0",
56
74
  });
75
+ // Register tools through a wrapper that attaches `structuredContent` (the parsed
76
+ // JSON object) alongside the text response, so clients that support structured
77
+ // output get machine-readable data without re-parsing a string. No outputSchema
78
+ // is declared (the SDK then skips validation and passes structuredContent
79
+ // through as-is), which keeps it robust across heterogeneous API responses.
80
+ // Handler errors throw and bypass this, surfacing as isError via the SDK.
81
+ const registerRaw = server.tool.bind(server);
82
+ function tool(name, description, paramsSchema, annotations, cb) {
83
+ registerRaw(name, description, paramsSchema, annotations, async (args, extra) => {
84
+ const res = (await cb(args, extra));
85
+ const first = res.content?.[0];
86
+ if (first?.type === "text" && typeof first.text === "string" && !res.isError) {
87
+ try {
88
+ const parsed = JSON.parse(first.text);
89
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
90
+ res.structuredContent = parsed;
91
+ }
92
+ }
93
+ catch {
94
+ // Response is not JSON (shouldn't happen for API data) — leave text-only.
95
+ }
96
+ }
97
+ return res;
98
+ });
99
+ }
57
100
  // ===================================================================
58
101
  // COUNTRIES
59
102
  // ===================================================================
60
- server.tool("list_countries", "List all 54 African countries with key facts (capital, region, area, currencies, languages)", {
103
+ tool("list_countries", "List all 54 African countries with key facts (capital, region, area, currencies, languages)", {
61
104
  region: z.string().optional().describe('Filter by region (e.g. "Eastern Africa", "Western Africa")'),
62
105
  sort: z.string().default("name").describe("Sort field"),
63
106
  page: z.number().int().min(1).optional().describe("Page number for pagination"),
@@ -75,17 +118,17 @@ server.tool("list_countries", "List all 54 African countries with key facts (cap
75
118
  },
76
119
  ],
77
120
  }));
78
- server.tool("get_country", "Get detailed information about a specific African country including coordinates, borders, currencies, and languages", {
121
+ tool("get_country", "Get detailed information about a specific African country including coordinates, borders, currencies, and languages", {
79
122
  country_code: z.string().length(2).describe('ISO 3166-1 alpha-2 code (e.g. "NG", "KE", "ZA")'),
80
123
  }, READ_ONLY, async ({ country_code }) => ({
81
124
  content: [{ type: "text", text: await apiGet(`/v1/countries/${country_code}`) }],
82
125
  }));
83
- server.tool("get_country_profile", "Get a curated profile for a country — key macro, demographic, and economic indicators", {
126
+ tool("get_country_profile", "Get a curated profile for a country — key macro, demographic, and economic indicators", {
84
127
  country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
85
128
  }, READ_ONLY, async ({ country_code }) => ({
86
129
  content: [{ type: "text", text: await apiGet(`/v1/countries/${country_code}/profile`) }],
87
130
  }));
88
- server.tool("get_country_signals", "Get real-time signals for a country — macro snapshot, market data, FX rates, power status, humanitarian alerts", {
131
+ tool("get_country_signals", "Get real-time signals for a country — macro snapshot, market data, FX rates, power status, humanitarian alerts", {
89
132
  country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
90
133
  }, READ_ONLY, async ({ country_code }) => ({
91
134
  content: [{ type: "text", text: await apiGet(`/v1/countries/${country_code}/signals`) }],
@@ -93,19 +136,19 @@ server.tool("get_country_signals", "Get real-time signals for a country — macr
93
136
  // ===================================================================
94
137
  // INDICATORS & DATA
95
138
  // ===================================================================
96
- server.tool("list_indicators", "List all available data indicators (127+) across categories like GDP, population, health, education, agriculture, energy, climate", {
139
+ tool("list_indicators", "List all available data indicators (127+) across categories like GDP, population, health, education, agriculture, energy, climate", {
97
140
  category: z.string().optional().describe('Filter by category (e.g. "economy", "health", "education")'),
98
141
  source: z.string().optional().describe("Filter by data source code"),
99
142
  has_data: z.boolean().optional().describe("If true, only return indicators that have observations"),
100
143
  }, READ_ONLY, async ({ category, source, has_data }) => ({
101
144
  content: [{ type: "text", text: await apiGet("/v1/indicators", { category, source, has_data }) }],
102
145
  }));
103
- server.tool("get_indicator", "Get detailed metadata about a specific indicator including description, unit, source, and available years", {
146
+ tool("get_indicator", "Get detailed metadata about a specific indicator including description, unit, source, and available years", {
104
147
  metric_key: z.string().describe('Indicator key (e.g. "gdp_current_usd", "population_total", "life_expectancy")'),
105
148
  }, READ_ONLY, async ({ metric_key }) => ({
106
149
  content: [{ type: "text", text: await apiGet(`/v1/indicators/${metric_key}`) }],
107
150
  }));
108
- server.tool("get_indicator_rankings", "Rank African countries by a specific indicator for a given year — great for comparisons", {
151
+ tool("get_indicator_rankings", "Rank African countries by a specific indicator for a given year — great for comparisons", {
109
152
  metric_key: z.string().describe('Indicator key (e.g. "gdp_current_usd", "population_total")'),
110
153
  year: z.number().int().min(1900).max(2200).describe("Year to rank by"),
111
154
  source: z.string().optional().describe("Source code filter"),
@@ -119,7 +162,7 @@ server.tool("get_indicator_rankings", "Rank African countries by a specific indi
119
162
  },
120
163
  ],
121
164
  }));
122
- server.tool("query_data", "Query time-series observations for any combination of countries and indicators", {
165
+ tool("query_data", "Query time-series observations for any combination of countries and indicators", {
123
166
  country_code: z.string().optional().describe('Single ISO alpha-2 code (e.g. "NG")'),
124
167
  country_codes: z.string().optional().describe('Comma-separated codes (e.g. "NG,KE,ZA")'),
125
168
  metric_key: z.string().optional().describe('Single indicator key (e.g. "gdp_current_usd")'),
@@ -137,7 +180,7 @@ server.tool("query_data", "Query time-series observations for any combination of
137
180
  // ===================================================================
138
181
  // GEOGRAPHIES
139
182
  // ===================================================================
140
- server.tool("list_geographies", "List geographical entities — continents, regions, subregions, and countries in a hierarchy", {
183
+ tool("list_geographies", "List geographical entities — continents, regions, subregions, and countries in a hierarchy", {
141
184
  type: z.string().optional().describe("Filter by geography type"),
142
185
  parent_key: z.string().optional().describe("Filter by parent geography key"),
143
186
  country_code: z.string().optional().describe("Filter by country code"),
@@ -149,12 +192,12 @@ server.tool("list_geographies", "List geographical entities — continents, regi
149
192
  // ===================================================================
150
193
  // GOVERNMENT
151
194
  // ===================================================================
152
- server.tool("get_government_overview", "Get a government overview — current head of state, head of government, and cabinet summary", {
195
+ tool("get_government_overview", "Get a government overview — current head of state, head of government, and cabinet summary", {
153
196
  country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
154
197
  }, READ_ONLY, async ({ country_code }) => ({
155
198
  content: [{ type: "text", text: await apiGet(`/v1/government/overview/${country_code}`) }],
156
199
  }));
157
- server.tool("search_leaders", "Search African heads of state and government — current and historical", {
200
+ tool("search_leaders", "Search African heads of state and government — current and historical", {
158
201
  q: z.string().optional().describe("Search by name"),
159
202
  country_code: z.string().optional().describe("Filter by country (ISO alpha-2)"),
160
203
  current_only: z.boolean().optional().describe("Only current leaders"),
@@ -162,12 +205,12 @@ server.tool("search_leaders", "Search African heads of state and government —
162
205
  }, READ_ONLY, async (params) => ({
163
206
  content: [{ type: "text", text: await apiGet("/v1/government/leaders", params) }],
164
207
  }));
165
- server.tool("get_leader", "Get detailed information about a specific leader including biography, terms, and political party", {
208
+ tool("get_leader", "Get detailed information about a specific leader including biography, terms, and political party", {
166
209
  leader_wikidata_id: z.string().describe('Wikidata ID of the leader (e.g. "Q7939")'),
167
210
  }, READ_ONLY, async ({ leader_wikidata_id }) => ({
168
211
  content: [{ type: "text", text: await apiGet(`/v1/government/leaders/${leader_wikidata_id}`) }],
169
212
  }));
170
- server.tool("list_government_terms", "List leadership terms — who governed which country and when", {
213
+ tool("list_government_terms", "List leadership terms — who governed which country and when", {
171
214
  country_code: z.string().optional().describe("Filter by country"),
172
215
  leader_wikidata_id: z.string().optional().describe("Filter by leader"),
173
216
  role_type: z.enum(["head_of_state", "head_of_government"]).optional().describe("Role type filter"),
@@ -178,12 +221,12 @@ server.tool("list_government_terms", "List leadership terms — who governed whi
178
221
  }, READ_ONLY, async (params) => ({
179
222
  content: [{ type: "text", text: await apiGet("/v1/government/terms", params) }],
180
223
  }));
181
- server.tool("get_cabinet", "Get the current cabinet for a country — ministers, deputy ministers, and key officials", {
224
+ tool("get_cabinet", "Get the current cabinet for a country — ministers, deputy ministers, and key officials", {
182
225
  country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
183
226
  }, READ_ONLY, async ({ country_code }) => ({
184
227
  content: [{ type: "text", text: await apiGet(`/v1/government/cabinet/${country_code}`) }],
185
228
  }));
186
- server.tool("list_cabinet_members", "Search cabinet members across African governments", {
229
+ tool("list_cabinet_members", "Search cabinet members across African governments", {
187
230
  country_code: z.string().optional().describe("Filter by country"),
188
231
  q: z.string().optional().describe("Search by name"),
189
232
  role_category: z
@@ -198,7 +241,7 @@ server.tool("list_cabinet_members", "Search cabinet members across African gover
198
241
  // ===================================================================
199
242
  // ELECTIONS
200
243
  // ===================================================================
201
- server.tool("list_elections", "List elections across Africa — filter by country, scope, status, and year range", {
244
+ tool("list_elections", "List elections across Africa — filter by country, scope, status, and year range", {
202
245
  country_code: z.string().optional().describe("Filter by country"),
203
246
  election_scope: z.enum(["presidential", "parliamentary", "general", "other"]).optional().describe("Election scope"),
204
247
  status: z.enum(["upcoming", "completed", "unknown"]).optional().describe("Election status"),
@@ -208,23 +251,23 @@ server.tool("list_elections", "List elections across Africa — filter by countr
208
251
  }, READ_ONLY, async (params) => ({
209
252
  content: [{ type: "text", text: await apiGet("/v1/elections", params) }],
210
253
  }));
211
- server.tool("get_upcoming_elections", "Get upcoming elections across Africa sorted by date", {
254
+ tool("get_upcoming_elections", "Get upcoming elections across Africa sorted by date", {
212
255
  limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
213
256
  }, READ_ONLY, async ({ limit }) => ({
214
257
  content: [{ type: "text", text: await apiGet("/v1/elections/upcoming", { limit }) }],
215
258
  }));
216
- server.tool("get_country_elections", "Get an election overview for a country — recent and upcoming elections with results", {
259
+ tool("get_country_elections", "Get an election overview for a country — recent and upcoming elections with results", {
217
260
  country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
218
261
  top_limit: z.number().int().min(1).max(20).default(5).describe("Top results per category"),
219
262
  }, READ_ONLY, async ({ country_code, top_limit }) => ({
220
263
  content: [{ type: "text", text: await apiGet(`/v1/elections/country/${country_code}`, { top_limit }) }],
221
264
  }));
222
- server.tool("get_election", "Get detailed information about a specific election", {
265
+ tool("get_election", "Get detailed information about a specific election", {
223
266
  election_wikidata_id: z.string().describe("Wikidata ID of the election"),
224
267
  }, READ_ONLY, async ({ election_wikidata_id }) => ({
225
268
  content: [{ type: "text", text: await apiGet(`/v1/elections/${election_wikidata_id}`) }],
226
269
  }));
227
- server.tool("get_election_results", "Get results for a specific election — candidates, parties, and vote counts", {
270
+ tool("get_election_results", "Get results for a specific election — candidates, parties, and vote counts", {
228
271
  election_wikidata_id: z.string().describe("Wikidata ID of the election"),
229
272
  limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
230
273
  }, READ_ONLY, async ({ election_wikidata_id, limit }) => ({
@@ -233,18 +276,18 @@ server.tool("get_election_results", "Get results for a specific election — can
233
276
  // ===================================================================
234
277
  // MARKETS
235
278
  // ===================================================================
236
- server.tool("list_exchanges", "List African stock exchanges — NGX, JSE, BRVM, Casablanca, etc.", {
279
+ tool("list_exchanges", "List African stock exchanges — NGX, JSE, BRVM, Casablanca, etc.", {
237
280
  country_code: z.string().optional().describe("Filter by country"),
238
281
  limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
239
282
  }, READ_ONLY, async (params) => ({
240
283
  content: [{ type: "text", text: await apiGet("/v1/markets/exchanges", params) }],
241
284
  }));
242
- server.tool("get_exchange", "Get details about a specific stock exchange", {
285
+ tool("get_exchange", "Get details about a specific stock exchange", {
243
286
  exchange_code: z.string().describe('Exchange code (e.g. "NGX", "JSE", "BRVM")'),
244
287
  }, READ_ONLY, async ({ exchange_code }) => ({
245
288
  content: [{ type: "text", text: await apiGet(`/v1/markets/exchanges/${exchange_code}`) }],
246
289
  }));
247
- server.tool("list_tickers", "Search listed securities (equities) across African exchanges", {
290
+ tool("list_tickers", "Search listed securities (equities) across African exchanges", {
248
291
  exchange_code: z.string().optional().describe('Filter by exchange (e.g. "NGX")'),
249
292
  country_code: z.string().optional().describe("Filter by country"),
250
293
  instrument_type: z.string().optional().describe("Filter by instrument type"),
@@ -253,13 +296,13 @@ server.tool("list_tickers", "Search listed securities (equities) across African
253
296
  }, READ_ONLY, async (params) => ({
254
297
  content: [{ type: "text", text: await apiGet("/v1/markets/tickers", params) }],
255
298
  }));
256
- server.tool("get_ticker", "Get details about a specific listed security", {
299
+ tool("get_ticker", "Get details about a specific listed security", {
257
300
  exchange_code: z.string().describe('Exchange code (e.g. "NGX")'),
258
301
  symbol: z.string().describe('Ticker symbol (e.g. "DANGCEM")'),
259
302
  }, READ_ONLY, async ({ exchange_code, symbol }) => ({
260
303
  content: [{ type: "text", text: await apiGet(`/v1/markets/tickers/${exchange_code}/${symbol}`) }],
261
304
  }));
262
- server.tool("get_ticker_history", "Get price history (OHLC + volume) for a listed security", {
305
+ tool("get_ticker_history", "Get price history (OHLC + volume) for a listed security", {
263
306
  exchange_code: z.string().describe("Exchange code"),
264
307
  symbol: z.string().describe("Ticker symbol"),
265
308
  start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
@@ -273,7 +316,20 @@ server.tool("get_ticker_history", "Get price history (OHLC + volume) for a liste
273
316
  },
274
317
  ],
275
318
  }));
276
- server.tool("get_fx_rates", "Get current FX rates for African currencies against a base currency", {
319
+ tool("get_index_history", "Get the main stock-index level history for an exchange (e.g. NGX All-Share, JSE All Share, BRVM Composite) — index value plus total deals, volume, traded value and market cap", {
320
+ exchange_code: z.string().describe('Exchange code (e.g. "NGX", "JSE", "BRVM")'),
321
+ start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
322
+ end_date: z.string().optional().describe("End date (YYYY-MM-DD)"),
323
+ limit: z.number().int().min(1).max(5000).default(365).describe("Max data points"),
324
+ }, READ_ONLY, async ({ exchange_code, start_date, end_date, limit }) => ({
325
+ content: [
326
+ {
327
+ type: "text",
328
+ text: await apiGet(`/v1/markets/exchanges/${exchange_code}/index/history`, { start_date, end_date, limit }),
329
+ },
330
+ ],
331
+ }));
332
+ tool("get_fx_rates", "Get current FX rates for African currencies against a base currency", {
277
333
  base_currency: z.string().length(3).default("USD").describe("Base currency (3-letter code)"),
278
334
  quote_currencies: z.string().optional().describe('Comma-separated quote currencies (e.g. "NGN,KES,ZAR")'),
279
335
  country_code: z.string().optional().describe("Filter by country"),
@@ -281,7 +337,7 @@ server.tool("get_fx_rates", "Get current FX rates for African currencies against
281
337
  }, READ_ONLY, async (params) => ({
282
338
  content: [{ type: "text", text: await apiGet("/v1/markets/fx-rates", params) }],
283
339
  }));
284
- server.tool("get_fx_rate_history", "Get historical FX rate data for a currency pair", {
340
+ tool("get_fx_rate_history", "Get historical FX rate data for a currency pair", {
285
341
  base_currency: z.string().length(3).describe('Base currency (e.g. "USD")'),
286
342
  quote_currency: z.string().length(3).describe('Quote currency (e.g. "NGN", "KES", "ZAR")'),
287
343
  start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
@@ -298,7 +354,7 @@ server.tool("get_fx_rate_history", "Get historical FX rate data for a currency p
298
354
  // ===================================================================
299
355
  // TRADE
300
356
  // ===================================================================
301
- server.tool("get_trade_overview", "Get a trade overview — total exports/imports, top partners, top products, and trends", {
357
+ tool("get_trade_overview", "Get a trade overview — total exports/imports, top partners, top products, and trends", {
302
358
  country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
303
359
  start_year: z.number().int().optional().describe("Start year"),
304
360
  end_year: z.number().int().optional().describe("End year"),
@@ -311,7 +367,7 @@ server.tool("get_trade_overview", "Get a trade overview — total exports/import
311
367
  },
312
368
  ],
313
369
  }));
314
- server.tool("get_trade_flows", "Query bilateral trade flows between countries — exports and imports with values", {
370
+ tool("get_trade_flows", "Query bilateral trade flows between countries — exports and imports with values", {
315
371
  reporter_country_code: z.string().optional().describe("Reporting country (ISO alpha-2)"),
316
372
  partner_country_code: z.string().optional().describe("Partner country code"),
317
373
  product_code: z.string().optional().describe("HS product code"),
@@ -323,7 +379,7 @@ server.tool("get_trade_flows", "Query bilateral trade flows between countries
323
379
  }, READ_ONLY, async (params) => ({
324
380
  content: [{ type: "text", text: await apiGet("/v1/trade/flows", params) }],
325
381
  }));
326
- server.tool("get_trade_partners", "Get top trading partners for a country", {
382
+ tool("get_trade_partners", "Get top trading partners for a country", {
327
383
  reporter_country_code: z.string().length(2).describe("Reporting country (required)"),
328
384
  flow_type: z.enum(["export", "import"]).optional().describe("Flow type"),
329
385
  year: z.number().int().optional().describe("Exact year"),
@@ -333,7 +389,7 @@ server.tool("get_trade_partners", "Get top trading partners for a country", {
333
389
  }, READ_ONLY, async (params) => ({
334
390
  content: [{ type: "text", text: await apiGet("/v1/trade/partners", params) }],
335
391
  }));
336
- server.tool("get_trade_products", "Get top traded products for a country — what it exports and imports", {
392
+ tool("get_trade_products", "Get top traded products for a country — what it exports and imports", {
337
393
  reporter_country_code: z.string().length(2).describe("Reporting country (required)"),
338
394
  flow_type: z.enum(["export", "import"]).optional().describe("Flow type"),
339
395
  year: z.number().int().optional().describe("Exact year"),
@@ -346,7 +402,7 @@ server.tool("get_trade_products", "Get top traded products for a country — wha
346
402
  // ===================================================================
347
403
  // POLICIES
348
404
  // ===================================================================
349
- server.tool("list_policies", "Search government policies, laws, and regulations across Africa", {
405
+ tool("list_policies", "Search government policies, laws, and regulations across Africa", {
350
406
  country_code: z.string().optional().describe("Filter by country"),
351
407
  document_type: z
352
408
  .enum(["constitution", "law", "policy", "strategy", "regulation", "bill", "decree", "other"])
@@ -360,18 +416,18 @@ server.tool("list_policies", "Search government policies, laws, and regulations
360
416
  }, READ_ONLY, async (params) => ({
361
417
  content: [{ type: "text", text: await apiGet("/v1/policies", params) }],
362
418
  }));
363
- server.tool("get_policy", "Get detailed information about a specific policy, law, or regulation", {
419
+ tool("get_policy", "Get detailed information about a specific policy, law, or regulation", {
364
420
  policy_wikidata_id: z.string().describe("Wikidata ID of the policy"),
365
421
  }, READ_ONLY, async ({ policy_wikidata_id }) => ({
366
422
  content: [{ type: "text", text: await apiGet(`/v1/policies/${policy_wikidata_id}`) }],
367
423
  }));
368
- server.tool("get_country_policies", "Get a policy overview for a country — recent and notable policies grouped by type", {
424
+ tool("get_country_policies", "Get a policy overview for a country — recent and notable policies grouped by type", {
369
425
  country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
370
426
  top_limit: z.number().int().min(1).max(20).default(5).describe("Top items per category"),
371
427
  }, READ_ONLY, async ({ country_code, top_limit }) => ({
372
428
  content: [{ type: "text", text: await apiGet(`/v1/policies/country/${country_code}`, { top_limit }) }],
373
429
  }));
374
- server.tool("get_country_policy_timeline", "Get a chronological policy timeline for a country", {
430
+ tool("get_country_policy_timeline", "Get a chronological policy timeline for a country", {
375
431
  country_code: z.string().length(2).describe("ISO alpha-2 code"),
376
432
  top_limit: z.number().int().min(1).max(100).default(20).describe("Number of events"),
377
433
  }, READ_ONLY, async ({ country_code, top_limit }) => ({
@@ -379,7 +435,7 @@ server.tool("get_country_policy_timeline", "Get a chronological policy timeline
379
435
  { type: "text", text: await apiGet(`/v1/policies/country/${country_code}/timeline`, { top_limit }) },
380
436
  ],
381
437
  }));
382
- server.tool("list_policy_events", "List policy lifecycle events — when policies were announced, adopted, amended, repealed", {
438
+ tool("list_policy_events", "List policy lifecycle events — when policies were announced, adopted, amended, repealed", {
383
439
  country_code: z.string().optional().describe("Filter by country"),
384
440
  event_type: z
385
441
  .enum(["announced", "adopted", "implemented", "amended", "repealed", "suspended"])
@@ -391,7 +447,7 @@ server.tool("list_policy_events", "List policy lifecycle events — when policie
391
447
  }, READ_ONLY, async (params) => ({
392
448
  content: [{ type: "text", text: await apiGet("/v1/policies/events", params) }],
393
449
  }));
394
- server.tool("get_policy_events", "Get all lifecycle events for a specific policy", {
450
+ tool("get_policy_events", "Get all lifecycle events for a specific policy", {
395
451
  policy_wikidata_id: z.string().describe("Wikidata ID of the policy"),
396
452
  limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
397
453
  }, READ_ONLY, async ({ policy_wikidata_id, limit }) => ({
@@ -400,10 +456,10 @@ server.tool("get_policy_events", "Get all lifecycle events for a specific policy
400
456
  // ===================================================================
401
457
  // SOURCES
402
458
  // ===================================================================
403
- server.tool("list_sources", "List all data sources — World Bank, UN agencies, central banks, exchanges, etc.", {}, READ_ONLY, async () => ({
459
+ tool("list_sources", "List all data sources — World Bank, UN agencies, central banks, exchanges, etc.", {}, READ_ONLY, async () => ({
404
460
  content: [{ type: "text", text: await apiGet("/v1/sources") }],
405
461
  }));
406
- server.tool("get_source", "Get details about a specific data source — description, URL, coverage, and update frequency", {
462
+ tool("get_source", "Get details about a specific data source — description, URL, coverage, and update frequency", {
407
463
  source_code: z.string().describe("Source code identifier"),
408
464
  }, READ_ONLY, async ({ source_code }) => ({
409
465
  content: [{ type: "text", text: await apiGet(`/v1/sources/${source_code}`) }],
@@ -411,7 +467,7 @@ server.tool("get_source", "Get details about a specific data source — descript
411
467
  // ===================================================================
412
468
  // PLATFORM
413
469
  // ===================================================================
414
- server.tool("get_platform_info", "Get Africa API platform version and status", {}, READ_ONLY, async () => ({
470
+ tool("get_platform_info", "Get Africa API platform version and status", {}, READ_ONLY, async () => ({
415
471
  content: [{ type: "text", text: await apiGet("/v1/platform/version") }],
416
472
  }));
417
473
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "africa-api-mcp",
3
3
  "mcpName": "io.github.africa-api/africa-api-mcp",
4
- "version": "0.1.1",
4
+ "version": "0.3.1",
5
5
  "description": "MCP server for Africa API — access data on all 54 African countries through Claude",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -42,7 +42,7 @@
42
42
  "node": ">=18"
43
43
  },
44
44
  "dependencies": {
45
- "@modelcontextprotocol/sdk": "^1.12.0"
45
+ "@modelcontextprotocol/sdk": "^1.29.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "typescript": "^5.7.0",