@apogeoapi/mcp 1.0.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.
- package/README.md +106 -0
- package/dist/index.js +21 -0
- package/dist/lib/api-client.js +17 -0
- package/dist/tools/cities.js +21 -0
- package/dist/tools/countries.js +41 -0
- package/dist/tools/currency.js +10 -0
- package/dist/tools/ip.js +10 -0
- package/dist/tools/search.js +14 -0
- package/dist/tools/states.js +21 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @apogeoapi/mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [ApogeoAPI](https://app.apogeoapi.com) — geographic data, live exchange rates, and IP geolocation for Claude Desktop, Cursor, and any MCP-compatible AI assistant.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
This server exposes ApogeoAPI's REST endpoints as tools that AI assistants can call directly. Ask Claude "What is the current USD rate for Argentina?" or "Geolocate IP 8.8.8.8" and it will call the right tool automatically.
|
|
8
|
+
|
|
9
|
+
## Available tools
|
|
10
|
+
|
|
11
|
+
| Tool | Description | Plan required |
|
|
12
|
+
|------|-------------|---------------|
|
|
13
|
+
| `get_country` | Full country data by ISO2/ISO3 code — name, capital, region, population, currency, live USD rate, timezones, phone code | Free |
|
|
14
|
+
| `list_countries` | Paginated list of all 250+ countries | Free |
|
|
15
|
+
| `search_countries` | Search countries by partial name | Free |
|
|
16
|
+
| `get_states` | All states/provinces for a country | Basic+ |
|
|
17
|
+
| `get_cities` | All cities for a state by numeric state ID | Basic+ |
|
|
18
|
+
| `get_currency_rate` | Live USD exchange rate for a country's currency (updated every 4 hours) | Basic+ |
|
|
19
|
+
| `geolocate_ip` | Country, region, city, coordinates, timezone, and EU membership for any IPv4/IPv6 | Basic+ |
|
|
20
|
+
| `global_search` | Search across countries, states, and cities in one query | Free |
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### Claude Desktop
|
|
25
|
+
|
|
26
|
+
Add the following to your `claude_desktop_config.json`:
|
|
27
|
+
|
|
28
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
29
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"apogeoapi": {
|
|
35
|
+
"command": "npx",
|
|
36
|
+
"args": ["-y", "@apogeoapi/mcp"],
|
|
37
|
+
"env": {
|
|
38
|
+
"APOGEOAPI_KEY": "apogeoapi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Restart Claude Desktop after saving the file. The ApogeoAPI tools will appear in Claude's tool list.
|
|
46
|
+
|
|
47
|
+
### Cursor
|
|
48
|
+
|
|
49
|
+
Add the same block under `mcpServers` in Cursor's MCP configuration file (`~/.cursor/mcp.json`).
|
|
50
|
+
|
|
51
|
+
### Manual build
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/your-org/apogeoapi-mcp
|
|
55
|
+
cd apogeoapi-mcp
|
|
56
|
+
npm install
|
|
57
|
+
npm run build
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Then reference the built file directly:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"apogeoapi": {
|
|
66
|
+
"command": "node",
|
|
67
|
+
"args": ["/absolute/path/to/mcp-server/dist/index.js"],
|
|
68
|
+
"env": {
|
|
69
|
+
"APOGEOAPI_KEY": "apogeoapi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Getting an API key
|
|
77
|
+
|
|
78
|
+
1. Create a free account at [app.apogeoapi.com](https://app.apogeoapi.com)
|
|
79
|
+
2. Go to **API Keys** and generate a new key
|
|
80
|
+
3. Paste the key into the `APOGEOAPI_KEY` environment variable above
|
|
81
|
+
|
|
82
|
+
No credit card required for the Free plan (1,000 req/month).
|
|
83
|
+
|
|
84
|
+
## Plan requirements
|
|
85
|
+
|
|
86
|
+
| Feature | Free | Basic ($19/mo) | Starter ($29/mo) | Professional ($79/mo) |
|
|
87
|
+
|---------|------|----------------|-------------------|-----------------------|
|
|
88
|
+
| Countries (list, search, get) | Yes | Yes | Yes | Yes |
|
|
89
|
+
| Global search | Yes | Yes | Yes | Yes |
|
|
90
|
+
| States & cities | No | Yes | Yes | Yes |
|
|
91
|
+
| Live currency rates | No | Yes | Yes | Yes |
|
|
92
|
+
| IP geolocation | No | Yes | Yes | Yes |
|
|
93
|
+
| Monthly requests | 1,000 | 15,000 | 100,000 | 500,000 |
|
|
94
|
+
|
|
95
|
+
Upgrade at [app.apogeoapi.com/dashboard/billing](https://app.apogeoapi.com/dashboard/billing).
|
|
96
|
+
|
|
97
|
+
## Environment variables
|
|
98
|
+
|
|
99
|
+
| Variable | Required | Description |
|
|
100
|
+
|----------|----------|-------------|
|
|
101
|
+
| `APOGEOAPI_KEY` | Yes | Your API key from the dashboard |
|
|
102
|
+
| `APOGEOAPI_BASE_URL` | No | Override the API base URL (default: `https://api.apogeoapi.com`) |
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { registerCountryTools } from './tools/countries.js';
|
|
5
|
+
import { registerStateTools } from './tools/states.js';
|
|
6
|
+
import { registerCityTools } from './tools/cities.js';
|
|
7
|
+
import { registerCurrencyTools } from './tools/currency.js';
|
|
8
|
+
import { registerIpTools } from './tools/ip.js';
|
|
9
|
+
import { registerSearchTools } from './tools/search.js';
|
|
10
|
+
const server = new McpServer({
|
|
11
|
+
name: 'apogeoapi',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
});
|
|
14
|
+
registerCountryTools(server);
|
|
15
|
+
registerStateTools(server);
|
|
16
|
+
registerCityTools(server);
|
|
17
|
+
registerCurrencyTools(server);
|
|
18
|
+
registerIpTools(server);
|
|
19
|
+
registerSearchTools(server);
|
|
20
|
+
const transport = new StdioServerTransport();
|
|
21
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const BASE = process.env.APOGEOAPI_BASE_URL ?? 'https://api.apogeoapi.com';
|
|
2
|
+
function getKey() {
|
|
3
|
+
const key = process.env.APOGEOAPI_KEY;
|
|
4
|
+
if (!key)
|
|
5
|
+
throw new Error('APOGEOAPI_KEY environment variable is required');
|
|
6
|
+
return key;
|
|
7
|
+
}
|
|
8
|
+
export async function apiGet(path) {
|
|
9
|
+
const res = await fetch(`${BASE}${path}`, {
|
|
10
|
+
headers: { 'X-API-Key': getKey() },
|
|
11
|
+
});
|
|
12
|
+
if (!res.ok) {
|
|
13
|
+
const body = await res.text().catch(() => '');
|
|
14
|
+
throw new Error(`ApogeoAPI ${res.status}: ${body || res.statusText}`);
|
|
15
|
+
}
|
|
16
|
+
return res.json();
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { apiGet } from '../lib/api-client.js';
|
|
3
|
+
export function registerCityTools(server) {
|
|
4
|
+
server.tool('get_cities', 'Get all cities for a state/province by state ID. Requires Starter plan or above.', {
|
|
5
|
+
state_id: z.number().int().positive().describe('Numeric state ID (from get_states response)'),
|
|
6
|
+
page: z.number().int().positive().optional(),
|
|
7
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
8
|
+
fields: z.enum(['basic', 'standard', 'full']).optional(),
|
|
9
|
+
}, async ({ state_id, page, limit, fields }) => {
|
|
10
|
+
const params = new URLSearchParams();
|
|
11
|
+
if (page)
|
|
12
|
+
params.set('page', String(page));
|
|
13
|
+
if (limit)
|
|
14
|
+
params.set('limit', String(limit));
|
|
15
|
+
if (fields)
|
|
16
|
+
params.set('fields', fields);
|
|
17
|
+
const qs = params.toString() ? `?${params}` : '';
|
|
18
|
+
const data = await apiGet(`/v1/api/geo/states/${state_id}/cities${qs}`);
|
|
19
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { apiGet } from '../lib/api-client.js';
|
|
3
|
+
export function registerCountryTools(server) {
|
|
4
|
+
server.tool('get_country', 'Get full data for a country by ISO2 or ISO3 code — includes name, capital, region, population, currency, live USD exchange rate, timezones, and phone code.', {
|
|
5
|
+
code: z.string().min(2).max(3).describe('ISO2 (e.g. AR) or ISO3 (e.g. ARG) country code'),
|
|
6
|
+
fields: z.enum(['basic', 'standard', 'full']).optional().describe('Detail level: basic (fast), standard (default), full (all fields)'),
|
|
7
|
+
}, async ({ code, fields }) => {
|
|
8
|
+
const qs = fields ? `?fields=${fields}` : '';
|
|
9
|
+
const data = await apiGet(`/v1/api/geo/countries/${code.toUpperCase()}${qs}`);
|
|
10
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
11
|
+
});
|
|
12
|
+
server.tool('list_countries', 'List all countries with pagination. Returns basic info by default — add fields=full for all data.', {
|
|
13
|
+
page: z.number().int().positive().optional().describe('Page number (default: 1)'),
|
|
14
|
+
limit: z.number().int().min(1).max(100).optional().describe('Results per page (default: 50, max: 100)'),
|
|
15
|
+
fields: z.enum(['basic', 'standard', 'full']).optional().describe('Detail level'),
|
|
16
|
+
}, async ({ page, limit, fields }) => {
|
|
17
|
+
const params = new URLSearchParams();
|
|
18
|
+
if (page)
|
|
19
|
+
params.set('page', String(page));
|
|
20
|
+
if (limit)
|
|
21
|
+
params.set('limit', String(limit));
|
|
22
|
+
if (fields)
|
|
23
|
+
params.set('fields', fields);
|
|
24
|
+
const qs = params.toString() ? `?${params}` : '';
|
|
25
|
+
const data = await apiGet(`/v1/api/geo/countries${qs}`);
|
|
26
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
27
|
+
});
|
|
28
|
+
server.tool('search_countries', 'Search countries by name (e.g. "arg" finds Argentina). Returns paginated matches.', {
|
|
29
|
+
q: z.string().min(1).describe('Search query (partial name match)'),
|
|
30
|
+
page: z.number().int().positive().optional(),
|
|
31
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
32
|
+
}, async ({ q, page, limit }) => {
|
|
33
|
+
const params = new URLSearchParams({ q });
|
|
34
|
+
if (page)
|
|
35
|
+
params.set('page', String(page));
|
|
36
|
+
if (limit)
|
|
37
|
+
params.set('limit', String(limit));
|
|
38
|
+
const data = await apiGet(`/v1/api/geo/countries/search?${params}`);
|
|
39
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { apiGet } from '../lib/api-client.js';
|
|
3
|
+
export function registerCurrencyTools(server) {
|
|
4
|
+
server.tool('get_currency_rate', 'Get the live USD exchange rate for a country\'s currency. Updated every 4 hours. Requires Starter plan or above.', {
|
|
5
|
+
country_code: z.string().min(2).max(3).describe('ISO2 or ISO3 country code (e.g. AR, BR, JP)'),
|
|
6
|
+
}, async ({ country_code }) => {
|
|
7
|
+
const data = await apiGet(`/v1/api/geo/countries/${country_code.toUpperCase()}/currency-rate`);
|
|
8
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
9
|
+
});
|
|
10
|
+
}
|
package/dist/tools/ip.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { apiGet } from '../lib/api-client.js';
|
|
3
|
+
export function registerIpTools(server) {
|
|
4
|
+
server.tool('geolocate_ip', 'Geolocate an IPv4 or IPv6 address — returns country, region, city, coordinates, timezone, and EU membership. Requires Starter plan or above.', {
|
|
5
|
+
address: z.string().describe('IPv4 or IPv6 address to geolocate (e.g. 8.8.8.8 or 2001:4860:4860::8888)'),
|
|
6
|
+
}, async ({ address }) => {
|
|
7
|
+
const data = await apiGet(`/v1/api/geo/ip/${address}`);
|
|
8
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { apiGet } from '../lib/api-client.js';
|
|
3
|
+
export function registerSearchTools(server) {
|
|
4
|
+
server.tool('global_search', 'Search across countries, states, and cities in a single query. Returns the best matches from all geographic entity types.', {
|
|
5
|
+
q: z.string().min(1).describe('Search term (e.g. "Buenos Aires", "Pampas", "Cordoba")'),
|
|
6
|
+
limit: z.number().int().min(1).max(20).optional().describe('Max results (default: 10)'),
|
|
7
|
+
}, async ({ q, limit }) => {
|
|
8
|
+
const params = new URLSearchParams({ q });
|
|
9
|
+
if (limit)
|
|
10
|
+
params.set('limit', String(limit));
|
|
11
|
+
const data = await apiGet(`/v1/api/geo/search?${params}`);
|
|
12
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { apiGet } from '../lib/api-client.js';
|
|
3
|
+
export function registerStateTools(server) {
|
|
4
|
+
server.tool('get_states', 'Get all states/provinces for a country. Requires Starter plan or above.', {
|
|
5
|
+
country_code: z.string().min(2).max(3).describe('ISO2 or ISO3 country code'),
|
|
6
|
+
page: z.number().int().positive().optional(),
|
|
7
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
8
|
+
fields: z.enum(['basic', 'standard', 'full']).optional(),
|
|
9
|
+
}, async ({ country_code, page, limit, fields }) => {
|
|
10
|
+
const params = new URLSearchParams();
|
|
11
|
+
if (page)
|
|
12
|
+
params.set('page', String(page));
|
|
13
|
+
if (limit)
|
|
14
|
+
params.set('limit', String(limit));
|
|
15
|
+
if (fields)
|
|
16
|
+
params.set('fields', fields);
|
|
17
|
+
const qs = params.toString() ? `?${params}` : '';
|
|
18
|
+
const data = await apiGet(`/v1/api/geo/countries/${country_code.toUpperCase()}/states${qs}`);
|
|
19
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
20
|
+
});
|
|
21
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@apogeoapi/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for ApogeoAPI — geographic data, live exchange rates & IP geolocation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"apogeoapi-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsx src/index.ts",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
24
|
+
"zod": "^3.22.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "^5.3.0",
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"tsx": "^4.0.0"
|
|
30
|
+
},
|
|
31
|
+
"files": ["dist"],
|
|
32
|
+
"keywords": ["mcp", "geolocation", "geography", "currency", "ip-geo", "apogeoapi"],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/Ezleal/apo-geo-api.git",
|
|
37
|
+
"directory": "mcp-server"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://apogeoapi.com"
|
|
40
|
+
}
|