@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 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
+ }
@@ -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
+ }