africa-api-mcp 0.1.0

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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/dist/index.js +428 -0
  4. package/package.json +50 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Africa API
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # Africa API MCP Server
2
+
3
+ An [MCP](https://modelcontextprotocol.io) server that gives Claude direct access to the [Africa API](https://africa-api.com) — comprehensive data on all 54 African nations including economic indicators, markets, trade, government, elections, and policies.
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Get an API key
8
+
9
+ Sign up at [africa-api.com](https://africa-api.com) and create an API key from your dashboard.
10
+
11
+ ### 2. Connect to Claude
12
+
13
+ **Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
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
+ }
27
+ ```
28
+
29
+ **Claude Code** — add to `~/.claude/settings.json`:
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "africa-api": {
35
+ "command": "npx",
36
+ "args": ["-y", "africa-api-mcp"],
37
+ "env": {
38
+ "AFRICA_API_KEY": "your-api-key-here"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ Restart Claude and you're ready to go. No install step needed — `npx` handles it automatically.
46
+
47
+ ## What You Can Ask Claude
48
+
49
+ Once connected, Claude can answer questions like:
50
+
51
+ - "What's the GDP of Nigeria vs South Africa over the last 10 years?"
52
+ - "Show me current FX rates for East African currencies"
53
+ - "Who is the head of state of Kenya and when did they take office?"
54
+ - "What elections are coming up in Africa this year?"
55
+ - "What are Nigeria's top 5 export products?"
56
+ - "Show me the policy timeline for Rwanda"
57
+ - "Rank African countries by life expectancy"
58
+ - "What stocks are listed on the Nigerian Exchange?"
59
+ - "Compare trade flows between Kenya and Tanzania"
60
+
61
+ ## Available Tools
62
+
63
+ **40 tools** across 9 domains:
64
+
65
+ | Domain | Tools | What It Covers |
66
+ |--------|-------|----------------|
67
+ | **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 |
69
+ | **Government** | 6 | Heads of state, cabinets, leadership terms — current and historical |
70
+ | **Elections** | 5 | Election results, upcoming elections, country overviews |
71
+ | **Markets** | 7 | Stock exchanges, listed securities, price history, FX rates |
72
+ | **Trade** | 4 | Bilateral trade flows, top partners, product breakdowns |
73
+ | **Policies** | 6 | Laws, regulations, policy timelines, lifecycle events |
74
+ | **Sources** | 2 | Data provenance — World Bank, UN, central banks, etc. |
75
+ | **Geographies** | 1 | Continent / region / subregion hierarchy |
76
+
77
+ All tools are **read-only** with proper MCP safety annotations.
78
+
79
+ ## Configuration
80
+
81
+ | Environment Variable | Required | Default | Description |
82
+ |---------------------|----------|---------|-------------|
83
+ | `AFRICA_API_KEY` | Yes | — | Your Africa API bearer token |
84
+ | `AFRICA_API_BASE_URL` | No | `https://api.africa-api.com` | Override for local development |
85
+
86
+ ## Development
87
+
88
+ ```bash
89
+ git clone https://github.com/africa-api/africa-api-mcp.git
90
+ cd africa-api-mcp
91
+ npm install
92
+ npm run build
93
+
94
+ # Run directly
95
+ AFRICA_API_KEY=your-key node dist/index.js
96
+
97
+ # Test with MCP inspector
98
+ AFRICA_API_KEY=your-key npx @modelcontextprotocol/inspector node dist/index.js
99
+ ```
100
+
101
+ ## License
102
+
103
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Africa API MCP Server (Node.js)
4
+ * Exposes the Africa API as tools for Claude via the Model Context Protocol.
5
+ * Covers: Countries, Indicators, Data, Markets, Trade, Government, Elections, Policies, Sources.
6
+ */
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import { z } from "zod";
10
+ // ---------------------------------------------------------------------------
11
+ // Configuration
12
+ // ---------------------------------------------------------------------------
13
+ const BASE_URL = process.env.AFRICA_API_BASE_URL || "https://api.africa-api.com";
14
+ const API_KEY = process.env.AFRICA_API_KEY || "";
15
+ if (!API_KEY) {
16
+ console.error("Warning: AFRICA_API_KEY not set. Authenticated endpoints will fail.");
17
+ }
18
+ // Safety annotations — all tools are read-only
19
+ const READ_ONLY = {
20
+ readOnlyHint: true,
21
+ destructiveHint: false,
22
+ openWorldHint: true,
23
+ };
24
+ // ---------------------------------------------------------------------------
25
+ // HTTP helper
26
+ // ---------------------------------------------------------------------------
27
+ async function apiGet(path, params) {
28
+ const url = new URL(path, BASE_URL);
29
+ if (params) {
30
+ for (const [key, value] of Object.entries(params)) {
31
+ if (value !== undefined && value !== null) {
32
+ url.searchParams.set(key, String(value));
33
+ }
34
+ }
35
+ }
36
+ const headers = { Accept: "application/json" };
37
+ if (API_KEY) {
38
+ headers["Authorization"] = `Bearer ${API_KEY}`;
39
+ }
40
+ const resp = await fetch(url.toString(), { headers });
41
+ 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);
43
+ }
44
+ if (!resp.ok) {
45
+ return JSON.stringify({ error: `API returned ${resp.status}: ${resp.statusText}` }, null, 2);
46
+ }
47
+ const data = await resp.json();
48
+ return JSON.stringify(data, null, 2);
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Server setup
52
+ // ---------------------------------------------------------------------------
53
+ const server = new McpServer({
54
+ name: "Africa API",
55
+ version: "0.1.0",
56
+ });
57
+ // ===================================================================
58
+ // COUNTRIES
59
+ // ===================================================================
60
+ server.tool("list_countries", "List all 54 African countries with key facts (capital, region, area, currencies, languages)", {
61
+ region: z.string().optional().describe('Filter by region (e.g. "Eastern Africa", "Western Africa")'),
62
+ sort: z.string().default("name").describe("Sort field"),
63
+ page: z.number().int().min(1).optional().describe("Page number for pagination"),
64
+ per_page: z.number().int().min(1).max(100).default(20).describe("Items per page"),
65
+ }, READ_ONLY, async ({ region, sort, page, per_page }) => ({
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: await apiGet("/v1/countries", {
70
+ region,
71
+ sort,
72
+ ...(page !== undefined ? { paginate: true, page } : {}),
73
+ per_page,
74
+ }),
75
+ },
76
+ ],
77
+ }));
78
+ server.tool("get_country", "Get detailed information about a specific African country including coordinates, borders, currencies, and languages", {
79
+ country_code: z.string().length(2).describe('ISO 3166-1 alpha-2 code (e.g. "NG", "KE", "ZA")'),
80
+ }, READ_ONLY, async ({ country_code }) => ({
81
+ content: [{ type: "text", text: await apiGet(`/v1/countries/${country_code}`) }],
82
+ }));
83
+ server.tool("get_country_profile", "Get a curated profile for a country — key macro, demographic, and economic indicators", {
84
+ country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
85
+ }, READ_ONLY, async ({ country_code }) => ({
86
+ content: [{ type: "text", text: await apiGet(`/v1/countries/${country_code}/profile`) }],
87
+ }));
88
+ server.tool("get_country_signals", "Get real-time signals for a country — macro snapshot, market data, FX rates, power status, humanitarian alerts", {
89
+ country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
90
+ }, READ_ONLY, async ({ country_code }) => ({
91
+ content: [{ type: "text", text: await apiGet(`/v1/countries/${country_code}/signals`) }],
92
+ }));
93
+ // ===================================================================
94
+ // INDICATORS & DATA
95
+ // ===================================================================
96
+ server.tool("list_indicators", "List all available data indicators (127+) across categories like GDP, population, health, education, agriculture, energy, climate", {
97
+ category: z.string().optional().describe('Filter by category (e.g. "economy", "health", "education")'),
98
+ source: z.string().optional().describe("Filter by data source code"),
99
+ has_data: z.boolean().optional().describe("If true, only return indicators that have observations"),
100
+ }, READ_ONLY, async ({ category, source, has_data }) => ({
101
+ content: [{ type: "text", text: await apiGet("/v1/indicators", { category, source, has_data }) }],
102
+ }));
103
+ server.tool("get_indicator", "Get detailed metadata about a specific indicator including description, unit, source, and available years", {
104
+ metric_key: z.string().describe('Indicator key (e.g. "gdp_current_usd", "population_total", "life_expectancy")'),
105
+ }, READ_ONLY, async ({ metric_key }) => ({
106
+ content: [{ type: "text", text: await apiGet(`/v1/indicators/${metric_key}`) }],
107
+ }));
108
+ server.tool("get_indicator_rankings", "Rank African countries by a specific indicator for a given year — great for comparisons", {
109
+ metric_key: z.string().describe('Indicator key (e.g. "gdp_current_usd", "population_total")'),
110
+ year: z.number().int().min(1900).max(2200).describe("Year to rank by"),
111
+ source: z.string().optional().describe("Source code filter"),
112
+ limit: z.number().int().min(1).max(100).default(10).describe("Number of results"),
113
+ order: z.enum(["asc", "desc"]).default("desc").describe("Sort order"),
114
+ }, READ_ONLY, async ({ metric_key, year, source, limit, order }) => ({
115
+ content: [
116
+ {
117
+ type: "text",
118
+ text: await apiGet(`/v1/indicators/${metric_key}/rankings`, { year, source, limit, order }),
119
+ },
120
+ ],
121
+ }));
122
+ server.tool("query_data", "Query time-series observations for any combination of countries and indicators", {
123
+ country_code: z.string().optional().describe('Single ISO alpha-2 code (e.g. "NG")'),
124
+ country_codes: z.string().optional().describe('Comma-separated codes (e.g. "NG,KE,ZA")'),
125
+ metric_key: z.string().optional().describe('Single indicator key (e.g. "gdp_current_usd")'),
126
+ metric_keys: z.string().optional().describe("Comma-separated indicator keys"),
127
+ category: z.string().optional().describe("Filter by indicator category"),
128
+ source: z.string().optional().describe("Filter by source code"),
129
+ year: z.number().int().optional().describe("Exact year (cannot combine with start_year/end_year)"),
130
+ start_year: z.number().int().optional().describe("Start of year range"),
131
+ end_year: z.number().int().optional().describe("End of year range"),
132
+ latest: z.boolean().default(false).describe("Only return most recent observation per country+indicator"),
133
+ limit: z.number().int().min(1).max(1000).default(100).describe("Max results"),
134
+ }, READ_ONLY, async (params) => ({
135
+ content: [{ type: "text", text: await apiGet("/v1/data", params) }],
136
+ }));
137
+ // ===================================================================
138
+ // GEOGRAPHIES
139
+ // ===================================================================
140
+ server.tool("list_geographies", "List geographical entities — continents, regions, subregions, and countries in a hierarchy", {
141
+ type: z.string().optional().describe("Filter by geography type"),
142
+ parent_key: z.string().optional().describe("Filter by parent geography key"),
143
+ country_code: z.string().optional().describe("Filter by country code"),
144
+ q: z.string().optional().describe("Search query"),
145
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
146
+ }, READ_ONLY, async (params) => ({
147
+ content: [{ type: "text", text: await apiGet("/v1/geographies", params) }],
148
+ }));
149
+ // ===================================================================
150
+ // GOVERNMENT
151
+ // ===================================================================
152
+ server.tool("get_government_overview", "Get a government overview — current head of state, head of government, and cabinet summary", {
153
+ country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
154
+ }, READ_ONLY, async ({ country_code }) => ({
155
+ content: [{ type: "text", text: await apiGet(`/v1/government/overview/${country_code}`) }],
156
+ }));
157
+ server.tool("search_leaders", "Search African heads of state and government — current and historical", {
158
+ q: z.string().optional().describe("Search by name"),
159
+ country_code: z.string().optional().describe("Filter by country (ISO alpha-2)"),
160
+ current_only: z.boolean().optional().describe("Only current leaders"),
161
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
162
+ }, READ_ONLY, async (params) => ({
163
+ content: [{ type: "text", text: await apiGet("/v1/government/leaders", params) }],
164
+ }));
165
+ server.tool("get_leader", "Get detailed information about a specific leader including biography, terms, and political party", {
166
+ leader_wikidata_id: z.string().describe('Wikidata ID of the leader (e.g. "Q7939")'),
167
+ }, READ_ONLY, async ({ leader_wikidata_id }) => ({
168
+ content: [{ type: "text", text: await apiGet(`/v1/government/leaders/${leader_wikidata_id}`) }],
169
+ }));
170
+ server.tool("list_government_terms", "List leadership terms — who governed which country and when", {
171
+ country_code: z.string().optional().describe("Filter by country"),
172
+ leader_wikidata_id: z.string().optional().describe("Filter by leader"),
173
+ role_type: z.enum(["head_of_state", "head_of_government"]).optional().describe("Role type filter"),
174
+ current_only: z.boolean().optional().describe("Only current terms"),
175
+ start_date_from: z.string().optional().describe("Filter terms from date (YYYY-MM-DD)"),
176
+ end_date_to: z.string().optional().describe("Filter terms to date (YYYY-MM-DD)"),
177
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
178
+ }, READ_ONLY, async (params) => ({
179
+ content: [{ type: "text", text: await apiGet("/v1/government/terms", params) }],
180
+ }));
181
+ server.tool("get_cabinet", "Get the current cabinet for a country — ministers, deputy ministers, and key officials", {
182
+ country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
183
+ }, READ_ONLY, async ({ country_code }) => ({
184
+ content: [{ type: "text", text: await apiGet(`/v1/government/cabinet/${country_code}`) }],
185
+ }));
186
+ server.tool("list_cabinet_members", "Search cabinet members across African governments", {
187
+ country_code: z.string().optional().describe("Filter by country"),
188
+ q: z.string().optional().describe("Search by name"),
189
+ role_category: z
190
+ .enum(["minister", "vice_president", "deputy_prime_minister", "attorney_general", "cabinet_secretary", "executive_officeholder", "other"])
191
+ .optional()
192
+ .describe("Filter by role"),
193
+ current_only: z.boolean().default(true).describe("Only current members"),
194
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
195
+ }, READ_ONLY, async (params) => ({
196
+ content: [{ type: "text", text: await apiGet("/v1/government/cabinet-members", params) }],
197
+ }));
198
+ // ===================================================================
199
+ // ELECTIONS
200
+ // ===================================================================
201
+ server.tool("list_elections", "List elections across Africa — filter by country, scope, status, and year range", {
202
+ country_code: z.string().optional().describe("Filter by country"),
203
+ election_scope: z.enum(["presidential", "parliamentary", "general", "other"]).optional().describe("Election scope"),
204
+ status: z.enum(["upcoming", "completed", "unknown"]).optional().describe("Election status"),
205
+ start_year: z.number().int().optional().describe("From year"),
206
+ end_year: z.number().int().optional().describe("To year"),
207
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
208
+ }, READ_ONLY, async (params) => ({
209
+ content: [{ type: "text", text: await apiGet("/v1/elections", params) }],
210
+ }));
211
+ server.tool("get_upcoming_elections", "Get upcoming elections across Africa sorted by date", {
212
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
213
+ }, READ_ONLY, async ({ limit }) => ({
214
+ content: [{ type: "text", text: await apiGet("/v1/elections/upcoming", { limit }) }],
215
+ }));
216
+ server.tool("get_country_elections", "Get an election overview for a country — recent and upcoming elections with results", {
217
+ country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
218
+ top_limit: z.number().int().min(1).max(20).default(5).describe("Top results per category"),
219
+ }, READ_ONLY, async ({ country_code, top_limit }) => ({
220
+ content: [{ type: "text", text: await apiGet(`/v1/elections/country/${country_code}`, { top_limit }) }],
221
+ }));
222
+ server.tool("get_election", "Get detailed information about a specific election", {
223
+ election_wikidata_id: z.string().describe("Wikidata ID of the election"),
224
+ }, READ_ONLY, async ({ election_wikidata_id }) => ({
225
+ content: [{ type: "text", text: await apiGet(`/v1/elections/${election_wikidata_id}`) }],
226
+ }));
227
+ server.tool("get_election_results", "Get results for a specific election — candidates, parties, and vote counts", {
228
+ election_wikidata_id: z.string().describe("Wikidata ID of the election"),
229
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
230
+ }, READ_ONLY, async ({ election_wikidata_id, limit }) => ({
231
+ content: [{ type: "text", text: await apiGet(`/v1/elections/results/${election_wikidata_id}`, { limit }) }],
232
+ }));
233
+ // ===================================================================
234
+ // MARKETS
235
+ // ===================================================================
236
+ server.tool("list_exchanges", "List African stock exchanges — NGX, JSE, BRVM, Casablanca, etc.", {
237
+ country_code: z.string().optional().describe("Filter by country"),
238
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
239
+ }, READ_ONLY, async (params) => ({
240
+ content: [{ type: "text", text: await apiGet("/v1/markets/exchanges", params) }],
241
+ }));
242
+ server.tool("get_exchange", "Get details about a specific stock exchange", {
243
+ exchange_code: z.string().describe('Exchange code (e.g. "NGX", "JSE", "BRVM")'),
244
+ }, READ_ONLY, async ({ exchange_code }) => ({
245
+ content: [{ type: "text", text: await apiGet(`/v1/markets/exchanges/${exchange_code}`) }],
246
+ }));
247
+ server.tool("list_tickers", "Search listed securities (equities) across African exchanges", {
248
+ exchange_code: z.string().optional().describe('Filter by exchange (e.g. "NGX")'),
249
+ country_code: z.string().optional().describe("Filter by country"),
250
+ instrument_type: z.string().optional().describe("Filter by instrument type"),
251
+ q: z.string().optional().describe("Search by name or symbol"),
252
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
253
+ }, READ_ONLY, async (params) => ({
254
+ content: [{ type: "text", text: await apiGet("/v1/markets/tickers", params) }],
255
+ }));
256
+ server.tool("get_ticker", "Get details about a specific listed security", {
257
+ exchange_code: z.string().describe('Exchange code (e.g. "NGX")'),
258
+ symbol: z.string().describe('Ticker symbol (e.g. "DANGCEM")'),
259
+ }, READ_ONLY, async ({ exchange_code, symbol }) => ({
260
+ content: [{ type: "text", text: await apiGet(`/v1/markets/tickers/${exchange_code}/${symbol}`) }],
261
+ }));
262
+ server.tool("get_ticker_history", "Get price history (OHLC + volume) for a listed security", {
263
+ exchange_code: z.string().describe("Exchange code"),
264
+ symbol: z.string().describe("Ticker symbol"),
265
+ start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
266
+ end_date: z.string().optional().describe("End date (YYYY-MM-DD)"),
267
+ limit: z.number().int().min(1).max(5000).default(365).describe("Max data points"),
268
+ }, READ_ONLY, async ({ exchange_code, symbol, start_date, end_date, limit }) => ({
269
+ content: [
270
+ {
271
+ type: "text",
272
+ text: await apiGet(`/v1/markets/tickers/${exchange_code}/${symbol}/history`, { start_date, end_date, limit }),
273
+ },
274
+ ],
275
+ }));
276
+ server.tool("get_fx_rates", "Get current FX rates for African currencies against a base currency", {
277
+ base_currency: z.string().length(3).default("USD").describe("Base currency (3-letter code)"),
278
+ quote_currencies: z.string().optional().describe('Comma-separated quote currencies (e.g. "NGN,KES,ZAR")'),
279
+ country_code: z.string().optional().describe("Filter by country"),
280
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
281
+ }, READ_ONLY, async (params) => ({
282
+ content: [{ type: "text", text: await apiGet("/v1/markets/fx-rates", params) }],
283
+ }));
284
+ server.tool("get_fx_rate_history", "Get historical FX rate data for a currency pair", {
285
+ base_currency: z.string().length(3).describe('Base currency (e.g. "USD")'),
286
+ quote_currency: z.string().length(3).describe('Quote currency (e.g. "NGN", "KES", "ZAR")'),
287
+ start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
288
+ end_date: z.string().optional().describe("End date (YYYY-MM-DD)"),
289
+ limit: z.number().int().min(1).max(5000).default(365).describe("Max data points"),
290
+ }, READ_ONLY, async ({ base_currency, quote_currency, start_date, end_date, limit }) => ({
291
+ content: [
292
+ {
293
+ type: "text",
294
+ text: await apiGet(`/v1/markets/fx-rates/${base_currency}/${quote_currency}/history`, { start_date, end_date, limit }),
295
+ },
296
+ ],
297
+ }));
298
+ // ===================================================================
299
+ // TRADE
300
+ // ===================================================================
301
+ server.tool("get_trade_overview", "Get a trade overview — total exports/imports, top partners, top products, and trends", {
302
+ country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
303
+ start_year: z.number().int().optional().describe("Start year"),
304
+ end_year: z.number().int().optional().describe("End year"),
305
+ top_limit: z.number().int().min(1).max(20).default(5).describe("Top items per category"),
306
+ }, READ_ONLY, async ({ country_code, start_year, end_year, top_limit }) => ({
307
+ content: [
308
+ {
309
+ type: "text",
310
+ text: await apiGet(`/v1/trade/overview/${country_code}`, { start_year, end_year, top_limit }),
311
+ },
312
+ ],
313
+ }));
314
+ server.tool("get_trade_flows", "Query bilateral trade flows between countries — exports and imports with values", {
315
+ reporter_country_code: z.string().optional().describe("Reporting country (ISO alpha-2)"),
316
+ partner_country_code: z.string().optional().describe("Partner country code"),
317
+ product_code: z.string().optional().describe("HS product code"),
318
+ flow_type: z.enum(["export", "import"]).optional().describe("Flow type"),
319
+ year: z.number().int().optional().describe("Exact year"),
320
+ start_year: z.number().int().optional().describe("Start year"),
321
+ end_year: z.number().int().optional().describe("End year"),
322
+ limit: z.number().int().min(1).max(1000).default(100).describe("Max results"),
323
+ }, READ_ONLY, async (params) => ({
324
+ content: [{ type: "text", text: await apiGet("/v1/trade/flows", params) }],
325
+ }));
326
+ server.tool("get_trade_partners", "Get top trading partners for a country", {
327
+ reporter_country_code: z.string().length(2).describe("Reporting country (required)"),
328
+ flow_type: z.enum(["export", "import"]).optional().describe("Flow type"),
329
+ year: z.number().int().optional().describe("Exact year"),
330
+ start_year: z.number().int().optional().describe("Start year"),
331
+ end_year: z.number().int().optional().describe("End year"),
332
+ limit: z.number().int().min(1).max(1000).default(100).describe("Max results"),
333
+ }, READ_ONLY, async (params) => ({
334
+ content: [{ type: "text", text: await apiGet("/v1/trade/partners", params) }],
335
+ }));
336
+ server.tool("get_trade_products", "Get top traded products for a country — what it exports and imports", {
337
+ reporter_country_code: z.string().length(2).describe("Reporting country (required)"),
338
+ flow_type: z.enum(["export", "import"]).optional().describe("Flow type"),
339
+ year: z.number().int().optional().describe("Exact year"),
340
+ start_year: z.number().int().optional().describe("Start year"),
341
+ end_year: z.number().int().optional().describe("End year"),
342
+ limit: z.number().int().min(1).max(1000).default(100).describe("Max results"),
343
+ }, READ_ONLY, async (params) => ({
344
+ content: [{ type: "text", text: await apiGet("/v1/trade/products", params) }],
345
+ }));
346
+ // ===================================================================
347
+ // POLICIES
348
+ // ===================================================================
349
+ server.tool("list_policies", "Search government policies, laws, and regulations across Africa", {
350
+ country_code: z.string().optional().describe("Filter by country"),
351
+ document_type: z
352
+ .enum(["constitution", "law", "policy", "strategy", "regulation", "bill", "decree", "other"])
353
+ .optional()
354
+ .describe("Document type"),
355
+ status: z.enum(["active", "repealed", "draft", "unknown"]).optional().describe("Policy status"),
356
+ q: z.string().optional().describe("Search by title or content"),
357
+ start_year: z.number().int().optional().describe("From year"),
358
+ end_year: z.number().int().optional().describe("To year"),
359
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
360
+ }, READ_ONLY, async (params) => ({
361
+ content: [{ type: "text", text: await apiGet("/v1/policies", params) }],
362
+ }));
363
+ server.tool("get_policy", "Get detailed information about a specific policy, law, or regulation", {
364
+ policy_wikidata_id: z.string().describe("Wikidata ID of the policy"),
365
+ }, READ_ONLY, async ({ policy_wikidata_id }) => ({
366
+ content: [{ type: "text", text: await apiGet(`/v1/policies/${policy_wikidata_id}`) }],
367
+ }));
368
+ server.tool("get_country_policies", "Get a policy overview for a country — recent and notable policies grouped by type", {
369
+ country_code: z.string().length(2).describe('ISO alpha-2 code (e.g. "NG", "KE", "ZA")'),
370
+ top_limit: z.number().int().min(1).max(20).default(5).describe("Top items per category"),
371
+ }, READ_ONLY, async ({ country_code, top_limit }) => ({
372
+ content: [{ type: "text", text: await apiGet(`/v1/policies/country/${country_code}`, { top_limit }) }],
373
+ }));
374
+ server.tool("get_country_policy_timeline", "Get a chronological policy timeline for a country", {
375
+ country_code: z.string().length(2).describe("ISO alpha-2 code"),
376
+ top_limit: z.number().int().min(1).max(100).default(20).describe("Number of events"),
377
+ }, READ_ONLY, async ({ country_code, top_limit }) => ({
378
+ content: [
379
+ { type: "text", text: await apiGet(`/v1/policies/country/${country_code}/timeline`, { top_limit }) },
380
+ ],
381
+ }));
382
+ server.tool("list_policy_events", "List policy lifecycle events — when policies were announced, adopted, amended, repealed", {
383
+ country_code: z.string().optional().describe("Filter by country"),
384
+ event_type: z
385
+ .enum(["announced", "adopted", "implemented", "amended", "repealed", "suspended"])
386
+ .optional()
387
+ .describe("Event type"),
388
+ start_year: z.number().int().optional().describe("From year"),
389
+ end_year: z.number().int().optional().describe("To year"),
390
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
391
+ }, READ_ONLY, async (params) => ({
392
+ content: [{ type: "text", text: await apiGet("/v1/policies/events", params) }],
393
+ }));
394
+ server.tool("get_policy_events", "Get all lifecycle events for a specific policy", {
395
+ policy_wikidata_id: z.string().describe("Wikidata ID of the policy"),
396
+ limit: z.number().int().min(1).max(500).default(100).describe("Max results"),
397
+ }, READ_ONLY, async ({ policy_wikidata_id, limit }) => ({
398
+ content: [{ type: "text", text: await apiGet(`/v1/policies/${policy_wikidata_id}/events`, { limit }) }],
399
+ }));
400
+ // ===================================================================
401
+ // SOURCES
402
+ // ===================================================================
403
+ server.tool("list_sources", "List all data sources — World Bank, UN agencies, central banks, exchanges, etc.", {}, READ_ONLY, async () => ({
404
+ content: [{ type: "text", text: await apiGet("/v1/sources") }],
405
+ }));
406
+ server.tool("get_source", "Get details about a specific data source — description, URL, coverage, and update frequency", {
407
+ source_code: z.string().describe("Source code identifier"),
408
+ }, READ_ONLY, async ({ source_code }) => ({
409
+ content: [{ type: "text", text: await apiGet(`/v1/sources/${source_code}`) }],
410
+ }));
411
+ // ===================================================================
412
+ // PLATFORM
413
+ // ===================================================================
414
+ server.tool("get_platform_info", "Get Africa API platform version and status", {}, READ_ONLY, async () => ({
415
+ content: [{ type: "text", text: await apiGet("/v1/platform/version") }],
416
+ }));
417
+ // ---------------------------------------------------------------------------
418
+ // Start server
419
+ // ---------------------------------------------------------------------------
420
+ async function main() {
421
+ const transport = new StdioServerTransport();
422
+ await server.connect(transport);
423
+ }
424
+ main().catch((err) => {
425
+ console.error("Fatal error:", err);
426
+ process.exit(1);
427
+ });
428
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "africa-api-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Africa API — access data on all 54 African countries through Claude",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "africa-api-mcp": "dist/index.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsc --watch",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "model-context-protocol",
19
+ "africa",
20
+ "api",
21
+ "claude",
22
+ "ai",
23
+ "data"
24
+ ],
25
+ "author": "Africa API <team@startuplist.africa>",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/africa-api/africa-api-mcp.git"
30
+ },
31
+ "homepage": "https://africa-api.com",
32
+ "bugs": {
33
+ "url": "https://github.com/africa-api/africa-api-mcp/issues"
34
+ },
35
+ "files": [
36
+ "dist/**/*.js",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.12.0"
45
+ },
46
+ "devDependencies": {
47
+ "typescript": "^5.7.0",
48
+ "@types/node": "^22.0.0"
49
+ }
50
+ }