@mrfentmen/exoplanet-archive-mcp 0.0.0-stage → 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 +32 -2
- package/dist/api.d.ts +3 -0
- package/dist/api.js +64 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +18 -0
- package/package.json +42 -4
package/README.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
-
#
|
|
1
|
+
# NASA Exoplanet Archive MCP
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Confirmed exoplanets with host stars and discovery data from NASA. Keyless — no signup, no API key, no environment variables.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx -y @mrfentmen/exoplanet-archive-mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or add it to your MCP client:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"exoplanet-archive-mcp": { "command": "npx", "args": ["-y", "@mrfentmen/exoplanet-archive-mcp"] }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Tools
|
|
22
|
+
|
|
23
|
+
| Tool | What it does |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `search_planets` | Find exoplanets by name |
|
|
26
|
+
|
|
27
|
+
## Notes
|
|
28
|
+
|
|
29
|
+
Queries run through the archive TAP service. The table pscomppars holds one row per confirmed planet.
|
|
30
|
+
|
|
31
|
+
## Licence
|
|
32
|
+
|
|
33
|
+
MIT
|
package/dist/api.d.ts
ADDED
package/dist/api.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NASA Exoplanet Archive — Confirmed exoplanets with host stars and discovery data from NASA. Keyless: no signup, no API key, no environment variables.
|
|
3
|
+
*
|
|
4
|
+
* Verified live against exoplanetarchive.ipac.caltech.edu.
|
|
5
|
+
*/
|
|
6
|
+
const API = "https://exoplanetarchive.ipac.caltech.edu/TAP/sync";
|
|
7
|
+
export class ExoplanetArchiveError extends Error {
|
|
8
|
+
}
|
|
9
|
+
const val = (v) => {
|
|
10
|
+
if (typeof v === "string")
|
|
11
|
+
return v;
|
|
12
|
+
if (typeof v === "number")
|
|
13
|
+
return Number.isFinite(v) ? String(v) : "";
|
|
14
|
+
if (Array.isArray(v))
|
|
15
|
+
return v.length > 0 ? val(v[0]) : "";
|
|
16
|
+
if (v && typeof v === "object") {
|
|
17
|
+
const o = v;
|
|
18
|
+
if ("en" in o)
|
|
19
|
+
return val(o.en);
|
|
20
|
+
const first = Object.values(o)[0];
|
|
21
|
+
return first === undefined ? "" : val(first);
|
|
22
|
+
}
|
|
23
|
+
return "";
|
|
24
|
+
};
|
|
25
|
+
const at = (o, path) => path === "" ? o : path.split(".").reduce((acc, k) => (acc && typeof acc === "object" ? acc[k] : undefined), o);
|
|
26
|
+
const rows = (v) => (Array.isArray(v) ? v : []);
|
|
27
|
+
async function get(url, init, timeoutMs = 25000) {
|
|
28
|
+
const ctrl = new AbortController();
|
|
29
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
...init,
|
|
33
|
+
signal: ctrl.signal,
|
|
34
|
+
headers: { "user-agent": "exoplanet-archive-mcp", accept: "application/json", ...(init?.headers ?? {}) },
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
throw new ExoplanetArchiveError(`NASA Exoplanet Archive returned HTTP ${res.status}`);
|
|
38
|
+
return (await res.json());
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
if (e instanceof ExoplanetArchiveError)
|
|
42
|
+
throw e;
|
|
43
|
+
if (e.name === "AbortError")
|
|
44
|
+
throw new ExoplanetArchiveError("NASA Exoplanet Archive request timed out");
|
|
45
|
+
throw new ExoplanetArchiveError(`NASA Exoplanet Archive request failed: ${e.message}`);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function searchPlanets(name, limit = 5) {
|
|
52
|
+
const q = name.trim();
|
|
53
|
+
if (q === "")
|
|
54
|
+
throw new ExoplanetArchiveError("name must not be empty");
|
|
55
|
+
const size = Math.min(25, Math.max(1, limit));
|
|
56
|
+
const safe = q.replace(/'/g, "");
|
|
57
|
+
const sql = `select top ${size} pl_name,hostname,disc_year,discoverymethod from pscomppars where pl_name like '%${safe}%'`;
|
|
58
|
+
const body = await get(`${API}?query=${encodeURIComponent(sql)}&format=json`);
|
|
59
|
+
const items = rows(body);
|
|
60
|
+
if (items.length === 0)
|
|
61
|
+
return `The archive has no planets matching "${q}".`;
|
|
62
|
+
const lines = items.map((r, i) => `${i + 1}. ${val(at(r, "pl_name"))} — ${val(at(r, "hostname"))} (${val(at(r, "disc_year"))}, ${val(at(r, "discoverymethod"))})`);
|
|
63
|
+
return [`Exoplanets matching "${q}" — ${items.length} shown`, "", ...lines].join("\n");
|
|
64
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createServer } from "./server.js";
|
|
4
|
+
async function main() {
|
|
5
|
+
const server = createServer();
|
|
6
|
+
const transport = new StdioServerTransport();
|
|
7
|
+
await server.connect(transport);
|
|
8
|
+
console.error("exoplanet-archive-mcp running on stdio");
|
|
9
|
+
}
|
|
10
|
+
main().catch((err) => {
|
|
11
|
+
console.error("Fatal error:", err);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { searchPlanets } from "./api.js";
|
|
4
|
+
const text = (t) => ({ content: [{ type: "text", text: t }] });
|
|
5
|
+
const fail = (t) => ({ content: [{ type: "text", text: t }], isError: true });
|
|
6
|
+
const errorMessage = (e) => `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
7
|
+
export function createServer() {
|
|
8
|
+
const server = new McpServer({ name: "exoplanet-archive-mcp", version: "1.0.0" });
|
|
9
|
+
server.tool("search_planets", "Find exoplanets by name", { name: z.string().min(1).describe("Planet or host name, e.g. Kepler"), limit: z.number().int().min(1).max(25).default(5).describe("How many planets") }, async ({ name, limit }) => {
|
|
10
|
+
try {
|
|
11
|
+
return text(await searchPlanets(name, limit));
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
return fail(errorMessage(e));
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return server;
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrfentmen/exoplanet-archive-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "NASA Exoplanet Archive: confirmed exoplanets, host stars and discovery year - keyless.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"mcpName": "io.github.mrfentmen/exoplanet-archive-mcp",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/mrfentmen/awesome-servers.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"exoplanet-archive-mcp": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"start": "node dist/index.js",
|
|
21
|
+
"test": "node tests/smoke.mjs"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"exoplanet-archive",
|
|
26
|
+
"space",
|
|
27
|
+
"exoplanets",
|
|
28
|
+
"astronomy",
|
|
29
|
+
"nasa",
|
|
30
|
+
"keyless"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
35
|
+
"zod": "^3.23.8"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
39
|
+
"typescript": "^5.6.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20"
|
|
43
|
+
}
|
|
44
|
+
}
|