@unclick/ipapi-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.
- package/LICENSE +15 -0
- package/README.md +34 -0
- package/dist/index.js +66 -0
- package/dist/ipapi-tool.js +77 -0
- package/package.json +44 -0
- package/server.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Apache License 2.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UnClick / malamutemayhem
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# IP Geolocation MCP by UnClick
|
|
2
|
+
|
|
3
|
+
IP address geolocation and batch lookup with country, city, region, ISP, and timezone.
|
|
4
|
+
|
|
5
|
+
> By UnClick. 180+ tools plus persistent agent memory in one install: https://unclick.world
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Installs straight from GitHub, no npm account needed.
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"ipapi": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["-y", "https://github.com/malamutemayhem/unclick/releases/download/standalone-mcps-latest/ipapi.tgz"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Tools
|
|
23
|
+
|
|
24
|
+
- `ip_lookup`
|
|
25
|
+
- `ip_batch`
|
|
26
|
+
|
|
27
|
+
## Want the rest?
|
|
28
|
+
|
|
29
|
+
This is one connector. [UnClick](https://unclick.world) bundles 180+ tools plus
|
|
30
|
+
persistent cross-session agent memory in a single install.
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
Apache-2.0
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// IP Geolocation MCP. Standalone MCP server by UnClick.
|
|
3
|
+
// By UnClick. 180+ tools plus persistent agent memory in one install: https://unclick.world
|
|
4
|
+
//
|
|
5
|
+
// Generated from the UnClick connector by scripts/generate-standalone-mcp.mjs.
|
|
6
|
+
// Edit the connector in the UnClick monorepo, not here.
|
|
7
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { ipLookup, ipBatch, } from "./ipapi-tool.js";
|
|
11
|
+
const TOOLS = [
|
|
12
|
+
{
|
|
13
|
+
name: "ip_lookup",
|
|
14
|
+
description: "Look up geolocation for an IP address.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
properties: {
|
|
19
|
+
ip: { type: "string" },
|
|
20
|
+
api_key: { type: "string" },
|
|
21
|
+
},
|
|
22
|
+
required: ["ip"],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "ip_batch",
|
|
27
|
+
description: "Batch IP address geolocation lookup.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object",
|
|
30
|
+
additionalProperties: false,
|
|
31
|
+
properties: {
|
|
32
|
+
ips: { type: "array", items: {}, description: "Array of IP address strings" },
|
|
33
|
+
api_key: { type: "string" },
|
|
34
|
+
},
|
|
35
|
+
required: ["ips"],
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
const HANDLERS = {
|
|
40
|
+
ip_lookup: (args) => ipLookup(args),
|
|
41
|
+
ip_batch: (args) => ipBatch(args),
|
|
42
|
+
};
|
|
43
|
+
const server = new Server({ name: "io.github.malamutemayhem/ipapi", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
44
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
45
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
46
|
+
const handler = HANDLERS[req.params.name];
|
|
47
|
+
if (!handler) {
|
|
48
|
+
return { content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }], isError: true };
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const result = await handler((req.params.arguments ?? {}));
|
|
52
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
56
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
async function main() {
|
|
60
|
+
const transport = new StdioServerTransport();
|
|
61
|
+
await server.connect(transport);
|
|
62
|
+
}
|
|
63
|
+
main().catch((err) => {
|
|
64
|
+
process.stderr.write(`[ipapi-mcp] fatal: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// IP geolocation integration for the UnClick MCP server.
|
|
2
|
+
// Uses the ip-api.com free tier via fetch - no auth required (100 req/min).
|
|
3
|
+
// Returns country, region, city, lat/lon, ISP, timezone, and more.
|
|
4
|
+
const IPAPI_BASE = "http://ip-api.com";
|
|
5
|
+
// --- API helper ---
|
|
6
|
+
async function ipapiGet(ip) {
|
|
7
|
+
const path = ip ? `/${encodeURIComponent(ip)}` : "/";
|
|
8
|
+
const url = `${IPAPI_BASE}/json${path}?fields=66846719`;
|
|
9
|
+
const response = await fetch(url, { headers: { "Accept": "application/json" } });
|
|
10
|
+
if (!response.ok) {
|
|
11
|
+
throw new Error(`HTTP ${response.status} from ip-api.com`);
|
|
12
|
+
}
|
|
13
|
+
const data = await response.json();
|
|
14
|
+
if (data.status === "fail") {
|
|
15
|
+
throw new Error(`ip-api error: ${data.message ?? "Query failed"}`);
|
|
16
|
+
}
|
|
17
|
+
return data;
|
|
18
|
+
}
|
|
19
|
+
function normalizeIpData(data) {
|
|
20
|
+
return {
|
|
21
|
+
ip: data.query,
|
|
22
|
+
status: data.status,
|
|
23
|
+
country: data.country,
|
|
24
|
+
country_code: data.countryCode,
|
|
25
|
+
region: data.regionName,
|
|
26
|
+
region_code: data.region,
|
|
27
|
+
city: data.city,
|
|
28
|
+
zip: data.zip,
|
|
29
|
+
lat: data.lat,
|
|
30
|
+
lon: data.lon,
|
|
31
|
+
timezone: data.timezone,
|
|
32
|
+
isp: data.isp,
|
|
33
|
+
org: data.org,
|
|
34
|
+
as: data.as,
|
|
35
|
+
mobile: data.mobile,
|
|
36
|
+
proxy: data.proxy,
|
|
37
|
+
hosting: data.hosting,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// --- Operations ---
|
|
41
|
+
export async function ipLookup(args) {
|
|
42
|
+
const ip = String(args.ip ?? "").trim();
|
|
43
|
+
// Empty ip will look up the caller's IP
|
|
44
|
+
const data = await ipapiGet(ip);
|
|
45
|
+
return normalizeIpData(data);
|
|
46
|
+
}
|
|
47
|
+
export async function ipBatch(args) {
|
|
48
|
+
const addresses = args.addresses;
|
|
49
|
+
if (!Array.isArray(addresses) || addresses.length === 0) {
|
|
50
|
+
throw new Error("addresses is required and must be a non-empty array of IP strings.");
|
|
51
|
+
}
|
|
52
|
+
if (addresses.length > 100) {
|
|
53
|
+
throw new Error("addresses array must contain 100 or fewer IPs per request.");
|
|
54
|
+
}
|
|
55
|
+
const body = addresses.map((a) => ({
|
|
56
|
+
query: String(a).trim(),
|
|
57
|
+
fields: "66846719",
|
|
58
|
+
}));
|
|
59
|
+
const response = await fetch(`${IPAPI_BASE}/batch`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "Content-Type": "application/json", "Accept": "application/json" },
|
|
62
|
+
body: JSON.stringify(body),
|
|
63
|
+
});
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new Error(`HTTP ${response.status} from ip-api.com batch endpoint`);
|
|
66
|
+
}
|
|
67
|
+
const results = await response.json();
|
|
68
|
+
return {
|
|
69
|
+
count: results.length,
|
|
70
|
+
results: results.map((r) => {
|
|
71
|
+
if (r.status === "fail") {
|
|
72
|
+
return { ip: r.query, error: r.message ?? "Query failed" };
|
|
73
|
+
}
|
|
74
|
+
return normalizeIpData(r);
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unclick/ipapi-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"mcpName": "io.github.malamutemayhem/ipapi",
|
|
5
|
+
"description": "IP address geolocation and batch lookup with country, city, region, ISP, and timezone. By UnClick (https://unclick.world).",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"model-context-protocol",
|
|
9
|
+
"unclick",
|
|
10
|
+
"ip",
|
|
11
|
+
"geolocation",
|
|
12
|
+
"network"
|
|
13
|
+
],
|
|
14
|
+
"author": "UnClick (https://unclick.world)",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"bin": {
|
|
17
|
+
"ipapi-mcp": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"server.json"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"start": "node dist/index.js",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.15.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^5.6.0",
|
|
35
|
+
"@types/node": "^22.0.0"
|
|
36
|
+
},
|
|
37
|
+
"license": "Apache-2.0",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/malamutemayhem/unclick.git",
|
|
41
|
+
"directory": "packages/standalone/ipapi-mcp"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://unclick.world"
|
|
44
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.malamutemayhem/ipapi",
|
|
4
|
+
"title": "IP Geolocation MCP by UnClick",
|
|
5
|
+
"description": "IP address geolocation and batch lookup with country, city, region, ISP, and timezone. By UnClick.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://unclick.world",
|
|
8
|
+
"icons": [
|
|
9
|
+
{
|
|
10
|
+
"src": "https://unclick.world/favicon.png",
|
|
11
|
+
"mimeType": "image/png",
|
|
12
|
+
"sizes": [
|
|
13
|
+
"512x512"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"url": "https://github.com/malamutemayhem/unclick.git",
|
|
19
|
+
"source": "github",
|
|
20
|
+
"subfolder": "packages/standalone/ipapi-mcp"
|
|
21
|
+
},
|
|
22
|
+
"packages": [
|
|
23
|
+
{
|
|
24
|
+
"registryType": "npm",
|
|
25
|
+
"identifier": "@unclick/ipapi-mcp",
|
|
26
|
+
"version": "0.1.0",
|
|
27
|
+
"runtimeHint": "npx",
|
|
28
|
+
"transport": {
|
|
29
|
+
"type": "stdio"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|