parseapi-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/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # parseapi-mcp
2
+
3
+ Official parseAPI MCP server. Every parseAPI lookup as a tool for AI agents: IP and place data, email, phone and domain validation, weather, currency, timezones, holidays.
4
+
5
+ Get a key at [parseapi.com](https://parseapi.com). Free plan works.
6
+
7
+ ## Hosted
8
+
9
+ One URL, nothing to install.
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "parseapi": {
15
+ "url": "https://mcp.parseapi.com",
16
+ "headers": { "X-API-Key": "your-api-key" }
17
+ }
18
+ }
19
+ }
20
+ ```
21
+
22
+ ## Local
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "parseapi": {
28
+ "command": "npx",
29
+ "args": ["-y", "parseapi-mcp"],
30
+ "env": { "PARSEAPI_KEY": "your-api-key" }
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ ## Tools
37
+
38
+ One tool per endpoint, named after the route: `ip`, `postal`, `city`, `city_search`, `email`, `phone`, `weather`, `currency_rate`, `timezone`, `holiday`, `point`, `elevation`, and the rest. Every tool returns the same minimal JSON the API serves.
39
+
40
+ Errors come back as JSON with a machine-readable `code`. Branch on `code`, never on message text. A miss is `not_found`. No key is `invalid_api_key`.
41
+
42
+ ## Development
43
+
44
+ ```bash
45
+ npm install
46
+ npm run build
47
+ npm run smoke # stdio + http, live calls when PARSEAPI_KEY is set
48
+ npm run serve # http on :8080
49
+ ```
50
+
51
+ MIT licensed.
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/registry.ts
4
+ import { McpServer } from "@modelcontextprotocol/server";
5
+ import { parseAPI } from "@parseapi/sdk";
6
+ import * as z from "zod";
7
+
8
+ // src/errors.ts
9
+ import { ParseAPIError } from "@parseapi/sdk";
10
+ function ok(data) {
11
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
12
+ }
13
+ function errorJson(code, message, docs, requestId) {
14
+ return {
15
+ isError: true,
16
+ content: [{ type: "text", text: JSON.stringify({ code, message, docs, request_id: requestId }) }]
17
+ };
18
+ }
19
+ function toErrorResult(err) {
20
+ if (err instanceof ParseAPIError) {
21
+ return errorJson(err.code, err.message, err.docs, err.requestId);
22
+ }
23
+ const message = err instanceof Error ? err.message : String(err);
24
+ return errorJson("network_error", message, null, null);
25
+ }
26
+ function noKeyResult(transport) {
27
+ const where = transport === "stdio" ? "set it as the PARSEAPI_KEY environment variable for this MCP server" : "send it in the X-API-Key header";
28
+ return errorJson(
29
+ "invalid_api_key",
30
+ `No API key. Get a free key at https://parseapi.com and ${where}.`,
31
+ "https://parseapi.com/docs#invalid_api_key",
32
+ null
33
+ );
34
+ }
35
+
36
+ // src/registry.ts
37
+ var VERSION = "0.1.0";
38
+ var deep = z.boolean().optional().describe("Include the nested deep object with richer fields. Paid on most endpoints.");
39
+ var lat = z.number().min(-90).max(90).describe("Latitude in decimal degrees");
40
+ var lon = z.number().min(-180).max(180).describe("Longitude in decimal degrees");
41
+ var iso2 = (what) => z.string().describe(`ISO 3166-1 alpha-2 ${what}, e.g. US`);
42
+ function buildServer(key, transport) {
43
+ const server = new McpServer(
44
+ {
45
+ name: "parseapi",
46
+ version: VERSION,
47
+ title: "parseAPI",
48
+ description: "Lookups for agents: IP and place data, email, phone and domain validation, weather, currency, timezones, holidays. Real reference data instead of guessing.",
49
+ websiteUrl: "https://parseapi.com"
50
+ },
51
+ { capabilities: { tools: {} } }
52
+ );
53
+ const parse = key ? parseAPI(key) : null;
54
+ function tool(name, description, shape, fn) {
55
+ server.registerTool(
56
+ name,
57
+ {
58
+ description,
59
+ inputSchema: z.object(shape),
60
+ annotations: { readOnlyHint: true }
61
+ },
62
+ async (args) => {
63
+ if (!parse) return noKeyResult(transport);
64
+ try {
65
+ return ok(await fn(parse, args));
66
+ } catch (err) {
67
+ return toErrorResult(err);
68
+ }
69
+ }
70
+ );
71
+ }
72
+ tool(
73
+ "ip",
74
+ "Look up an IPv4 or IPv6 address: country, region, ASN, timezone. Deep adds datacenter, relay and tor flags.",
75
+ { ip: z.string().describe("IPv4 or IPv6 address, e.g. 8.8.8.8"), deep },
76
+ (c, a) => c.ip(a.ip, { deep: a.deep })
77
+ );
78
+ if (transport === "stdio") {
79
+ tool(
80
+ "ip_self",
81
+ "Look up the public IP of the machine running this MCP server.",
82
+ { deep },
83
+ (c, a) => c.ip.self({ deep: a.deep })
84
+ );
85
+ }
86
+ tool(
87
+ "continent",
88
+ "Look up a continent by code: name, area, population.",
89
+ { code: z.string().describe("Continent code: AF, AN, AS, EU, NA, OC, SA") },
90
+ (c, a) => c.continent(a.code)
91
+ );
92
+ tool(
93
+ "continent_countries",
94
+ "List every country on a continent.",
95
+ { code: z.string().describe("Continent code: AF, AN, AS, EU, NA, OC, SA") },
96
+ (c, a) => c.continent.countries(a.code)
97
+ );
98
+ tool(
99
+ "country",
100
+ "Look up a country: names, capital, currency, languages, calling code, timezones.",
101
+ { code: iso2("country code") },
102
+ (c, a) => c.country(a.code)
103
+ );
104
+ tool(
105
+ "country_states",
106
+ "List the states, provinces or regions of a country.",
107
+ { code: iso2("country code") },
108
+ (c, a) => c.country.states(a.code)
109
+ );
110
+ tool(
111
+ "state",
112
+ "Look up a state, province or region by its code within a country.",
113
+ { code: z.string().describe("State code, e.g. NC"), country: iso2("country code") },
114
+ (c, a) => c.state(a.code, { country: a.country })
115
+ );
116
+ tool(
117
+ "state_districts",
118
+ "List the districts, counties or departments of a state.",
119
+ { code: z.string().describe("State code, e.g. NC"), country: iso2("country code") },
120
+ (c, a) => c.state.districts(a.code, { country: a.country })
121
+ );
122
+ tool(
123
+ "district",
124
+ "Look up a district, county or department by code.",
125
+ { code: z.string().describe("District code, e.g. 37081"), country: iso2("country code").optional() },
126
+ (c, a) => c.district(a.code, { country: a.country })
127
+ );
128
+ tool(
129
+ "city",
130
+ "Look up a city by name: coordinates, state, population, timezone. Pass country to disambiguate name ties.",
131
+ {
132
+ name: z.string().describe("City name, e.g. charlotte"),
133
+ country: iso2("country code").optional(),
134
+ state: z.string().optional().describe("State code to disambiguate, e.g. NC")
135
+ },
136
+ (c, a) => c.city(a.name, { country: a.country, state: a.state })
137
+ );
138
+ tool(
139
+ "city_id",
140
+ "Refetch a city by its stable parse id from an earlier response.",
141
+ { id: z.string().describe("Stable city id, e.g. city_mb8mbqrkz8zb") },
142
+ (c, a) => c.city.id(a.id)
143
+ );
144
+ tool(
145
+ "city_search",
146
+ "Search cities by name prefix. Use when the exact name is unknown.",
147
+ {
148
+ q: z.string().describe("Name prefix, e.g. char"),
149
+ country: iso2("country code").optional(),
150
+ state: z.string().optional().describe("State code filter"),
151
+ limit: z.number().int().min(1).max(50).optional().describe("Max results")
152
+ },
153
+ (c, a) => c.city.search(a.q, { country: a.country, state: a.state, limit: a.limit })
154
+ );
155
+ tool(
156
+ "city_nearest",
157
+ "Find the nearest city to coordinates.",
158
+ { lat, lon },
159
+ (c, a) => c.city.nearest(a.lat, a.lon)
160
+ );
161
+ tool(
162
+ "postal",
163
+ "Look up a postal code: place name, coordinates, state, district, timezone, elevation. Country is required.",
164
+ { code: z.string().describe("Postal code, e.g. 28202"), country: iso2("country code") },
165
+ (c, a) => c.postal(a.code, { country: a.country })
166
+ );
167
+ tool(
168
+ "postal_nearby",
169
+ "List postal codes near a given one, sorted by distance.",
170
+ {
171
+ code: z.string().describe("Postal code to search around"),
172
+ country: iso2("country code"),
173
+ radius: z.number().positive().optional().describe("Search radius"),
174
+ unit: z.enum(["km", "mi"]).optional().describe("Radius unit, default km")
175
+ },
176
+ (c, a) => c.postal.nearby(a.code, { country: a.country, radius: a.radius, unit: a.unit })
177
+ );
178
+ tool(
179
+ "postal_distance",
180
+ "Distance between two postal codes in the same country.",
181
+ {
182
+ from: z.string().describe("First postal code"),
183
+ to: z.string().describe("Second postal code"),
184
+ country: iso2("country code")
185
+ },
186
+ (c, a) => c.postal.distance(a.from, a.to, { country: a.country })
187
+ );
188
+ tool(
189
+ "point",
190
+ "Reverse geocode coordinates to country, state, district and nearest city. Deep adds richer admin data.",
191
+ { lat, lon, deep },
192
+ (c, a) => c.point(a.lat, a.lon, { deep: a.deep })
193
+ );
194
+ tool(
195
+ "elevation",
196
+ "Elevation in meters at coordinates.",
197
+ { lat, lon },
198
+ (c, a) => c.elevation(a.lat, a.lon)
199
+ );
200
+ tool(
201
+ "weather",
202
+ "Current weather observation at coordinates from official national agencies. Every measurement ships metric and imperial side by side. Deep adds forecast and alerts where available.",
203
+ {
204
+ lat,
205
+ lon,
206
+ deep
207
+ },
208
+ (c, a) => c.weather(a.lat, a.lon, { deep: a.deep })
209
+ );
210
+ tool(
211
+ "email",
212
+ "Validate an email address: syntax, domain, MX, disposable and role flags. Deep runs a live mailbox verification.",
213
+ { email: z.string().describe("Email address to validate"), deep },
214
+ (c, a) => c.email(a.email, { deep: a.deep })
215
+ );
216
+ tool(
217
+ "phone",
218
+ "Validate and parse a phone number: country, type, formats. Pass country for national-format numbers.",
219
+ {
220
+ number: z.string().describe("Phone number, e.g. +14155552671"),
221
+ country: iso2("country code for national-format numbers").optional(),
222
+ deep
223
+ },
224
+ (c, a) => c.phone(a.number, { country: a.country, deep: a.deep })
225
+ );
226
+ tool(
227
+ "domain",
228
+ "Look up a domain: registration, DNS, mail setup. Deep adds richer checks.",
229
+ { domain: z.string().describe("Domain name, e.g. example.com"), deep },
230
+ (c, a) => c.domain(a.domain, { deep: a.deep })
231
+ );
232
+ tool(
233
+ "mx",
234
+ "MX records and mail provider for a domain.",
235
+ { domain: z.string().describe("Domain name") },
236
+ (c, a) => c.mx(a.domain)
237
+ );
238
+ tool(
239
+ "useragent",
240
+ "Parse a User-Agent string: browser, OS, device, bot detection.",
241
+ { ua: z.string().describe("The User-Agent string to parse"), deep },
242
+ (c, a) => c.useragent(a.ua, { deep: a.deep })
243
+ );
244
+ tool(
245
+ "currency",
246
+ "Look up a currency: name, symbol, decimal places, countries using it.",
247
+ { code: z.string().describe("ISO 4217 code, e.g. USD") },
248
+ (c, a) => c.currency(a.code)
249
+ );
250
+ tool(
251
+ "currency_rate",
252
+ "Exchange rate between two currencies from official central bank data.",
253
+ {
254
+ base: z.string().describe("Base currency ISO 4217 code, e.g. USD"),
255
+ quote: z.string().describe("Quote currency ISO 4217 code, e.g. EUR")
256
+ },
257
+ (c, a) => c.currency.rate(a.base, a.quote)
258
+ );
259
+ tool(
260
+ "language",
261
+ "Look up a language by BCP 47 or ISO 639-3 code: names, script, direction.",
262
+ { code: z.string().describe("Language code, e.g. en, ja, gsw") },
263
+ (c, a) => c.language(a.code)
264
+ );
265
+ tool(
266
+ "timezone",
267
+ "Look up an IANA timezone: current offset, DST state, local time. Pass at for a specific instant.",
268
+ {
269
+ timezone: z.string().describe("IANA timezone id, e.g. America/New_York"),
270
+ at: z.string().optional().describe("ISO 8601 instant to evaluate, default now")
271
+ },
272
+ (c, a) => c.timezone(a.timezone, { at: a.at })
273
+ );
274
+ tool(
275
+ "holiday",
276
+ "Public holidays and cultural observances for a country and year. Each row carries type: public or observance.",
277
+ {
278
+ country: iso2("country code"),
279
+ year: z.number().int().optional().describe("Year, default current")
280
+ },
281
+ (c, a) => c.holiday(a.country, { year: a.year })
282
+ );
283
+ tool(
284
+ "holiday_date",
285
+ "Whether a specific date is a holiday or observance in a country. holiday is null when it is not.",
286
+ { country: iso2("country code"), date: z.string().describe("Date as YYYY-MM-DD") },
287
+ (c, a) => c.holiday.date(a.country, a.date)
288
+ );
289
+ tool(
290
+ "emoji",
291
+ "Look up an emoji by name or character: unicode, hex, skin tones.",
292
+ { emoji: z.string().describe("Emoji name or the character itself, e.g. rocket") },
293
+ (c, a) => c.emoji(a.emoji)
294
+ );
295
+ tool(
296
+ "emoji_search",
297
+ "Search emoji by keyword.",
298
+ {
299
+ q: z.string().describe("Search keyword, e.g. fire"),
300
+ limit: z.number().int().min(1).max(50).optional().describe("Max results")
301
+ },
302
+ (c, a) => c.emoji.search(a.q, { limit: a.limit })
303
+ );
304
+ return server;
305
+ }
306
+
307
+ export {
308
+ VERSION,
309
+ buildServer
310
+ };
package/dist/http.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildServer
4
+ } from "./chunk-QELYOT3A.js";
5
+
6
+ // src/http.ts
7
+ import { createServer } from "http";
8
+ import { createMcpHandler } from "@modelcontextprotocol/server";
9
+ import { toNodeHandler } from "@modelcontextprotocol/node";
10
+ function keyFrom(request) {
11
+ if (!request) return null;
12
+ const headerKey = request.headers.get("x-api-key");
13
+ if (headerKey) return headerKey;
14
+ const auth = request.headers.get("authorization");
15
+ if (auth?.toLowerCase().startsWith("bearer ")) return auth.slice(7).trim() || null;
16
+ return null;
17
+ }
18
+ var handler = createMcpHandler((ctx) => buildServer(keyFrom(ctx.requestInfo), "http"), {
19
+ onerror: (err) => console.error("parseapi-mcp:", err.message)
20
+ });
21
+ var nodeHandler = toNodeHandler(handler);
22
+ var port = Number(process.env.PORT ?? 8080);
23
+ createServer((req, res) => {
24
+ if (req.method === "GET" && req.url === "/health") {
25
+ res.writeHead(200, { "content-type": "text/plain" }).end("ok");
26
+ return;
27
+ }
28
+ void nodeHandler(req, res);
29
+ }).listen(port, () => {
30
+ console.error(`parseapi-mcp listening on :${port}`);
31
+ });
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ VERSION,
4
+ buildServer
5
+ } from "./chunk-QELYOT3A.js";
6
+ export {
7
+ VERSION,
8
+ buildServer
9
+ };
package/dist/stdio.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildServer
4
+ } from "./chunk-QELYOT3A.js";
5
+
6
+ // src/stdio.ts
7
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
8
+ var key = process.env.PARSEAPI_KEY ?? null;
9
+ if (!key) {
10
+ console.error(
11
+ "parseapi-mcp: PARSEAPI_KEY is not set. Tools are listed but calls return invalid_api_key. Get a free key at https://parseapi.com"
12
+ );
13
+ }
14
+ serveStdio(() => buildServer(key, "stdio"));
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "parseapi-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Official parseAPI MCP server. Location, validate, and decode lookups for AI agents: geo, email, phone, weather, currency.",
5
+ "keywords": [
6
+ "parseapi",
7
+ "mcp",
8
+ "model context protocol",
9
+ "agents",
10
+ "geolocation",
11
+ "email validation",
12
+ "weather"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "parseAPI <hello@parseapi.com> (https://parseapi.com)",
16
+ "homepage": "https://parseapi.com",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/parseapi/parseapi-mcp.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/parseapi/parseapi-mcp/issues"
23
+ },
24
+ "type": "module",
25
+ "bin": {
26
+ "parseapi-mcp": "./dist/stdio.js"
27
+ },
28
+ "exports": {
29
+ ".": "./dist/registry.js",
30
+ "./http": "./dist/http.js"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "typecheck": "tsc --noEmit",
41
+ "smoke": "npm run build && node smoke/smoke.mjs",
42
+ "serve": "npm run build && node dist/http.js"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/server": "^2.0.0",
46
+ "@modelcontextprotocol/node": "^2.0.0",
47
+ "@parseapi/sdk": "^0.1.0",
48
+ "zod": "^4.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^22.10.0",
52
+ "tsup": "^8.3.5",
53
+ "typescript": "^5.7.0"
54
+ }
55
+ }