ipgeolocation-io-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/LICENSE +21 -0
- package/README.md +606 -0
- package/dist/client.d.ts +97 -0
- package/dist/client.js +173 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +31 -0
- package/dist/tools/abuse.d.ts +2 -0
- package/dist/tools/abuse.js +49 -0
- package/dist/tools/asn.d.ts +2 -0
- package/dist/tools/asn.js +59 -0
- package/dist/tools/astronomy.d.ts +2 -0
- package/dist/tools/astronomy.js +140 -0
- package/dist/tools/geolocation.d.ts +2 -0
- package/dist/tools/geolocation.js +261 -0
- package/dist/tools/security.d.ts +2 -0
- package/dist/tools/security.js +95 -0
- package/dist/tools/timezone.d.ts +2 -0
- package/dist/tools/timezone.js +168 -0
- package/dist/tools/useragent.d.ts +2 -0
- package/dist/tools/useragent.js +78 -0
- package/package.json +64 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export declare function getApiKey(): string;
|
|
2
|
+
export declare class ApiError extends Error {
|
|
3
|
+
status: number;
|
|
4
|
+
constructor(status: number, message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function getIpGeolocation(params: {
|
|
7
|
+
ip?: string;
|
|
8
|
+
lang?: string;
|
|
9
|
+
include?: string;
|
|
10
|
+
fields?: string;
|
|
11
|
+
excludes?: string;
|
|
12
|
+
}): Promise<unknown>;
|
|
13
|
+
export declare function getMyIp(): Promise<string>;
|
|
14
|
+
export declare function getSecurity(params: {
|
|
15
|
+
ip?: string;
|
|
16
|
+
fields?: string;
|
|
17
|
+
excludes?: string;
|
|
18
|
+
}): Promise<unknown>;
|
|
19
|
+
export declare function getSecurityBulk(params: {
|
|
20
|
+
ips: string[];
|
|
21
|
+
fields?: string;
|
|
22
|
+
excludes?: string;
|
|
23
|
+
}): Promise<unknown>;
|
|
24
|
+
export declare function getTimezone(params: {
|
|
25
|
+
tz?: string;
|
|
26
|
+
lat?: string;
|
|
27
|
+
long?: string;
|
|
28
|
+
location?: string;
|
|
29
|
+
ip?: string;
|
|
30
|
+
iata_code?: string;
|
|
31
|
+
icao_code?: string;
|
|
32
|
+
lo_code?: string;
|
|
33
|
+
lang?: string;
|
|
34
|
+
}): Promise<unknown>;
|
|
35
|
+
export declare function convertTimezone(params: {
|
|
36
|
+
time?: string;
|
|
37
|
+
tz_from?: string;
|
|
38
|
+
tz_to?: string;
|
|
39
|
+
lat_from?: string;
|
|
40
|
+
long_from?: string;
|
|
41
|
+
lat_to?: string;
|
|
42
|
+
long_to?: string;
|
|
43
|
+
location_from?: string;
|
|
44
|
+
location_to?: string;
|
|
45
|
+
iata_from?: string;
|
|
46
|
+
iata_to?: string;
|
|
47
|
+
icao_from?: string;
|
|
48
|
+
icao_to?: string;
|
|
49
|
+
locode_from?: string;
|
|
50
|
+
locode_to?: string;
|
|
51
|
+
}): Promise<unknown>;
|
|
52
|
+
export declare function getAstronomy(params: {
|
|
53
|
+
lat?: string;
|
|
54
|
+
long?: string;
|
|
55
|
+
location?: string;
|
|
56
|
+
ip?: string;
|
|
57
|
+
date?: string;
|
|
58
|
+
elevation?: string;
|
|
59
|
+
time_zone?: string;
|
|
60
|
+
lang?: string;
|
|
61
|
+
}): Promise<unknown>;
|
|
62
|
+
export declare function getAsn(params: {
|
|
63
|
+
asn?: string;
|
|
64
|
+
ip?: string;
|
|
65
|
+
include?: string;
|
|
66
|
+
fields?: string;
|
|
67
|
+
excludes?: string;
|
|
68
|
+
}): Promise<unknown>;
|
|
69
|
+
export declare function getIpGeolocationBulk(params: {
|
|
70
|
+
ips: string[];
|
|
71
|
+
lang?: string;
|
|
72
|
+
include?: string;
|
|
73
|
+
fields?: string;
|
|
74
|
+
excludes?: string;
|
|
75
|
+
}): Promise<unknown>;
|
|
76
|
+
export declare function getAbuseContact(params: {
|
|
77
|
+
ip?: string;
|
|
78
|
+
fields?: string;
|
|
79
|
+
excludes?: string;
|
|
80
|
+
}): Promise<unknown>;
|
|
81
|
+
export declare function parseUserAgent(params: {
|
|
82
|
+
uaString: string;
|
|
83
|
+
}): Promise<unknown>;
|
|
84
|
+
export declare function parseUserAgentBulk(params: {
|
|
85
|
+
uaStrings: string[];
|
|
86
|
+
}): Promise<unknown>;
|
|
87
|
+
export declare function getAstronomyTimeSeries(params: {
|
|
88
|
+
lat?: string;
|
|
89
|
+
long?: string;
|
|
90
|
+
location?: string;
|
|
91
|
+
ip?: string;
|
|
92
|
+
dateStart: string;
|
|
93
|
+
dateEnd: string;
|
|
94
|
+
elevation?: string;
|
|
95
|
+
time_zone?: string;
|
|
96
|
+
lang?: string;
|
|
97
|
+
}): Promise<unknown>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const API_BASE = "https://api.ipgeolocation.io";
|
|
2
|
+
let apiKey;
|
|
3
|
+
export function getApiKey() {
|
|
4
|
+
if (!apiKey) {
|
|
5
|
+
apiKey = process.env.IPGEOLOCATION_API_KEY;
|
|
6
|
+
}
|
|
7
|
+
if (!apiKey) {
|
|
8
|
+
throw new Error("API key not configured. Set the IPGEOLOCATION_API_KEY environment variable.");
|
|
9
|
+
}
|
|
10
|
+
return apiKey;
|
|
11
|
+
}
|
|
12
|
+
export class ApiError extends Error {
|
|
13
|
+
status;
|
|
14
|
+
constructor(status, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.name = "ApiError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function request(path, params = {}, options = {}) {
|
|
21
|
+
const url = new URL(path, API_BASE);
|
|
22
|
+
if (!options.skipAuth) {
|
|
23
|
+
url.searchParams.set("apiKey", getApiKey());
|
|
24
|
+
}
|
|
25
|
+
for (const [key, value] of Object.entries(params)) {
|
|
26
|
+
if (value !== undefined && value !== "") {
|
|
27
|
+
url.searchParams.set(key, value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const fetchOptions = {
|
|
31
|
+
method: options.method || "GET",
|
|
32
|
+
headers: {
|
|
33
|
+
Accept: "application/json",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
if (options.body) {
|
|
37
|
+
fetchOptions.headers = {
|
|
38
|
+
...fetchOptions.headers,
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
};
|
|
41
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
42
|
+
}
|
|
43
|
+
const response = await fetch(url.toString(), fetchOptions);
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
let message;
|
|
46
|
+
try {
|
|
47
|
+
const text = await response.text();
|
|
48
|
+
message = text || response.statusText;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
message = response.statusText;
|
|
52
|
+
}
|
|
53
|
+
throw new ApiError(response.status, `${response.status}: ${message}`);
|
|
54
|
+
}
|
|
55
|
+
return response.json();
|
|
56
|
+
}
|
|
57
|
+
export async function getIpGeolocation(params) {
|
|
58
|
+
return request("/v3/ipgeo", {
|
|
59
|
+
ip: params.ip,
|
|
60
|
+
lang: params.lang,
|
|
61
|
+
include: params.include,
|
|
62
|
+
fields: params.fields,
|
|
63
|
+
excludes: params.excludes,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export async function getMyIp() {
|
|
67
|
+
const response = await fetch(`${API_BASE}/v3/getip`);
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new ApiError(response.status, "Failed to retrieve IP address");
|
|
70
|
+
}
|
|
71
|
+
const data = (await response.json());
|
|
72
|
+
return data.ip;
|
|
73
|
+
}
|
|
74
|
+
export async function getSecurity(params) {
|
|
75
|
+
return request("/v3/security", {
|
|
76
|
+
ip: params.ip,
|
|
77
|
+
fields: params.fields,
|
|
78
|
+
excludes: params.excludes,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function getSecurityBulk(params) {
|
|
82
|
+
return request("/v3/security-bulk", {
|
|
83
|
+
fields: params.fields,
|
|
84
|
+
excludes: params.excludes,
|
|
85
|
+
}, { method: "POST", body: { ips: params.ips } });
|
|
86
|
+
}
|
|
87
|
+
export async function getTimezone(params) {
|
|
88
|
+
return request("/v3/timezone", {
|
|
89
|
+
tz: params.tz,
|
|
90
|
+
lat: params.lat,
|
|
91
|
+
long: params.long,
|
|
92
|
+
location: params.location,
|
|
93
|
+
ip: params.ip,
|
|
94
|
+
iata_code: params.iata_code,
|
|
95
|
+
icao_code: params.icao_code,
|
|
96
|
+
lo_code: params.lo_code,
|
|
97
|
+
lang: params.lang,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export async function convertTimezone(params) {
|
|
101
|
+
return request("/v3/timezone/convert", {
|
|
102
|
+
time: params.time,
|
|
103
|
+
tz_from: params.tz_from,
|
|
104
|
+
tz_to: params.tz_to,
|
|
105
|
+
lat_from: params.lat_from,
|
|
106
|
+
long_from: params.long_from,
|
|
107
|
+
lat_to: params.lat_to,
|
|
108
|
+
long_to: params.long_to,
|
|
109
|
+
location_from: params.location_from,
|
|
110
|
+
location_to: params.location_to,
|
|
111
|
+
iata_from: params.iata_from,
|
|
112
|
+
iata_to: params.iata_to,
|
|
113
|
+
icao_from: params.icao_from,
|
|
114
|
+
icao_to: params.icao_to,
|
|
115
|
+
locode_from: params.locode_from,
|
|
116
|
+
locode_to: params.locode_to,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export async function getAstronomy(params) {
|
|
120
|
+
return request("/v3/astronomy", {
|
|
121
|
+
lat: params.lat,
|
|
122
|
+
long: params.long,
|
|
123
|
+
location: params.location,
|
|
124
|
+
ip: params.ip,
|
|
125
|
+
date: params.date,
|
|
126
|
+
elevation: params.elevation,
|
|
127
|
+
time_zone: params.time_zone,
|
|
128
|
+
lang: params.lang,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
export async function getAsn(params) {
|
|
132
|
+
return request("/v3/asn", {
|
|
133
|
+
asn: params.asn,
|
|
134
|
+
ip: params.ip,
|
|
135
|
+
include: params.include,
|
|
136
|
+
fields: params.fields,
|
|
137
|
+
excludes: params.excludes,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
export async function getIpGeolocationBulk(params) {
|
|
141
|
+
return request("/v3/ipgeo-bulk", {
|
|
142
|
+
lang: params.lang,
|
|
143
|
+
include: params.include,
|
|
144
|
+
fields: params.fields,
|
|
145
|
+
excludes: params.excludes,
|
|
146
|
+
}, { method: "POST", body: { ips: params.ips } });
|
|
147
|
+
}
|
|
148
|
+
export async function getAbuseContact(params) {
|
|
149
|
+
return request("/v3/abuse", {
|
|
150
|
+
ip: params.ip,
|
|
151
|
+
fields: params.fields,
|
|
152
|
+
excludes: params.excludes,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
export async function parseUserAgent(params) {
|
|
156
|
+
return request("/v3/user-agent", {}, { method: "POST", body: { uaString: params.uaString } });
|
|
157
|
+
}
|
|
158
|
+
export async function parseUserAgentBulk(params) {
|
|
159
|
+
return request("/v3/user-agent-bulk", {}, { method: "POST", body: { uaStrings: params.uaStrings } });
|
|
160
|
+
}
|
|
161
|
+
export async function getAstronomyTimeSeries(params) {
|
|
162
|
+
return request("/v3/astronomy/timeSeries", {
|
|
163
|
+
lat: params.lat,
|
|
164
|
+
long: params.long,
|
|
165
|
+
location: params.location,
|
|
166
|
+
ip: params.ip,
|
|
167
|
+
dateStart: params.dateStart,
|
|
168
|
+
dateEnd: params.dateEnd,
|
|
169
|
+
elevation: params.elevation,
|
|
170
|
+
time_zone: params.time_zone,
|
|
171
|
+
lang: params.lang,
|
|
172
|
+
});
|
|
173
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
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 { registerGeolocationTools } from "./tools/geolocation.js";
|
|
5
|
+
import { registerSecurityTools } from "./tools/security.js";
|
|
6
|
+
import { registerTimezoneTools } from "./tools/timezone.js";
|
|
7
|
+
import { registerAstronomyTools } from "./tools/astronomy.js";
|
|
8
|
+
import { registerAsnTools } from "./tools/asn.js";
|
|
9
|
+
import { registerAbuseTools } from "./tools/abuse.js";
|
|
10
|
+
import { registerUserAgentTools } from "./tools/useragent.js";
|
|
11
|
+
const server = new McpServer({
|
|
12
|
+
name: "ipgeolocation-io-mcp",
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
});
|
|
15
|
+
registerGeolocationTools(server);
|
|
16
|
+
registerSecurityTools(server);
|
|
17
|
+
registerTimezoneTools(server);
|
|
18
|
+
registerAstronomyTools(server);
|
|
19
|
+
registerAsnTools(server);
|
|
20
|
+
registerAbuseTools(server);
|
|
21
|
+
registerUserAgentTools(server);
|
|
22
|
+
const transport = new StdioServerTransport();
|
|
23
|
+
await server.connect(transport);
|
|
24
|
+
process.on("SIGINT", async () => {
|
|
25
|
+
await server.close();
|
|
26
|
+
process.exit(0);
|
|
27
|
+
});
|
|
28
|
+
process.on("SIGTERM", async () => {
|
|
29
|
+
await server.close();
|
|
30
|
+
process.exit(0);
|
|
31
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getAbuseContact } from "../client.js";
|
|
3
|
+
export function registerAbuseTools(server) {
|
|
4
|
+
server.registerTool("get_abuse_contact", {
|
|
5
|
+
title: "Abuse Contact Lookup",
|
|
6
|
+
annotations: {
|
|
7
|
+
readOnlyHint: true,
|
|
8
|
+
},
|
|
9
|
+
description: `Get abuse contact information for any IP address using ipgeolocation.io's dedicated abuse endpoint (GET /v3/abuse). Paid plans only. Free plan returns 401 Unauthorized. Costs 1 credit per lookup.
|
|
10
|
+
|
|
11
|
+
Returns: route, country, name, organization, kind, address, emails (array), phone_numbers (array). Useful for reporting malicious activity to the correct network operator.
|
|
12
|
+
|
|
13
|
+
Note: abuse data is also available through lookup_ip with include=abuse, which costs 2 credits total (1 base + 1 for abuse) but also returns full geolocation data. If you only need the abuse contact without geolocation, this dedicated endpoint is cheaper at 1 credit. Tip: you can also use lookup_ip with include=abuse&fields=abuse to get just the abuse data for 1 credit total. The fields and excludes parameters work on all plans to filter the response.`,
|
|
14
|
+
inputSchema: {
|
|
15
|
+
ip: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("IPv4 or IPv6 address to get abuse contact for. Omit to use the caller's IP."),
|
|
19
|
+
fields: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Comma-separated fields to return (e.g. emails,organization). Reduces response size. Works on all plans."),
|
|
23
|
+
excludes: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Comma-separated fields to exclude from response (e.g. phone_numbers,address)."),
|
|
27
|
+
},
|
|
28
|
+
}, async (params) => {
|
|
29
|
+
try {
|
|
30
|
+
const result = await getAbuseContact(params);
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
isError: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getAsn } from "../client.js";
|
|
3
|
+
export function registerAsnTools(server) {
|
|
4
|
+
server.registerTool("lookup_asn", {
|
|
5
|
+
title: "ASN Details",
|
|
6
|
+
annotations: {
|
|
7
|
+
readOnlyHint: true,
|
|
8
|
+
},
|
|
9
|
+
description: `Look up detailed Autonomous System Number (ASN) information using ipgeolocation.io's dedicated ASN endpoint (GET /v3/asn). Paid plans only. Free plan returns 401 Unauthorized. Costs 1 credit per lookup.
|
|
10
|
+
|
|
11
|
+
Query by AS number (e.g. AS13335) or by IP address to find its ASN. Returns: as_number, asn_name, organization, country, type (ISP/Business/Hosting/etc), domain, rir (ARIN/RIPE/APNIC/etc), date_allocated, allocation_status, num_of_ipv4_routes, num_of_ipv6_routes.
|
|
12
|
+
|
|
13
|
+
Optional include parameter adds: peers (peer ASNs), downstreams (downstream ASNs), upstreams (upstream ASNs), routes (announced IPv4/IPv6 routes), whois_response (raw WHOIS text).
|
|
14
|
+
|
|
15
|
+
Note: basic ASN data (as_number, organization, country) is already included in lookup_ip responses on all plans including free. Paid plan lookup_ip also returns type, domain, date_allocated, and rir. This dedicated endpoint adds asn_name, allocation_status, route counts, and the optional peers/downstreams/upstreams/routes/whois data that lookup_ip does not provide. Use lookup_ip for basic ASN info. Use this tool only when you need the extended ASN details.`,
|
|
16
|
+
inputSchema: {
|
|
17
|
+
asn: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("AS number to look up (e.g. AS13335 or just 13335). Takes priority over ip if both are provided."),
|
|
21
|
+
ip: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("IPv4 or IPv6 address to find the ASN for. Used only if asn is not provided."),
|
|
25
|
+
include: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Comma-separated extra data to include: peers, downstreams, upstreams, routes, whois_response. No additional credit cost."),
|
|
29
|
+
fields: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Comma-separated dot-path fields to return (e.g. asn.as_number,asn.organization). Overrides include if both are set."),
|
|
33
|
+
excludes: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Comma-separated dot-path fields to exclude (e.g. asn.date_allocated,asn.rir)."),
|
|
37
|
+
},
|
|
38
|
+
}, async (params) => {
|
|
39
|
+
try {
|
|
40
|
+
const result = await getAsn(params);
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getAstronomy, getAstronomyTimeSeries } from "../client.js";
|
|
3
|
+
export function registerAstronomyTools(server) {
|
|
4
|
+
server.registerTool("get_astronomy", {
|
|
5
|
+
title: "Astronomy Data",
|
|
6
|
+
annotations: {
|
|
7
|
+
readOnlyHint: true,
|
|
8
|
+
},
|
|
9
|
+
description: `Get sunrise, sunset, moonrise, moonset, twilight times, golden hour, blue hour, sun/moon positions, and moon phase data for any location using ipgeolocation.io (GET /v3/astronomy). Works on all plans including free. Costs 1 credit per request.
|
|
10
|
+
|
|
11
|
+
Look up by coordinates, city/address, or IP address. All lookup modes work on both free and paid plans. Supports custom dates and elevation for precise calculations.
|
|
12
|
+
|
|
13
|
+
Returns: sunrise, sunset, solar_noon, day_length, moonrise, moonset, civil/nautical/astronomical twilight start and end times, golden_hour and blue_hour start and end, sun_altitude, sun_azimuth, sun_distance, moon_altitude, moon_azimuth, moon_distance, moon_parallactic_angle, moon_phase (name and value 0-1), moon_illumination percentage.
|
|
14
|
+
|
|
15
|
+
The lang parameter for non-English location field responses is available on paid plans only. Free plan returns English responses regardless.`,
|
|
16
|
+
inputSchema: {
|
|
17
|
+
lat: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Latitude coordinate. Highest priority. Must be used with long."),
|
|
21
|
+
long: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Longitude coordinate. Must be used with lat."),
|
|
25
|
+
location: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("City or address string (e.g. San Francisco, CA)."),
|
|
29
|
+
ip: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("IPv4 or IPv6 address to get astronomy data for that IP's location."),
|
|
33
|
+
date: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Date in YYYY-MM-DD format. Defaults to today."),
|
|
37
|
+
elevation: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Elevation in meters above sea level (0-10000). Affects sunrise/sunset calculations for higher accuracy."),
|
|
41
|
+
time_zone: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("IANA timezone name to express times in (e.g. America/New_York). If set, time fields include full date instead of just time."),
|
|
45
|
+
lang: z
|
|
46
|
+
.string()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Response language for location fields in IP-based lookups (en, de, ru, ja, fr, cn, es, cs, it, ko, fa, pt). Paid plans only. Free plan returns English."),
|
|
49
|
+
},
|
|
50
|
+
}, async (params) => {
|
|
51
|
+
try {
|
|
52
|
+
const result = await getAstronomy(params);
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: "text",
|
|
64
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
isError: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
server.registerTool("get_astronomy_time_series", {
|
|
72
|
+
title: "Astronomy Time Series",
|
|
73
|
+
annotations: {
|
|
74
|
+
readOnlyHint: true,
|
|
75
|
+
},
|
|
76
|
+
description: `Get daily astronomical data for a date range (up to 90 days) using ipgeolocation.io's astronomy time series endpoint (GET /v3/astronomy/timeSeries). Works on all plans including free. Costs 1 credit per request regardless of the number of days.
|
|
77
|
+
|
|
78
|
+
Returns an array of daily astronomy objects, one per day in the range. Each object includes: date, sunrise, sunset, solar_noon, day_length, moonrise, moonset, moon_phase, moon_illumination_percentage, and all twilight/golden hour/blue hour times.
|
|
79
|
+
|
|
80
|
+
Time series responses do not include real-time positional data (sun_altitude, sun_azimuth, sun_distance, moon_altitude, moon_azimuth, moon_distance, moon_parallactic_angle). For those fields, use get_astronomy with a specific date instead.
|
|
81
|
+
|
|
82
|
+
Location can be specified by coordinates, city/address, or IP. If no location is given, uses the caller's IP.`,
|
|
83
|
+
inputSchema: {
|
|
84
|
+
lat: z
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Latitude coordinate. Highest priority. Must be used with long."),
|
|
88
|
+
long: z
|
|
89
|
+
.string()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("Longitude coordinate. Must be used with lat."),
|
|
92
|
+
location: z
|
|
93
|
+
.string()
|
|
94
|
+
.optional()
|
|
95
|
+
.describe("City or address string (e.g. San Francisco, CA)."),
|
|
96
|
+
ip: z
|
|
97
|
+
.string()
|
|
98
|
+
.optional()
|
|
99
|
+
.describe("IPv4 or IPv6 address to get astronomy data for that IP's location."),
|
|
100
|
+
dateStart: z
|
|
101
|
+
.string()
|
|
102
|
+
.describe("Start date in YYYY-MM-DD format. Required. Maximum range between dateStart and dateEnd is 90 days."),
|
|
103
|
+
dateEnd: z
|
|
104
|
+
.string()
|
|
105
|
+
.describe("End date in YYYY-MM-DD format. Required. Maximum range between dateStart and dateEnd is 90 days."),
|
|
106
|
+
elevation: z
|
|
107
|
+
.string()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Elevation in meters above sea level (0-10000). Affects sunrise/sunset calculations."),
|
|
110
|
+
time_zone: z
|
|
111
|
+
.string()
|
|
112
|
+
.optional()
|
|
113
|
+
.describe("IANA timezone name to express times in (e.g. America/New_York). If set, time fields include full date instead of just time."),
|
|
114
|
+
lang: z
|
|
115
|
+
.string()
|
|
116
|
+
.optional()
|
|
117
|
+
.describe("Response language for location fields in IP-based lookups (en, de, ru, ja, fr, cn, es, cs, it, ko, fa, pt). Paid plans only. Free plan returns 401 for non-English languages."),
|
|
118
|
+
},
|
|
119
|
+
}, async (params) => {
|
|
120
|
+
try {
|
|
121
|
+
const result = await getAstronomyTimeSeries(params);
|
|
122
|
+
return {
|
|
123
|
+
content: [
|
|
124
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: "text",
|
|
133
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|