@nucl-parquet/mcp 0.13.2 → 0.14.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/dist/index.d.ts +71 -17
- package/dist/index.js +529 -123
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,27 +1,81 @@
|
|
|
1
|
+
import duckdb from 'duckdb';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* nucl-parquet MCP server — wraps local data via DuckDB.
|
|
5
|
+
*
|
|
6
|
+
* SSoT refactor (epic #173, Sub-D #178): the MCP is a thin shell over
|
|
7
|
+
* DuckDB-backed Parquet views, not a re-implementation of parquet I/O.
|
|
8
|
+
* All data is read from the local data directory — no HTTP fetching.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
declare const PKG_VERSION: string;
|
|
1
12
|
interface Library {
|
|
2
13
|
name: string;
|
|
3
14
|
description: string;
|
|
4
|
-
source_url
|
|
5
|
-
projectiles
|
|
6
|
-
data_type
|
|
15
|
+
source_url?: string;
|
|
16
|
+
projectiles?: string[];
|
|
17
|
+
data_type?: string;
|
|
7
18
|
version: string;
|
|
19
|
+
path?: string;
|
|
20
|
+
}
|
|
21
|
+
interface ViewDef {
|
|
8
22
|
path: string;
|
|
23
|
+
type?: "file" | "glob";
|
|
24
|
+
optional?: boolean;
|
|
25
|
+
note?: string;
|
|
9
26
|
}
|
|
10
27
|
interface Catalog {
|
|
28
|
+
/**
|
|
29
|
+
* The data release this server is serving, e.g. `2026.8.3`.
|
|
30
|
+
*
|
|
31
|
+
* Distinct from `Library.version`, which is the *evaluation's* version
|
|
32
|
+
* ("2023-iso"). They answer different questions, and conflating them would
|
|
33
|
+
* make a cross-server check silently wrong rather than merely absent (#348).
|
|
34
|
+
*
|
|
35
|
+
* Read from the catalog on disk, never compiled in — a build-time constant
|
|
36
|
+
* would be a second source of truth for the one fact whose whole job is to
|
|
37
|
+
* identify the data actually being read.
|
|
38
|
+
*/
|
|
39
|
+
data_version: string;
|
|
11
40
|
libraries: Record<string, Library>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
path: string;
|
|
15
|
-
files: Record<string, string>;
|
|
16
|
-
};
|
|
17
|
-
stopping: {
|
|
18
|
-
path: string;
|
|
19
|
-
files: Record<string, string>;
|
|
20
|
-
sources: string[];
|
|
21
|
-
};
|
|
22
|
-
};
|
|
41
|
+
views?: Record<string, ViewDef>;
|
|
42
|
+
[key: string]: unknown;
|
|
23
43
|
}
|
|
24
|
-
declare function
|
|
25
|
-
|
|
44
|
+
declare function ensureCatalog(): Catalog;
|
|
45
|
+
/**
|
|
46
|
+
* Parse and validate a catalog.
|
|
47
|
+
*
|
|
48
|
+
* Separate from {@link ensureCatalog} because that one memoises a module-level
|
|
49
|
+
* singleton, which a test cannot re-enter — so the validation below would be
|
|
50
|
+
* unreachable from a test and, in practice, unverified.
|
|
51
|
+
*/
|
|
52
|
+
declare function parseCatalog(raw: string, dataDir: string): Catalog;
|
|
53
|
+
/**
|
|
54
|
+
* The `list_libraries` payload: the data release, then the libraries.
|
|
55
|
+
*
|
|
56
|
+
* A named function rather than an inline object so a test can assert the shape
|
|
57
|
+
* the tool actually returns. Asserting only that the catalog *has* a
|
|
58
|
+
* data_version would pass while the tool still returned a bare array.
|
|
59
|
+
*/
|
|
60
|
+
declare function libraryListPayload(cat: Catalog): {
|
|
61
|
+
data_version: string;
|
|
62
|
+
libraries: Array<Record<string, unknown>>;
|
|
63
|
+
};
|
|
64
|
+
declare function getDb(): duckdb.Database;
|
|
65
|
+
/**
|
|
66
|
+
* Server instructions, naming the data release in the client's context.
|
|
67
|
+
*
|
|
68
|
+
* The referral case #348 is about does not go through `list_libraries`:
|
|
69
|
+
* hyrr-mcp sends an agent straight to `get_cross_sections` for full σ(E)
|
|
70
|
+
* curves, having stated the release *it* computed against. Reporting the
|
|
71
|
+
* release only from a tool the agent has no reason to call would leave that
|
|
72
|
+
* claim as unverifiable as it was — so it is stated here too, where it reaches
|
|
73
|
+
* the model without a round trip, and in `list_libraries` where it can be read
|
|
74
|
+
* programmatically. Both read the same loaded catalog, so they cannot disagree.
|
|
75
|
+
*
|
|
76
|
+
* Kept word-for-word in step with the Rust server's `instructions()`; a test
|
|
77
|
+
* pins that the two do not drift.
|
|
78
|
+
*/
|
|
79
|
+
declare function instructions(dataVersion: string): string;
|
|
26
80
|
|
|
27
|
-
export {
|
|
81
|
+
export { PKG_VERSION, ensureCatalog, getDb, instructions, libraryListPayload, parseCatalog };
|
package/dist/index.js
CHANGED
|
@@ -1,120 +1,205 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import { join, resolve } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
4
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
-
import
|
|
7
|
-
import { compressors } from "hyparquet-compressors";
|
|
10
|
+
import duckdb from "duckdb";
|
|
8
11
|
import { z } from "zod";
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
12
|
+
var require2 = createRequire(import.meta.url);
|
|
13
|
+
var { version: PKG_VERSION } = require2("../package.json");
|
|
14
|
+
function resolveDataDir() {
|
|
15
|
+
const envDir = process.env.NUCL_PARQUET_DATA;
|
|
16
|
+
if (envDir && existsSync(envDir)) return resolve(envDir);
|
|
17
|
+
const repoDir = resolve(import.meta.dirname ?? ".", "..", "..", "..", "..", "data");
|
|
18
|
+
if (existsSync(join(repoDir, "catalog.json"))) return repoDir;
|
|
19
|
+
const homeData = join(homedir(), ".nucl-parquet");
|
|
20
|
+
if (existsSync(join(homeData, "catalog.json"))) return homeData;
|
|
21
|
+
throw new Error(
|
|
22
|
+
"Cannot find nucl-parquet data directory. Set NUCL_PARQUET_DATA env var or place data at ~/.nucl-parquet/. Data releases: https://github.com/exoma-ch/nucl-parquet/releases"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
var _catalog;
|
|
26
|
+
function ensureCatalog() {
|
|
27
|
+
if (_catalog) return _catalog;
|
|
28
|
+
const dataDir = resolveDataDir();
|
|
29
|
+
_catalog = parseCatalog(readFileSync(join(dataDir, "catalog.json"), "utf-8"), dataDir);
|
|
30
|
+
return _catalog;
|
|
31
|
+
}
|
|
32
|
+
function parseCatalog(raw, dataDir) {
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
if (typeof parsed.data_version !== "string" || parsed.data_version === "") {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`catalog.json at ${dataDir} has no 'data_version'. It is required by data/catalog.schema.json and identifies the data release this server serves; refusing to start rather than report an unknown release as if it were known.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
function libraryListPayload(cat) {
|
|
42
|
+
return {
|
|
43
|
+
// Beside the array rather than on each entry, so the release and a
|
|
44
|
+
// library's evaluation `version` cannot be read as the same kind of thing.
|
|
45
|
+
data_version: cat.data_version,
|
|
46
|
+
libraries: Object.entries(cat.libraries).filter(([, lib]) => lib.projectiles).map(([id, lib]) => ({
|
|
47
|
+
id,
|
|
48
|
+
name: lib.name,
|
|
49
|
+
description: lib.description,
|
|
50
|
+
projectiles: lib.projectiles,
|
|
51
|
+
version: lib.version,
|
|
52
|
+
data_type: lib.data_type
|
|
53
|
+
}))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
var _db;
|
|
57
|
+
function hasParquetFiles(dir) {
|
|
58
|
+
if (!existsSync(dir)) return false;
|
|
59
|
+
try {
|
|
60
|
+
return readdirSync(dir).some((f) => f.endsWith(".parquet"));
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function registerParquet(db, filePath, viewName) {
|
|
66
|
+
if (existsSync(filePath)) {
|
|
67
|
+
db.run(`CREATE VIEW ${viewName} AS SELECT * FROM read_parquet('${filePath}')`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function registerGlob(db, dir, viewName) {
|
|
71
|
+
if (hasParquetFiles(dir)) {
|
|
72
|
+
const glob = join(dir, "*.parquet");
|
|
73
|
+
db.run(`CREATE VIEW ${viewName} AS SELECT * FROM read_parquet('${glob}')`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function registerViews(db, dataDir) {
|
|
77
|
+
const catalog = ensureCatalog();
|
|
78
|
+
const libViews = [];
|
|
79
|
+
for (const [libKey, lib] of Object.entries(catalog.libraries)) {
|
|
80
|
+
if (!lib.path) continue;
|
|
81
|
+
const libDir = join(dataDir, lib.path);
|
|
82
|
+
if (!hasParquetFiles(libDir)) continue;
|
|
83
|
+
const viewName = libKey.replace(/-/g, "_").replace(/\./g, "_");
|
|
84
|
+
const glob = join(libDir, "*.parquet");
|
|
85
|
+
db.run(
|
|
86
|
+
`CREATE VIEW ${viewName} AS SELECT *, '${libKey}' AS library FROM read_parquet('${glob}', filename=true)`
|
|
87
|
+
);
|
|
88
|
+
if (lib.data_type === "cross_sections") libViews.push(viewName);
|
|
89
|
+
}
|
|
90
|
+
if (libViews.length > 0) {
|
|
91
|
+
const union = libViews.map((v) => `SELECT * FROM ${v}`).join(" UNION ALL ");
|
|
92
|
+
db.run(`CREATE VIEW xs AS ${union}`);
|
|
93
|
+
}
|
|
94
|
+
for (const [viewName, viewDef] of Object.entries(catalog.views ?? {})) {
|
|
95
|
+
const viewPath = join(dataDir, viewDef.path);
|
|
96
|
+
if (viewDef.type === "glob") {
|
|
97
|
+
registerGlob(db, viewPath, viewName);
|
|
98
|
+
} else {
|
|
99
|
+
registerParquet(db, viewPath, viewName);
|
|
41
100
|
}
|
|
42
101
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
102
|
+
const nuclidesPath = join(dataDir, "meta", "ensdf", "nuclides.parquet");
|
|
103
|
+
if (existsSync(nuclidesPath)) {
|
|
104
|
+
db.run("CREATE OR REPLACE VIEW ground_states AS SELECT * FROM nuclides WHERE state = 'g'");
|
|
105
|
+
}
|
|
106
|
+
const eadlDir = join(dataDir, "meta", "eadl");
|
|
107
|
+
if (hasParquetFiles(eadlDir)) {
|
|
108
|
+
db.run("CREATE VIEW eadl_transitions AS SELECT * FROM atomic_relaxation");
|
|
109
|
+
db.run("CREATE VIEW fluorescence AS SELECT * FROM atomic_relaxation WHERE transition_type = 'radiative'");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function getDb() {
|
|
113
|
+
if (_db) return _db;
|
|
114
|
+
const dataDir = resolveDataDir();
|
|
115
|
+
const db = new duckdb.Database(":memory:");
|
|
116
|
+
registerViews(db, dataDir);
|
|
117
|
+
_db = db;
|
|
118
|
+
return db;
|
|
119
|
+
}
|
|
120
|
+
function dbAll(db, sql, params) {
|
|
121
|
+
return new Promise((resolve2, reject) => {
|
|
122
|
+
const cb = (err, rows) => {
|
|
123
|
+
if (err) reject(err);
|
|
124
|
+
else resolve2(rows);
|
|
125
|
+
};
|
|
126
|
+
if (params && params.length > 0) {
|
|
127
|
+
db.all(sql, ...params, cb);
|
|
128
|
+
} else {
|
|
129
|
+
db.all(sql, cb);
|
|
58
130
|
}
|
|
59
131
|
});
|
|
60
|
-
parquetCache.set(relativePath, rows);
|
|
61
|
-
return rows;
|
|
62
132
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (!resp.ok) throw new Error(`HTTP ${resp.status} fetching manifest for ${libraryId}`);
|
|
70
|
-
return await resp.json();
|
|
133
|
+
function safeStringify(obj, indent) {
|
|
134
|
+
return JSON.stringify(
|
|
135
|
+
obj,
|
|
136
|
+
(_key, value) => typeof value === "bigint" ? Number(value) : value,
|
|
137
|
+
indent
|
|
138
|
+
);
|
|
71
139
|
}
|
|
72
|
-
function
|
|
73
|
-
|
|
140
|
+
async function query(sql, params, maxRows = 500) {
|
|
141
|
+
const db = getDb();
|
|
142
|
+
const countSql = `SELECT CAST(COUNT(*) AS INTEGER) AS n FROM (${sql})`;
|
|
143
|
+
const countRows = await dbAll(db, countSql, params);
|
|
144
|
+
const total = countRows[0]?.n ?? 0;
|
|
145
|
+
const limitSql = `${sql} LIMIT ${maxRows}`;
|
|
146
|
+
const rows = await dbAll(db, limitSql, params);
|
|
147
|
+
return { total, truncated: total > maxRows, rows };
|
|
74
148
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
return { rows: rows.slice(0, limit), truncated: true, total: rows.length };
|
|
149
|
+
function instructions(dataVersion) {
|
|
150
|
+
return `Serving nucl-parquet data release ${dataVersion}. This identifies the *data*, not this server's version \u2014 report it whenever you compare results with another server or tool. If another source states a different release, say so: agreement or disagreement computed across two releases is an artefact of the mismatch, not a physics result. Call list_libraries to read the same value programmatically.`;
|
|
78
151
|
}
|
|
79
|
-
var server = new McpServer(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
152
|
+
var server = new McpServer(
|
|
153
|
+
{
|
|
154
|
+
name: "nucl-parquet",
|
|
155
|
+
// The package version — the software, not the data. Both are reported
|
|
156
|
+
// because they answer different questions, and only one of them changes
|
|
157
|
+
// when the data does.
|
|
158
|
+
version: PKG_VERSION
|
|
159
|
+
},
|
|
160
|
+
{ instructions: instructions(ensureCatalog().data_version) }
|
|
161
|
+
);
|
|
83
162
|
server.tool(
|
|
84
163
|
"list_libraries",
|
|
85
164
|
"List all available nuclear data libraries with projectiles and descriptions",
|
|
86
165
|
{},
|
|
87
166
|
async () => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
version: lib.version,
|
|
94
|
-
data_type: lib.data_type
|
|
95
|
-
}));
|
|
96
|
-
return { content: [{ type: "text", text: JSON.stringify(libs, null, 2) }] };
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{ type: "text", text: safeStringify(libraryListPayload(ensureCatalog()), 2) }
|
|
170
|
+
]
|
|
171
|
+
};
|
|
97
172
|
}
|
|
98
173
|
);
|
|
99
174
|
server.tool(
|
|
100
175
|
"list_isotopes",
|
|
101
176
|
"List available target elements for a given library and projectile. Returns element symbols.",
|
|
102
177
|
{
|
|
103
|
-
library: z.string().describe("Library ID, e.g. 'tendl-
|
|
178
|
+
library: z.string().describe("Library ID, e.g. 'tendl-2025', 'endfb-8.1'"),
|
|
104
179
|
projectile: z.string().describe("Projectile: n, p, d, t, h, a, g")
|
|
105
180
|
},
|
|
106
181
|
async ({ library, projectile }) => {
|
|
107
|
-
const
|
|
182
|
+
const cat = ensureCatalog();
|
|
183
|
+
const lib = cat.libraries[library];
|
|
108
184
|
if (!lib) throw new Error(`Unknown library: ${library}. Use list_libraries to see available libraries.`);
|
|
109
|
-
if (!lib.projectiles
|
|
110
|
-
throw new Error(`Projectile '${projectile}' not available for ${library}. Available: ${lib.projectiles
|
|
185
|
+
if (!lib.projectiles?.includes(projectile)) {
|
|
186
|
+
throw new Error(`Projectile '${projectile}' not available for ${library}. Available: ${lib.projectiles?.join(", ") ?? "none"}`);
|
|
111
187
|
}
|
|
112
|
-
const
|
|
188
|
+
const dataDir = resolveDataDir();
|
|
189
|
+
const libPath = lib.path;
|
|
190
|
+
if (!libPath?.endsWith("xs/")) {
|
|
191
|
+
throw new Error(`Library ${library} path does not end with 'xs/' \u2014 cannot derive manifest path`);
|
|
192
|
+
}
|
|
193
|
+
const manifestPath = join(dataDir, libPath.replace("xs/", "manifest.json"));
|
|
194
|
+
if (!existsSync(manifestPath)) {
|
|
195
|
+
throw new Error(`Manifest not found at ${manifestPath}`);
|
|
196
|
+
}
|
|
197
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
113
198
|
const elements = manifest.elements ?? [];
|
|
114
199
|
return {
|
|
115
200
|
content: [{
|
|
116
201
|
type: "text",
|
|
117
|
-
text:
|
|
202
|
+
text: safeStringify({ library, projectile, elements, count: elements.length }, 2)
|
|
118
203
|
}]
|
|
119
204
|
};
|
|
120
205
|
}
|
|
@@ -123,26 +208,36 @@ server.tool(
|
|
|
123
208
|
"get_cross_sections",
|
|
124
209
|
"Get nuclear reaction cross-section data for a specific target element. Returns energy (MeV) and cross-section (mb) arrays with reaction product info.",
|
|
125
210
|
{
|
|
126
|
-
library: z.string().describe("Library ID, e.g. 'tendl-
|
|
211
|
+
library: z.string().describe("Library ID, e.g. 'tendl-2025'"),
|
|
127
212
|
projectile: z.string().describe("Projectile: n, p, d, t, h, a, g"),
|
|
128
213
|
element: z.string().describe("Target element symbol, e.g. 'Cu', 'Fe', 'Au'"),
|
|
129
214
|
max_rows: z.number().optional().describe("Max rows to return (default 500)")
|
|
130
215
|
},
|
|
131
216
|
async ({ library, projectile, element, max_rows }) => {
|
|
132
|
-
|
|
217
|
+
if (!/^[npdthag]$/.test(projectile)) {
|
|
218
|
+
throw new Error(`Invalid projectile: '${projectile}'. Must be one of: n, p, d, t, h, a, g`);
|
|
219
|
+
}
|
|
220
|
+
if (!/^[A-Z][a-z]?$/.test(element)) {
|
|
221
|
+
throw new Error(`Invalid element symbol: '${element}'. Must be 1-2 letters (e.g. 'Cu', 'Fe')`);
|
|
222
|
+
}
|
|
223
|
+
const cat = ensureCatalog();
|
|
224
|
+
const lib = cat.libraries[library];
|
|
133
225
|
if (!lib) throw new Error(`Unknown library: ${library}`);
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
226
|
+
const dataDir = resolveDataDir();
|
|
227
|
+
const parquetPath = join(dataDir, `${lib.path}${projectile}_${element}.parquet`);
|
|
228
|
+
if (!existsSync(parquetPath)) {
|
|
229
|
+
throw new Error(`No data for ${projectile}_${element} in ${library}`);
|
|
230
|
+
}
|
|
231
|
+
const db = getDb();
|
|
232
|
+
const result = await query(
|
|
233
|
+
`SELECT * FROM read_parquet('${parquetPath}')`,
|
|
234
|
+
[],
|
|
235
|
+
max_rows ?? 500
|
|
236
|
+
);
|
|
137
237
|
return {
|
|
138
238
|
content: [{
|
|
139
239
|
type: "text",
|
|
140
|
-
text:
|
|
141
|
-
library,
|
|
142
|
-
projectile,
|
|
143
|
-
element,
|
|
144
|
-
...result
|
|
145
|
-
}, null, 2)
|
|
240
|
+
text: safeStringify({ library, projectile, element, ...result }, 2)
|
|
146
241
|
}]
|
|
147
242
|
};
|
|
148
243
|
}
|
|
@@ -158,17 +253,24 @@ server.tool(
|
|
|
158
253
|
if (zNum === void 0 && aNum === void 0) {
|
|
159
254
|
throw new Error("Provide at least z or a to filter decay data.");
|
|
160
255
|
}
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
256
|
+
const conditions = [];
|
|
257
|
+
const params = [];
|
|
258
|
+
if (zNum !== void 0) {
|
|
259
|
+
conditions.push("Z = ?");
|
|
260
|
+
params.push(zNum);
|
|
261
|
+
}
|
|
262
|
+
if (aNum !== void 0) {
|
|
263
|
+
conditions.push("A = ?");
|
|
264
|
+
params.push(aNum);
|
|
265
|
+
}
|
|
266
|
+
const result = await query(
|
|
267
|
+
`SELECT * FROM decay WHERE ${conditions.join(" AND ")}`,
|
|
268
|
+
params
|
|
269
|
+
);
|
|
168
270
|
return {
|
|
169
271
|
content: [{
|
|
170
272
|
type: "text",
|
|
171
|
-
text:
|
|
273
|
+
text: safeStringify({ z: zNum, a: aNum, count: result.total, rows: result.rows }, 2)
|
|
172
274
|
}]
|
|
173
275
|
};
|
|
174
276
|
}
|
|
@@ -180,13 +282,11 @@ server.tool(
|
|
|
180
282
|
z: z.number().describe("Atomic number (e.g. 29 for Cu)")
|
|
181
283
|
},
|
|
182
284
|
async ({ z: zNum }) => {
|
|
183
|
-
const
|
|
184
|
-
const rows = await fetchParquetRows(metaPath);
|
|
185
|
-
const filtered = rows.filter((row) => row["Z"] === zNum);
|
|
285
|
+
const result = await query("SELECT * FROM abundances WHERE Z = ?", [zNum]);
|
|
186
286
|
return {
|
|
187
287
|
content: [{
|
|
188
288
|
type: "text",
|
|
189
|
-
text:
|
|
289
|
+
text: safeStringify({ z: zNum, count: result.total, isotopes: result.rows }, 2)
|
|
190
290
|
}]
|
|
191
291
|
};
|
|
192
292
|
}
|
|
@@ -196,34 +296,336 @@ server.tool(
|
|
|
196
296
|
"Get mass stopping power (dE/dx) data for a projectile in a target element.",
|
|
197
297
|
{
|
|
198
298
|
source: z.string().describe(
|
|
199
|
-
"Data source: PSTAR (protons), ASTAR (\u03B1, NIST ICRU-49), ESTAR (electrons), dSTAR, tSTAR (velocity-scaled from PSTAR), or catima (full Z\xD7Z table
|
|
299
|
+
"Data source: PSTAR (protons), ASTAR (\u03B1, NIST ICRU-49), ESTAR (electrons), dSTAR, tSTAR (velocity-scaled from PSTAR), or catima (full Z\xD7Z table)"
|
|
200
300
|
),
|
|
201
301
|
target_z: z.number().describe("Target element atomic number")
|
|
202
302
|
},
|
|
203
303
|
async ({ source, target_z }) => {
|
|
204
|
-
const
|
|
205
|
-
|
|
304
|
+
const viewMap = {
|
|
305
|
+
PSTAR: "stopping",
|
|
306
|
+
ASTAR: "stopping",
|
|
307
|
+
ESTAR: "stopping",
|
|
308
|
+
dSTAR: "stopping",
|
|
309
|
+
tSTAR: "stopping",
|
|
310
|
+
catima: "catima_stopping"
|
|
311
|
+
};
|
|
312
|
+
if (!(source in viewMap)) {
|
|
313
|
+
throw new Error(`Unknown source '${source}'. Valid: PSTAR, ASTAR, ESTAR, dSTAR, tSTAR, catima`);
|
|
314
|
+
}
|
|
315
|
+
const view = viewMap[source];
|
|
316
|
+
const sql = source === "catima" ? `SELECT * FROM ${view} WHERE target_Z = ?` : `SELECT * FROM ${view} WHERE source = ? AND target_Z = ?`;
|
|
317
|
+
const params = source === "catima" ? [target_z] : [source, target_z];
|
|
318
|
+
const result = await query(sql, params);
|
|
319
|
+
return {
|
|
320
|
+
content: [{
|
|
321
|
+
type: "text",
|
|
322
|
+
text: safeStringify({ source, target_z, count: result.total, rows: result.rows }, 2)
|
|
323
|
+
}]
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
server.tool(
|
|
328
|
+
"get_radiation",
|
|
329
|
+
"Get radiation emissions (gammas, X-rays, Auger electrons, conversion electrons) for a nuclide.",
|
|
330
|
+
{
|
|
331
|
+
z: z.number().describe("Atomic number of the parent nuclide"),
|
|
332
|
+
a: z.number().optional().describe("Mass number (omit for all isotopes of element Z)"),
|
|
333
|
+
max_rows: z.number().optional().describe("Max rows to return (default 500)")
|
|
334
|
+
},
|
|
335
|
+
async ({ z: zNum, a: aNum, max_rows }) => {
|
|
336
|
+
const conditions = ["Z = ?"];
|
|
337
|
+
const params = [zNum];
|
|
338
|
+
if (aNum !== void 0) {
|
|
339
|
+
conditions.push("A = ?");
|
|
340
|
+
params.push(aNum);
|
|
341
|
+
}
|
|
342
|
+
const result = await query(
|
|
343
|
+
`SELECT * FROM radiation WHERE ${conditions.join(" AND ")}`,
|
|
344
|
+
params,
|
|
345
|
+
max_rows ?? 500
|
|
346
|
+
);
|
|
347
|
+
return {
|
|
348
|
+
content: [{
|
|
349
|
+
type: "text",
|
|
350
|
+
text: safeStringify({ z: zNum, a: aNum, ...result }, 2)
|
|
351
|
+
}]
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
server.tool(
|
|
356
|
+
"get_coincidences",
|
|
357
|
+
"Get gamma-gamma and mixed-emission coincidence pairs for a nuclide. Includes beta/EC/X-ray/Auger/511 keV annihilation paired with gammas.",
|
|
358
|
+
{
|
|
359
|
+
z: z.number().describe("Atomic number of the parent nuclide"),
|
|
360
|
+
a: z.number().optional().describe("Mass number (omit for all isotopes of element Z)"),
|
|
361
|
+
max_rows: z.number().optional().describe("Max rows to return (default 500)")
|
|
362
|
+
},
|
|
363
|
+
async ({ z: zNum, a: aNum, max_rows }) => {
|
|
364
|
+
const conditions = ["Z = ?"];
|
|
365
|
+
const params = [zNum];
|
|
366
|
+
if (aNum !== void 0) {
|
|
367
|
+
conditions.push("A = ?");
|
|
368
|
+
params.push(aNum);
|
|
369
|
+
}
|
|
370
|
+
const result = await query(
|
|
371
|
+
`SELECT * FROM coincidences WHERE ${conditions.join(" AND ")}`,
|
|
372
|
+
params,
|
|
373
|
+
max_rows ?? 500
|
|
374
|
+
);
|
|
375
|
+
return {
|
|
376
|
+
content: [{
|
|
377
|
+
type: "text",
|
|
378
|
+
text: safeStringify({ z: zNum, a: aNum, ...result }, 2)
|
|
379
|
+
}]
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
server.tool(
|
|
384
|
+
"get_summing_partners",
|
|
385
|
+
"Get ICC-corrected summing partners for HPGe true-coincidence-summing (TCS) corrections. Returns emission pairs with pre-computed icc_correction_factor and pure_emission_joint_intensity.",
|
|
386
|
+
{
|
|
387
|
+
z: z.number().describe("Atomic number of the daughter nuclide (filing convention)"),
|
|
388
|
+
a: z.number().describe("Mass number"),
|
|
389
|
+
primary_energy_keV: z.number().optional().describe("Filter to pairs matching this energy on either side"),
|
|
390
|
+
tolerance_keV: z.number().optional().describe("Energy match tolerance in keV (default 0.5)"),
|
|
391
|
+
emission1_rad_type: z.string().optional().describe("Filter side 1: 'gamma', 'xray', 'auger'"),
|
|
392
|
+
max_rows: z.number().optional().describe("Max rows to return (default 500)")
|
|
393
|
+
},
|
|
394
|
+
async ({ z: zNum, a: aNum, primary_energy_keV, tolerance_keV, emission1_rad_type, max_rows }) => {
|
|
395
|
+
const conditions = ["Z = ?", "A = ?"];
|
|
396
|
+
const params = [zNum, aNum];
|
|
397
|
+
const tol = tolerance_keV ?? 0.5;
|
|
398
|
+
if (primary_energy_keV !== void 0) {
|
|
399
|
+
conditions.push("(ABS(emission1_energy_keV - ?) < ? OR ABS(emission2_energy_keV - ?) < ?)");
|
|
400
|
+
params.push(primary_energy_keV, tol, primary_energy_keV, tol);
|
|
401
|
+
}
|
|
402
|
+
if (emission1_rad_type !== void 0) {
|
|
403
|
+
conditions.push("emission1_rad_type = ?");
|
|
404
|
+
params.push(emission1_rad_type);
|
|
405
|
+
}
|
|
406
|
+
const result = await query(
|
|
407
|
+
`SELECT * FROM summing_partners WHERE ${conditions.join(" AND ")} ORDER BY pure_emission_joint_intensity DESC`,
|
|
408
|
+
params,
|
|
409
|
+
max_rows ?? 500
|
|
410
|
+
);
|
|
411
|
+
return {
|
|
412
|
+
content: [{
|
|
413
|
+
type: "text",
|
|
414
|
+
text: safeStringify({ z: zNum, a: aNum, primary_energy_keV, ...result }, 2)
|
|
415
|
+
}]
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
server.tool(
|
|
420
|
+
"get_emissions",
|
|
421
|
+
"Get absolute per-decay photon emission intensities (NuDat-equivalent). Returns gamma emissions for a parent nuclide with absolute intensities (photon emission probability per decay, 0-100%). Filed by parent, not daughter.",
|
|
422
|
+
{
|
|
423
|
+
parent_z: z.number().describe("Atomic number of the decaying parent (e.g. 27 for Co-60)"),
|
|
424
|
+
parent_a: z.number().describe("Mass number of the parent (e.g. 60 for Co-60)"),
|
|
425
|
+
parent_state: z.string().optional().describe("Nuclear state: '' (ground), 'm', 'm2'"),
|
|
426
|
+
decay_mode: z.string().optional().describe("Filter by decay mode: 'beta-', 'KshellEC', 'IT', etc."),
|
|
427
|
+
energy_keV: z.number().optional().describe("Filter to gammas near this energy"),
|
|
428
|
+
tolerance_keV: z.number().optional().describe("Energy tolerance (default 0.5 keV)"),
|
|
429
|
+
min_intensity_pct: z.number().optional().describe("Minimum absolute intensity (%) to include"),
|
|
430
|
+
max_rows: z.number().optional().describe("Max rows to return (default 500)")
|
|
431
|
+
},
|
|
432
|
+
async ({ parent_z, parent_a, parent_state, decay_mode, energy_keV, tolerance_keV, min_intensity_pct, max_rows }) => {
|
|
433
|
+
const conditions = ["parent_Z = ?", "parent_A = ?", "parent_state = ?"];
|
|
434
|
+
const params = [parent_z, parent_a, parent_state ?? ""];
|
|
435
|
+
const tol = tolerance_keV ?? 0.5;
|
|
436
|
+
if (decay_mode !== void 0) {
|
|
437
|
+
conditions.push("decay_mode = ?");
|
|
438
|
+
params.push(decay_mode);
|
|
439
|
+
}
|
|
440
|
+
if (energy_keV !== void 0) {
|
|
441
|
+
conditions.push("ABS(energy_keV - ?) < ?");
|
|
442
|
+
params.push(energy_keV, tol);
|
|
443
|
+
}
|
|
444
|
+
if (min_intensity_pct !== void 0 && min_intensity_pct > 0) {
|
|
445
|
+
conditions.push("intensity_pct >= ?");
|
|
446
|
+
params.push(min_intensity_pct);
|
|
447
|
+
}
|
|
448
|
+
const result = await query(
|
|
449
|
+
`SELECT * FROM emissions WHERE ${conditions.join(" AND ")} ORDER BY intensity_pct DESC`,
|
|
450
|
+
params,
|
|
451
|
+
max_rows ?? 500
|
|
452
|
+
);
|
|
453
|
+
return {
|
|
454
|
+
content: [{
|
|
455
|
+
type: "text",
|
|
456
|
+
text: safeStringify({ parent_z, parent_a, parent_state: parent_state ?? "", ...result }, 2)
|
|
457
|
+
}]
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
);
|
|
461
|
+
server.tool(
|
|
462
|
+
"get_beta_spectrum",
|
|
463
|
+
"Get the continuous beta-decay kinetic-energy spectrum for a nuclide. Returns pre-tabulated Fermi-function spectra (dN/dE normalized to 1).",
|
|
464
|
+
{
|
|
465
|
+
z: z.number().describe("Atomic number of the parent nuclide"),
|
|
466
|
+
a: z.number().describe("Mass number of the parent nuclide"),
|
|
467
|
+
max_rows: z.number().optional().describe("Max rows to return (default 500)")
|
|
468
|
+
},
|
|
469
|
+
async ({ z: zNum, a: aNum, max_rows }) => {
|
|
470
|
+
const result = await query(
|
|
471
|
+
"SELECT * FROM beta_spectra WHERE Z = ? AND A = ?",
|
|
472
|
+
[zNum, aNum],
|
|
473
|
+
max_rows ?? 500
|
|
474
|
+
);
|
|
475
|
+
return {
|
|
476
|
+
content: [{
|
|
477
|
+
type: "text",
|
|
478
|
+
text: safeStringify({ z: zNum, a: aNum, ...result }, 2)
|
|
479
|
+
}]
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
server.tool(
|
|
484
|
+
"get_compound_compositions",
|
|
485
|
+
"Get elemental compositions (weight fractions) for NIST XCOM standard materials. Useful for Bragg-additive cross-section calculations.",
|
|
486
|
+
{
|
|
487
|
+
material: z.string().optional().describe("Material name (e.g. 'Water, Liquid'). Omit to list all materials.")
|
|
488
|
+
},
|
|
489
|
+
async ({ material }) => {
|
|
490
|
+
const db = getDb();
|
|
491
|
+
if (material === void 0) {
|
|
492
|
+
const rows = await dbAll(db, "SELECT DISTINCT material FROM compound_compositions ORDER BY material");
|
|
493
|
+
const materials = rows.map((r) => r.material);
|
|
206
494
|
return {
|
|
207
495
|
content: [{
|
|
208
496
|
type: "text",
|
|
209
|
-
text:
|
|
210
|
-
error: `unknown source ${source}; valid: ${CATALOG.shared.stopping.sources.join(", ")}`
|
|
211
|
-
}, null, 2)
|
|
497
|
+
text: safeStringify({ count: materials.length, materials }, 2)
|
|
212
498
|
}]
|
|
213
499
|
};
|
|
214
500
|
}
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
501
|
+
const result = await query(
|
|
502
|
+
"SELECT * FROM compound_compositions WHERE material = ?",
|
|
503
|
+
[material]
|
|
504
|
+
);
|
|
505
|
+
if (result.total === 0) {
|
|
506
|
+
const allRows = await dbAll(db, "SELECT DISTINCT material FROM compound_compositions ORDER BY material");
|
|
507
|
+
const all = allRows.map((r) => r.material);
|
|
508
|
+
throw new Error(`Unknown material: '${material}'. Available: ${all.join(", ")}`);
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
content: [{
|
|
512
|
+
type: "text",
|
|
513
|
+
text: safeStringify({ material, count: result.total, composition: result.rows }, 2)
|
|
514
|
+
}]
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
server.tool(
|
|
519
|
+
"get_electron_stopping",
|
|
520
|
+
"Get electron stopping power with collision/radiative split. Richer than ESTAR \u2014 includes ~183 compounds plus all elements Z=1..98.",
|
|
521
|
+
{
|
|
522
|
+
target: z.string().optional().describe("Compound name (e.g. 'G4_WATER'). For elements use target_z instead."),
|
|
523
|
+
target_z: z.number().optional().describe("Atomic number for elemental targets"),
|
|
524
|
+
max_rows: z.number().optional().describe("Max rows to return (default 500)")
|
|
525
|
+
},
|
|
526
|
+
async ({ target, target_z, max_rows }) => {
|
|
527
|
+
if (target === void 0 && target_z === void 0) {
|
|
528
|
+
throw new Error("Provide target (compound name) or target_z (atomic number).");
|
|
529
|
+
}
|
|
530
|
+
let result;
|
|
531
|
+
if (target_z !== void 0) {
|
|
532
|
+
result = await query(
|
|
533
|
+
"SELECT * FROM electron_stopping WHERE target_Z = ?",
|
|
534
|
+
[target_z],
|
|
535
|
+
max_rows ?? 500
|
|
536
|
+
);
|
|
537
|
+
} else {
|
|
538
|
+
result = await query(
|
|
539
|
+
"SELECT * FROM electron_stopping WHERE name = ? OR g4_name = ?",
|
|
540
|
+
[target, target],
|
|
541
|
+
max_rows ?? 500
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
content: [{
|
|
546
|
+
type: "text",
|
|
547
|
+
text: safeStringify({ target, target_z, ...result }, 2)
|
|
548
|
+
}]
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
);
|
|
552
|
+
var BLOCKED_FUNCTIONS = /\b(read_parquet|parquet_scan|parquet_metadata|parquet_schema|read_csv|read_csv_auto|read_json|read_json_auto|read_text|read_blob|glob|copy|export|attach|load|install|create|drop|alter|insert|update|delete|truncate|query_table|pragma)\b/i;
|
|
553
|
+
var ALLOWED_FIRST_WORDS = /* @__PURE__ */ new Set(["SELECT", "WITH", "EXPLAIN", "DESCRIBE", "SHOW", "SUMMARIZE"]);
|
|
554
|
+
server.tool(
|
|
555
|
+
"sql_query",
|
|
556
|
+
"Execute read-only SQL against all 70+ nuclear data tables. Supports JOINs, aggregations, window functions. Use describe_schema() to discover available tables and columns.",
|
|
557
|
+
{
|
|
558
|
+
sql: z.string().describe("Read-only SQL query. DDL/DML will be rejected."),
|
|
559
|
+
max_rows: z.number().optional().describe("Max rows to return (default 10000)")
|
|
560
|
+
},
|
|
561
|
+
async ({ sql: userSql, max_rows }) => {
|
|
562
|
+
const stripped = userSql.trim();
|
|
563
|
+
if (!stripped) throw new Error("Empty SQL query");
|
|
564
|
+
const firstWord = stripped.split(/\s/)[0].toUpperCase();
|
|
565
|
+
if (!ALLOWED_FIRST_WORDS.has(firstWord)) {
|
|
566
|
+
throw new Error(`Only read queries are allowed (SELECT, WITH, EXPLAIN, DESCRIBE). Got: ${firstWord}`);
|
|
567
|
+
}
|
|
568
|
+
if (BLOCKED_FUNCTIONS.test(stripped)) {
|
|
569
|
+
throw new Error("Only read queries are allowed (SELECT, WITH, EXPLAIN, DESCRIBE). File-access and DDL functions are blocked.");
|
|
570
|
+
}
|
|
571
|
+
const db = getDb();
|
|
572
|
+
const limit = max_rows ?? 1e4;
|
|
573
|
+
let total;
|
|
574
|
+
let rows;
|
|
575
|
+
try {
|
|
576
|
+
const countRows = await dbAll(db, `SELECT CAST(COUNT(*) AS INTEGER) AS n FROM (${stripped})`);
|
|
577
|
+
total = countRows[0]?.n ?? 0;
|
|
578
|
+
rows = await dbAll(db, `SELECT * FROM (${stripped}) LIMIT ${limit}`);
|
|
579
|
+
} catch (e) {
|
|
580
|
+
throw new Error(`SQL error: ${e.message}`);
|
|
581
|
+
}
|
|
582
|
+
const truncated = total > limit;
|
|
583
|
+
const display = rows;
|
|
584
|
+
return {
|
|
585
|
+
content: [{
|
|
586
|
+
type: "text",
|
|
587
|
+
text: safeStringify({ total, truncated, rows: display }, 2)
|
|
588
|
+
}]
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
);
|
|
592
|
+
server.tool(
|
|
593
|
+
"describe_schema",
|
|
594
|
+
"List all available tables/views with their column names and types. Use this to discover what data is available before writing SQL queries.",
|
|
595
|
+
{},
|
|
596
|
+
async () => {
|
|
597
|
+
const db = getDb();
|
|
598
|
+
const tables = await dbAll(db, "SHOW TABLES");
|
|
599
|
+
const tableNames = tables.map((r) => r.name).sort();
|
|
600
|
+
const schema = {};
|
|
601
|
+
for (const tbl of tableNames) {
|
|
602
|
+
try {
|
|
603
|
+
const cols = await dbAll(db, `DESCRIBE ${tbl}`);
|
|
604
|
+
schema[tbl] = cols.map((c) => ({ name: c.column_name, type: c.column_type }));
|
|
605
|
+
} catch {
|
|
606
|
+
schema[tbl] = [];
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
content: [{
|
|
611
|
+
type: "text",
|
|
612
|
+
text: safeStringify({ tables: tableNames.length, schema }, 2)
|
|
613
|
+
}]
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
);
|
|
617
|
+
server.tool(
|
|
618
|
+
"list_tables",
|
|
619
|
+
"List all available table/view names (short form of describe_schema).",
|
|
620
|
+
{},
|
|
621
|
+
async () => {
|
|
622
|
+
const db = getDb();
|
|
623
|
+
const tables = await dbAll(db, "SHOW TABLES");
|
|
624
|
+
const tableNames = tables.map((r) => r.name).sort();
|
|
218
625
|
return {
|
|
219
626
|
content: [{
|
|
220
627
|
type: "text",
|
|
221
|
-
text:
|
|
222
|
-
source,
|
|
223
|
-
target_z,
|
|
224
|
-
count: filtered.length,
|
|
225
|
-
rows: filtered
|
|
226
|
-
}, null, 2)
|
|
628
|
+
text: safeStringify({ count: tableNames.length, tables: tableNames }, 2)
|
|
227
629
|
}]
|
|
228
630
|
};
|
|
229
631
|
}
|
|
@@ -231,7 +633,11 @@ server.tool(
|
|
|
231
633
|
var transport = new StdioServerTransport();
|
|
232
634
|
await server.connect(transport);
|
|
233
635
|
export {
|
|
234
|
-
|
|
235
|
-
|
|
636
|
+
PKG_VERSION,
|
|
637
|
+
ensureCatalog,
|
|
638
|
+
getDb,
|
|
639
|
+
instructions,
|
|
640
|
+
libraryListPayload,
|
|
641
|
+
parseCatalog
|
|
236
642
|
};
|
|
237
643
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { parquetRead } from \"hyparquet\";\nimport { compressors } from \"hyparquet-compressors\";\nimport { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\nconst BASE_URL =\n process.env.NUCL_PARQUET_BASE_URL ??\n \"https://raw.githubusercontent.com/exoma-ch/nucl-parquet/main/data/\";\n\n// ---------------------------------------------------------------------------\n// Embedded catalog (from catalog.json)\n// ---------------------------------------------------------------------------\n\ninterface Library {\n name: string;\n description: string;\n source_url: string;\n projectiles: string[];\n data_type: string;\n version: string;\n path: string;\n}\n\ninterface Catalog {\n libraries: Record<string, Library>;\n shared: {\n meta: { path: string; files: Record<string, string> };\n stopping: { path: string; files: Record<string, string>; sources: string[] };\n };\n}\n\nconst CATALOG: Catalog = {\n libraries: {\n \"tendl-2024\": { name: \"TENDL-2024\", description: \"TALYS Evaluated Nuclear Data Library 2024 (IAEA/PSI)\", source_url: \"https://tendl.web.psi.ch/tendl_2024/tendl2024.html\", projectiles: [\"p\", \"d\", \"t\", \"h\", \"a\"], data_type: \"cross_sections\", version: \"2024\", path: \"tendl-2024/xs/\" },\n \"endfb-8.1\": { name: \"ENDF/B-VIII.1\", description: \"US Evaluated Nuclear Data File (NNDC/BNL)\", source_url: \"https://www.nndc.bnl.gov/endf-b8.1/\", projectiles: [\"n\", \"p\", \"d\", \"t\", \"h\", \"a\"], data_type: \"cross_sections\", version: \"VIII.1\", path: \"endfb-8.1/xs/\" },\n \"jeff-4.0\": { name: \"JEFF-4.0\", description: \"Joint Evaluated Fission and Fusion File (NEA)\", source_url: \"https://www.oecd-nea.org/dbdata/jeff/\", projectiles: [\"n\", \"p\"], data_type: \"cross_sections\", version: \"4.0\", path: \"jeff-4.0/xs/\" },\n \"jendl-5\": { name: \"JENDL-5\", description: \"Japanese Evaluated Nuclear Data Library (JAEA)\", source_url: \"https://wwwndc.jaea.go.jp/jendl/j5/j5.html\", projectiles: [\"n\", \"p\", \"d\", \"a\"], data_type: \"cross_sections\", version: \"5\", path: \"jendl-5/xs/\" },\n \"tendl-2025\": { name: \"TENDL-2025\", description: \"TALYS Evaluated Nuclear Data Library 2025 (PSI)\", source_url: \"https://tendl.web.psi.ch/\", projectiles: [\"n\", \"p\", \"d\", \"t\", \"h\", \"a\"], data_type: \"cross_sections\", version: \"2025\", path: \"tendl-2025/xs/\" },\n \"cendl-3.2\": { name: \"CENDL-3.2\", description: \"Chinese Evaluated Nuclear Data Library (CIAE)\", source_url: \"http://www.nuclear.csdb.cn/\", projectiles: [\"n\"], data_type: \"cross_sections\", version: \"3.2\", path: \"cendl-3.2/xs/\" },\n \"brond-3.1\": { name: \"BROND-3.1\", description: \"Russian Evaluated Nuclear Data Library (IPPE)\", source_url: \"https://vant.ippe.ru/\", projectiles: [\"n\"], data_type: \"cross_sections\", version: \"3.1\", path: \"brond-3.1/xs/\" },\n \"fendl-3.2\": { name: \"FENDL-3.2\", description: \"Fusion Evaluated Nuclear Data Library (IAEA)\", source_url: \"https://www-nds.iaea.org/fendl/\", projectiles: [\"n\"], data_type: \"cross_sections\", version: \"3.2\", path: \"fendl-3.2/xs/\" },\n \"eaf-2010\": { name: \"EAF-2010\", description: \"European Activation File (CCFE)\", source_url: \"https://fispact.ukaea.uk/\", projectiles: [\"n\"], data_type: \"cross_sections\", version: \"2010\", path: \"eaf-2010/xs/\" },\n \"irdff-2\": { name: \"IRDFF-II\", description: \"International Reactor Dosimetry and Fusion File (IAEA)\", source_url: \"https://www-nds.iaea.org/IRDFF/\", projectiles: [\"n\"], data_type: \"cross_sections\", version: \"II\", path: \"irdff-2/xs/\" },\n \"iaea-medical\": { name: \"IAEA-Medical\", description: \"Medical isotope production cross-sections (IAEA)\", source_url: \"https://www-nds.iaea.org/medical/\", projectiles: [\"p\", \"d\", \"h\", \"a\"], data_type: \"cross_sections\", version: \"latest\", path: \"iaea-medical/xs/\" },\n \"jendl-ad-2017\": { name: \"JENDL/AD-2017\", description: \"Activation/Dosimetry Library (JAEA)\", source_url: \"https://wwwndc.jaea.go.jp/jendl/jad/jad.html\", projectiles: [\"n\", \"p\"], data_type: \"cross_sections\", version: \"2017\", path: \"jendl-ad-2017/xs/\" },\n \"jendl-deu-2020\": { name: \"JENDL-DEU-2020\", description: \"Dedicated deuteron-induced reaction library (JAEA)\", source_url: \"https://wwwndc.jaea.go.jp/jendl/deu/deu.html\", projectiles: [\"d\"], data_type: \"cross_sections\", version: \"2020\", path: \"jendl-deu-2020/xs/\" },\n \"iaea-pd-2019\": { name: \"IAEA-PD-2019\", description: \"Photonuclear Data Library (IAEA)\", source_url: \"https://www-nds.iaea.org/photonuclear/\", projectiles: [\"g\"], data_type: \"cross_sections\", version: \"2019\", path: \"iaea-pd-2019/xs/\" },\n \"exfor\": { name: \"EXFOR\", description: \"Experimental nuclear reaction data (IAEA NDS)\", source_url: \"https://www-nds.iaea.org/exfor/\", projectiles: [\"n\", \"p\", \"d\", \"t\", \"h\", \"a\"], data_type: \"experimental_cross_sections\", version: \"latest\", path: \"exfor/\" },\n },\n shared: {\n meta: { path: \"meta/\", files: { abundances: \"abundances.parquet\", decay: \"decay.parquet\", elements: \"elements.parquet\" } },\n stopping: {\n path: \"stopping/\",\n files: {\n PSTAR: \"PSTAR.parquet\",\n ASTAR: \"ASTAR.parquet\",\n ESTAR: \"ESTAR.parquet\",\n dSTAR: \"dSTAR.parquet\",\n tSTAR: \"tSTAR.parquet\",\n catima: \"catima/catima.parquet\",\n } as Record<string, string>,\n sources: [\"PSTAR\", \"ASTAR\", \"ESTAR\", \"dSTAR\", \"tSTAR\", \"catima\"],\n },\n },\n};\n\n// ---------------------------------------------------------------------------\n// Parquet fetch + cache\n// ---------------------------------------------------------------------------\n\nconst parquetCache = new Map<string, Record<string, unknown>[]>();\n\nexport async function fetchParquetRows(\n relativePath: string,\n baseUrl: string = BASE_URL,\n): Promise<Record<string, unknown>[]> {\n if (parquetCache.has(relativePath)) return parquetCache.get(relativePath)!;\n\n const url = new URL(relativePath, baseUrl).href;\n const resp = await fetch(url);\n if (!resp.ok) throw new Error(`HTTP ${resp.status} fetching ${url}`);\n const buffer = await resp.arrayBuffer();\n\n const rows: Record<string, unknown>[] = [];\n await parquetRead({\n file: buffer,\n compressors,\n rowFormat: \"object\",\n onComplete: (data: Record<string, unknown>[]) => {\n rows.push(...data);\n },\n });\n\n parquetCache.set(relativePath, rows);\n return rows;\n}\n\nasync function fetchManifest(libraryId: string): Promise<Record<string, unknown>> {\n const lib = CATALOG.libraries[libraryId];\n if (!lib) throw new Error(`Unknown library: ${libraryId}`);\n const manifestPath = lib.path.replace(/xs\\/$/, \"manifest.json\");\n const url = new URL(manifestPath, BASE_URL).href;\n const resp = await fetch(url);\n if (!resp.ok) throw new Error(`HTTP ${resp.status} fetching manifest for ${libraryId}`);\n return (await resp.json()) as Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nexport function getCatalog(): Catalog {\n return CATALOG;\n}\n\nfunction truncateRows(rows: Record<string, unknown>[], limit: number): { rows: Record<string, unknown>[]; truncated: boolean; total: number } {\n if (rows.length <= limit) return { rows, truncated: false, total: rows.length };\n return { rows: rows.slice(0, limit), truncated: true, total: rows.length };\n}\n\n// ---------------------------------------------------------------------------\n// MCP Server\n// ---------------------------------------------------------------------------\n\nconst server = new McpServer({\n name: \"nucl-parquet\",\n version: \"0.3.7\",\n});\n\nserver.tool(\n \"list_libraries\",\n \"List all available nuclear data libraries with projectiles and descriptions\",\n {},\n async () => {\n const libs = Object.entries(CATALOG.libraries).map(([id, lib]) => ({\n id,\n name: lib.name,\n description: lib.description,\n projectiles: lib.projectiles,\n version: lib.version,\n data_type: lib.data_type,\n }));\n return { content: [{ type: \"text\" as const, text: JSON.stringify(libs, null, 2) }] };\n },\n);\n\nserver.tool(\n \"list_isotopes\",\n \"List available target elements for a given library and projectile. Returns element symbols.\",\n {\n library: z.string().describe(\"Library ID, e.g. 'tendl-2024', 'endfb-8.1'\"),\n projectile: z.string().describe(\"Projectile: n, p, d, t, h, a, g\"),\n },\n async ({ library, projectile }) => {\n const lib = CATALOG.libraries[library];\n if (!lib) throw new Error(`Unknown library: ${library}. Use list_libraries to see available libraries.`);\n if (!lib.projectiles.includes(projectile)) {\n throw new Error(`Projectile '${projectile}' not available for ${library}. Available: ${lib.projectiles.join(\", \")}`);\n }\n\n const manifest = await fetchManifest(library);\n const elements = (manifest.elements as string[]) ?? [];\n\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({ library, projectile, elements, count: elements.length }, null, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_cross_sections\",\n \"Get nuclear reaction cross-section data for a specific target element. Returns energy (MeV) and cross-section (mb) arrays with reaction product info.\",\n {\n library: z.string().describe(\"Library ID, e.g. 'tendl-2024'\"),\n projectile: z.string().describe(\"Projectile: n, p, d, t, h, a, g\"),\n element: z.string().describe(\"Target element symbol, e.g. 'Cu', 'Fe', 'Au'\"),\n max_rows: z.number().optional().describe(\"Max rows to return (default 500)\"),\n },\n async ({ library, projectile, element, max_rows }) => {\n const lib = CATALOG.libraries[library];\n if (!lib) throw new Error(`Unknown library: ${library}`);\n\n const parquetPath = `${lib.path}${projectile}_${element}.parquet`;\n const rows = await fetchParquetRows(parquetPath);\n const result = truncateRows(rows, max_rows ?? 500);\n\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({\n library,\n projectile,\n element,\n ...result,\n }, null, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_decay_data\",\n \"Get radioactive decay data (half-lives, decay modes, daughters) for a nuclide or element. Filter by Z and/or A.\",\n {\n z: z.number().optional().describe(\"Atomic number (e.g. 92 for U)\"),\n a: z.number().optional().describe(\"Mass number (e.g. 238)\"),\n },\n async ({ z: zNum, a: aNum }) => {\n if (zNum === undefined && aNum === undefined) {\n throw new Error(\"Provide at least z or a to filter decay data.\");\n }\n\n const metaPath = CATALOG.shared.meta.path + CATALOG.shared.meta.files.decay;\n const rows = await fetchParquetRows(metaPath);\n\n const filtered = rows.filter((row) => {\n if (zNum !== undefined && row[\"Z\"] !== zNum) return false;\n if (aNum !== undefined && row[\"A\"] !== aNum) return false;\n return true;\n });\n\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({ z: zNum, a: aNum, count: filtered.length, rows: filtered }, null, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_abundances\",\n \"Get natural isotope abundances and atomic masses for an element.\",\n {\n z: z.number().describe(\"Atomic number (e.g. 29 for Cu)\"),\n },\n async ({ z: zNum }) => {\n const metaPath = CATALOG.shared.meta.path + CATALOG.shared.meta.files.abundances;\n const rows = await fetchParquetRows(metaPath);\n\n const filtered = rows.filter((row) => row[\"Z\"] === zNum);\n\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({ z: zNum, count: filtered.length, isotopes: filtered }, null, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_stopping_power\",\n \"Get mass stopping power (dE/dx) data for a projectile in a target element.\",\n {\n source: z.string().describe(\n \"Data source: PSTAR (protons), ASTAR (α, NIST ICRU-49), ESTAR (electrons), dSTAR, tSTAR (velocity-scaled from PSTAR), or catima (full Z×Z table; α/³He fallback for non-NIST elements)\",\n ),\n target_z: z.number().describe(\"Target element atomic number\"),\n },\n async ({ source, target_z }) => {\n // Post-#143: aggregate stopping.parquet is deleted; route per-source.\n const fileMap = CATALOG.shared.stopping.files;\n if (!(source in fileMap)) {\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({\n error: `unknown source ${source}; valid: ${CATALOG.shared.stopping.sources.join(\", \")}`,\n }, null, 2),\n }],\n };\n }\n const stoppingPath = CATALOG.shared.stopping.path + fileMap[source];\n const rows = await fetchParquetRows(stoppingPath);\n\n // catima uses (proj_Z, target_Z); NIST tables use (source, target_Z).\n // Filter by target_Z in both cases — the file already restricts to one source.\n const filtered = rows.filter((row) => row[\"target_Z\"] === target_z);\n\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({\n source,\n target_z,\n count: filtered.length,\n rows: filtered,\n }, null, 2),\n }],\n };\n },\n);\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * nucl-parquet MCP server — wraps local data via DuckDB.\n *\n * SSoT refactor (epic #173, Sub-D #178): the MCP is a thin shell over\n * DuckDB-backed Parquet views, not a re-implementation of parquet I/O.\n * All data is read from the local data directory — no HTTP fetching.\n */\n\nimport { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport duckdb from \"duckdb\";\nimport { z } from \"zod\";\n\nconst require = createRequire(import.meta.url);\nexport const { version: PKG_VERSION } = require(\"../package.json\") as { version: string };\n\n// ---------------------------------------------------------------------------\n// Data directory resolution\n// ---------------------------------------------------------------------------\n\nfunction resolveDataDir(): string {\n // 1. Explicit env var\n const envDir = process.env.NUCL_PARQUET_DATA;\n if (envDir && existsSync(envDir)) return resolve(envDir);\n\n // 2. Repo-local data/ (development)\n const repoDir = resolve(import.meta.dirname ?? \".\", \"..\", \"..\", \"..\", \"..\", \"data\");\n if (existsSync(join(repoDir, \"catalog.json\"))) return repoDir;\n\n // 3. ~/.nucl-parquet/ (installed data)\n const homeData = join(homedir(), \".nucl-parquet\");\n if (existsSync(join(homeData, \"catalog.json\"))) return homeData;\n\n throw new Error(\n \"Cannot find nucl-parquet data directory. Set NUCL_PARQUET_DATA env var \" +\n \"or place data at ~/.nucl-parquet/. Data releases: \" +\n \"https://github.com/exoma-ch/nucl-parquet/releases\",\n );\n}\n\n// ---------------------------------------------------------------------------\n// Catalog (loaded from disk, not hardcoded)\n// ---------------------------------------------------------------------------\n\ninterface Library {\n name: string;\n description: string;\n source_url?: string;\n projectiles?: string[];\n data_type?: string;\n version: string;\n path?: string;\n}\n\ninterface ViewDef {\n path: string;\n type?: \"file\" | \"glob\";\n optional?: boolean;\n note?: string;\n}\n\ninterface Catalog {\n /**\n * The data release this server is serving, e.g. `2026.8.3`.\n *\n * Distinct from `Library.version`, which is the *evaluation's* version\n * (\"2023-iso\"). They answer different questions, and conflating them would\n * make a cross-server check silently wrong rather than merely absent (#348).\n *\n * Read from the catalog on disk, never compiled in — a build-time constant\n * would be a second source of truth for the one fact whose whole job is to\n * identify the data actually being read.\n */\n data_version: string;\n libraries: Record<string, Library>;\n views?: Record<string, ViewDef>;\n [key: string]: unknown;\n}\n\nlet _catalog: Catalog | undefined;\n\nexport function ensureCatalog(): Catalog {\n if (_catalog) return _catalog;\n const dataDir = resolveDataDir();\n _catalog = parseCatalog(readFileSync(join(dataDir, \"catalog.json\"), \"utf-8\"), dataDir);\n return _catalog;\n}\n\n/**\n * Parse and validate a catalog.\n *\n * Separate from {@link ensureCatalog} because that one memoises a module-level\n * singleton, which a test cannot re-enter — so the validation below would be\n * unreachable from a test and, in practice, unverified.\n */\nexport function parseCatalog(raw: string, dataDir: string): Catalog {\n const parsed = JSON.parse(raw) as Catalog;\n // `as Catalog` is a claim, not a check. `data_version` is required by\n // data/catalog.schema.json, and the whole point of reporting it is that it\n // identifies the tree actually being read — so an absent one must fail here\n // rather than surface as `undefined` in a referral an agent is trying to\n // verify (#348). The Rust server refuses the same catalog, for the same\n // reason.\n if (typeof parsed.data_version !== \"string\" || parsed.data_version === \"\") {\n throw new Error(\n `catalog.json at ${dataDir} has no 'data_version'. It is required by ` +\n `data/catalog.schema.json and identifies the data release this server serves; ` +\n `refusing to start rather than report an unknown release as if it were known.`,\n );\n }\n return parsed;\n}\n\n/**\n * The `list_libraries` payload: the data release, then the libraries.\n *\n * A named function rather than an inline object so a test can assert the shape\n * the tool actually returns. Asserting only that the catalog *has* a\n * data_version would pass while the tool still returned a bare array.\n */\nexport function libraryListPayload(cat: Catalog): {\n data_version: string;\n libraries: Array<Record<string, unknown>>;\n} {\n return {\n // Beside the array rather than on each entry, so the release and a\n // library's evaluation `version` cannot be read as the same kind of thing.\n data_version: cat.data_version,\n libraries: Object.entries(cat.libraries)\n .filter(([, lib]) => lib.projectiles)\n .map(([id, lib]) => ({\n id,\n name: lib.name,\n description: lib.description,\n projectiles: lib.projectiles,\n version: lib.version,\n data_type: lib.data_type,\n })),\n };\n}\n\n// ---------------------------------------------------------------------------\n// DuckDB connection (lazy init, thread-safe via singleton)\n// ---------------------------------------------------------------------------\n\nlet _db: duckdb.Database | undefined;\n\nfunction hasParquetFiles(dir: string): boolean {\n if (!existsSync(dir)) return false;\n try {\n return readdirSync(dir).some((f) => f.endsWith(\".parquet\"));\n } catch {\n return false;\n }\n}\n\nfunction registerParquet(db: duckdb.Database, filePath: string, viewName: string): void {\n if (existsSync(filePath)) {\n db.run(`CREATE VIEW ${viewName} AS SELECT * FROM read_parquet('${filePath}')`);\n }\n}\n\nfunction registerGlob(db: duckdb.Database, dir: string, viewName: string): void {\n if (hasParquetFiles(dir)) {\n const glob = join(dir, \"*.parquet\");\n db.run(`CREATE VIEW ${viewName} AS SELECT * FROM read_parquet('${glob}')`);\n }\n}\n\nfunction registerViews(db: duckdb.Database, dataDir: string): void {\n const catalog = ensureCatalog();\n\n // --- Cross-section libraries ---\n const libViews: string[] = [];\n for (const [libKey, lib] of Object.entries(catalog.libraries)) {\n if (!lib.path) continue;\n const libDir = join(dataDir, lib.path);\n if (!hasParquetFiles(libDir)) continue;\n\n const viewName = libKey.replace(/-/g, \"_\").replace(/\\./g, \"_\");\n const glob = join(libDir, \"*.parquet\");\n db.run(\n `CREATE VIEW ${viewName} AS SELECT *, '${libKey}' AS library FROM read_parquet('${glob}', filename=true)`,\n );\n if (lib.data_type === \"cross_sections\") libViews.push(viewName);\n }\n if (libViews.length > 0) {\n const union = libViews.map((v) => `SELECT * FROM ${v}`).join(\" UNION ALL \");\n db.run(`CREATE VIEW xs AS ${union}`);\n }\n\n // --- Catalog-driven view registration ---\n // All views declared in catalog.json::views — single source of truth.\n // New data tables become queryable by adding an entry to catalog.json,\n // no code changes needed in any client (Python, TypeScript, Rust).\n for (const [viewName, viewDef] of Object.entries(catalog.views ?? {})) {\n const viewPath = join(dataDir, viewDef.path);\n if (viewDef.type === \"glob\") {\n registerGlob(db, viewPath, viewName);\n } else {\n registerParquet(db, viewPath, viewName);\n }\n }\n\n // --- Special views that need logic beyond simple registration ---\n\n // ground_states: when nuclides.parquet exists, override with filtered view\n const nuclidesPath = join(dataDir, \"meta\", \"ensdf\", \"nuclides.parquet\");\n if (existsSync(nuclidesPath)) {\n // `'g'`, not `''`: #380 gave `state` one vocabulary and nuclides.parquet\n // now reads g=3148 / m=739 / m2=82 / m3=7 / null=13, with no `''` at all.\n // This view was silently empty against the rebuilt data.\n db.run(\"CREATE OR REPLACE VIEW ground_states AS SELECT * FROM nuclides WHERE state = 'g'\");\n }\n\n // EADL aliases: eadl_transitions (v0.11 compat) + fluorescence (radiative subset)\n const eadlDir = join(dataDir, \"meta\", \"eadl\");\n if (hasParquetFiles(eadlDir)) {\n db.run(\"CREATE VIEW eadl_transitions AS SELECT * FROM atomic_relaxation\");\n db.run(\"CREATE VIEW fluorescence AS SELECT * FROM atomic_relaxation WHERE transition_type = 'radiative'\");\n }\n}\n\nexport function getDb(): duckdb.Database {\n if (_db) return _db;\n const dataDir = resolveDataDir();\n const db = new duckdb.Database(\":memory:\");\n registerViews(db, dataDir);\n _db = db;\n return db;\n}\n\n// ---------------------------------------------------------------------------\n// DuckDB async helpers\n// ---------------------------------------------------------------------------\n\nfunction dbAll(db: duckdb.Database, sql: string, params?: unknown[]): Promise<Record<string, unknown>[]> {\n return new Promise((resolve, reject) => {\n const cb = (err: Error | null, rows: Record<string, unknown>[]) => {\n if (err) reject(err);\n else resolve(rows);\n };\n if (params && params.length > 0) {\n db.all(sql, ...params, cb);\n } else {\n db.all(sql, cb);\n }\n });\n}\n\nfunction dbRun(db: duckdb.Database, sql: string): Promise<void> {\n return new Promise((resolve, reject) => {\n db.run(sql, (err: Error | null) => {\n if (err) reject(err);\n else resolve();\n });\n });\n}\n\n/** Serialize rows to JSON, converting BigInt values to Number. */\nfunction safeStringify(obj: unknown, indent?: number): string {\n return JSON.stringify(\n obj,\n (_key, value) => (typeof value === \"bigint\" ? Number(value) : value),\n indent,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Query helper\n// ---------------------------------------------------------------------------\n\nasync function query(\n sql: string,\n params?: unknown[],\n maxRows: number = 500,\n): Promise<{ total: number; truncated: boolean; rows: Record<string, unknown>[] }> {\n const db = getDb();\n // Get total count via subquery\n const countSql = `SELECT CAST(COUNT(*) AS INTEGER) AS n FROM (${sql})`;\n const countRows = await dbAll(db, countSql, params);\n const total = (countRows[0]?.n as number) ?? 0;\n\n // Get limited rows\n const limitSql = `${sql} LIMIT ${maxRows}`;\n const rows = await dbAll(db, limitSql, params);\n return { total, truncated: total > maxRows, rows };\n}\n\n// ---------------------------------------------------------------------------\n// MCP Server\n// ---------------------------------------------------------------------------\n\n/**\n * Server instructions, naming the data release in the client's context.\n *\n * The referral case #348 is about does not go through `list_libraries`:\n * hyrr-mcp sends an agent straight to `get_cross_sections` for full σ(E)\n * curves, having stated the release *it* computed against. Reporting the\n * release only from a tool the agent has no reason to call would leave that\n * claim as unverifiable as it was — so it is stated here too, where it reaches\n * the model without a round trip, and in `list_libraries` where it can be read\n * programmatically. Both read the same loaded catalog, so they cannot disagree.\n *\n * Kept word-for-word in step with the Rust server's `instructions()`; a test\n * pins that the two do not drift.\n */\nexport function instructions(dataVersion: string): string {\n return (\n `Serving nucl-parquet data release ${dataVersion}. This identifies the *data*, ` +\n `not this server's version — report it whenever you compare results with another ` +\n `server or tool. If another source states a different release, say so: agreement ` +\n `or disagreement computed across two releases is an artefact of the mismatch, not ` +\n `a physics result. Call list_libraries to read the same value programmatically.`\n );\n}\n\nconst server = new McpServer(\n {\n name: \"nucl-parquet\",\n // The package version — the software, not the data. Both are reported\n // because they answer different questions, and only one of them changes\n // when the data does.\n version: PKG_VERSION,\n },\n { instructions: instructions(ensureCatalog().data_version) },\n);\n\n// ---------------------------------------------------------------------------\n// Library / cross-section tools\n// ---------------------------------------------------------------------------\n\nserver.tool(\n \"list_libraries\",\n \"List all available nuclear data libraries with projectiles and descriptions\",\n {},\n async () => {\n // An envelope, not a bare array: `data_version` identifies the data\n // *release* these libraries came out of, a different fact from any one\n // library's evaluation `version`, and it has nowhere else to live (#348).\n return {\n content: [\n { type: \"text\" as const, text: safeStringify(libraryListPayload(ensureCatalog()), 2) },\n ],\n };\n },\n);\n\nserver.tool(\n \"list_isotopes\",\n \"List available target elements for a given library and projectile. Returns element symbols.\",\n {\n library: z.string().describe(\"Library ID, e.g. 'tendl-2025', 'endfb-8.1'\"),\n projectile: z.string().describe(\"Projectile: n, p, d, t, h, a, g\"),\n },\n async ({ library, projectile }) => {\n const cat = ensureCatalog();\n const lib = cat.libraries[library];\n if (!lib) throw new Error(`Unknown library: ${library}. Use list_libraries to see available libraries.`);\n if (!lib.projectiles?.includes(projectile)) {\n throw new Error(`Projectile '${projectile}' not available for ${library}. Available: ${lib.projectiles?.join(\", \") ?? \"none\"}`);\n }\n\n const dataDir = resolveDataDir();\n const libPath = lib.path;\n if (!libPath?.endsWith(\"xs/\")) {\n throw new Error(`Library ${library} path does not end with 'xs/' — cannot derive manifest path`);\n }\n const manifestPath = join(dataDir, libPath.replace(\"xs/\", \"manifest.json\"));\n if (!existsSync(manifestPath)) {\n throw new Error(`Manifest not found at ${manifestPath}`);\n }\n const manifest = JSON.parse(readFileSync(manifestPath, \"utf-8\")) as { elements?: string[] };\n const elements = manifest.elements ?? [];\n\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ library, projectile, elements, count: elements.length }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_cross_sections\",\n \"Get nuclear reaction cross-section data for a specific target element. Returns energy (MeV) and cross-section (mb) arrays with reaction product info.\",\n {\n library: z.string().describe(\"Library ID, e.g. 'tendl-2025'\"),\n projectile: z.string().describe(\"Projectile: n, p, d, t, h, a, g\"),\n element: z.string().describe(\"Target element symbol, e.g. 'Cu', 'Fe', 'Au'\"),\n max_rows: z.number().optional().describe(\"Max rows to return (default 500)\"),\n },\n async ({ library, projectile, element, max_rows }) => {\n if (!/^[npdthag]$/.test(projectile)) {\n throw new Error(`Invalid projectile: '${projectile}'. Must be one of: n, p, d, t, h, a, g`);\n }\n if (!/^[A-Z][a-z]?$/.test(element)) {\n throw new Error(`Invalid element symbol: '${element}'. Must be 1-2 letters (e.g. 'Cu', 'Fe')`);\n }\n\n const cat = ensureCatalog();\n const lib = cat.libraries[library];\n if (!lib) throw new Error(`Unknown library: ${library}`);\n\n const dataDir = resolveDataDir();\n const parquetPath = join(dataDir, `${lib.path}${projectile}_${element}.parquet`);\n if (!existsSync(parquetPath)) {\n throw new Error(`No data for ${projectile}_${element} in ${library}`);\n }\n\n const db = getDb();\n const result = await query(\n `SELECT * FROM read_parquet('${parquetPath}')`,\n [],\n max_rows ?? 500,\n );\n\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ library, projectile, element, ...result }, 2),\n }],\n };\n },\n);\n\n// ---------------------------------------------------------------------------\n// Nuclear structure tools (DuckDB views)\n// ---------------------------------------------------------------------------\n\nserver.tool(\n \"get_decay_data\",\n \"Get radioactive decay data (half-lives, decay modes, daughters) for a nuclide or element. Filter by Z and/or A.\",\n {\n z: z.number().optional().describe(\"Atomic number (e.g. 92 for U)\"),\n a: z.number().optional().describe(\"Mass number (e.g. 238)\"),\n },\n async ({ z: zNum, a: aNum }) => {\n if (zNum === undefined && aNum === undefined) {\n throw new Error(\"Provide at least z or a to filter decay data.\");\n }\n const conditions: string[] = [];\n const params: unknown[] = [];\n if (zNum !== undefined) { conditions.push(\"Z = ?\"); params.push(zNum); }\n if (aNum !== undefined) { conditions.push(\"A = ?\"); params.push(aNum); }\n\n const result = await query(\n `SELECT * FROM decay WHERE ${conditions.join(\" AND \")}`,\n params,\n );\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ z: zNum, a: aNum, count: result.total, rows: result.rows }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_abundances\",\n \"Get natural isotope abundances and atomic masses for an element.\",\n {\n z: z.number().describe(\"Atomic number (e.g. 29 for Cu)\"),\n },\n async ({ z: zNum }) => {\n const result = await query(\"SELECT * FROM abundances WHERE Z = ?\", [zNum]);\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ z: zNum, count: result.total, isotopes: result.rows }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_stopping_power\",\n \"Get mass stopping power (dE/dx) data for a projectile in a target element.\",\n {\n source: z.string().describe(\n \"Data source: PSTAR (protons), ASTAR (α, NIST ICRU-49), ESTAR (electrons), dSTAR, tSTAR (velocity-scaled from PSTAR), or catima (full Z×Z table)\",\n ),\n target_z: z.number().describe(\"Target element atomic number\"),\n },\n async ({ source, target_z }) => {\n const viewMap: Record<string, string> = {\n PSTAR: \"stopping\", ASTAR: \"stopping\", ESTAR: \"stopping\",\n dSTAR: \"stopping\", tSTAR: \"stopping\", catima: \"catima_stopping\",\n };\n if (!(source in viewMap)) {\n throw new Error(`Unknown source '${source}'. Valid: PSTAR, ASTAR, ESTAR, dSTAR, tSTAR, catima`);\n }\n const view = viewMap[source];\n const sql = source === \"catima\"\n ? `SELECT * FROM ${view} WHERE target_Z = ?`\n : `SELECT * FROM ${view} WHERE source = ? AND target_Z = ?`;\n const params = source === \"catima\" ? [target_z] : [source, target_z];\n\n const result = await query(sql, params);\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ source, target_z, count: result.total, rows: result.rows }, 2),\n }],\n };\n },\n);\n\n// ---------------------------------------------------------------------------\n// Radiation / coincidence / spectra tools\n// ---------------------------------------------------------------------------\n\nserver.tool(\n \"get_radiation\",\n \"Get radiation emissions (gammas, X-rays, Auger electrons, conversion electrons) for a nuclide.\",\n {\n z: z.number().describe(\"Atomic number of the parent nuclide\"),\n a: z.number().optional().describe(\"Mass number (omit for all isotopes of element Z)\"),\n max_rows: z.number().optional().describe(\"Max rows to return (default 500)\"),\n },\n async ({ z: zNum, a: aNum, max_rows }) => {\n const conditions = [\"Z = ?\"];\n const params: unknown[] = [zNum];\n if (aNum !== undefined) { conditions.push(\"A = ?\"); params.push(aNum); }\n\n const result = await query(\n `SELECT * FROM radiation WHERE ${conditions.join(\" AND \")}`,\n params,\n max_rows ?? 500,\n );\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ z: zNum, a: aNum, ...result }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_coincidences\",\n \"Get gamma-gamma and mixed-emission coincidence pairs for a nuclide. Includes beta/EC/X-ray/Auger/511 keV annihilation paired with gammas.\",\n {\n z: z.number().describe(\"Atomic number of the parent nuclide\"),\n a: z.number().optional().describe(\"Mass number (omit for all isotopes of element Z)\"),\n max_rows: z.number().optional().describe(\"Max rows to return (default 500)\"),\n },\n async ({ z: zNum, a: aNum, max_rows }) => {\n const conditions = [\"Z = ?\"];\n const params: unknown[] = [zNum];\n if (aNum !== undefined) { conditions.push(\"A = ?\"); params.push(aNum); }\n\n const result = await query(\n `SELECT * FROM coincidences WHERE ${conditions.join(\" AND \")}`,\n params,\n max_rows ?? 500,\n );\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ z: zNum, a: aNum, ...result }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_summing_partners\",\n \"Get ICC-corrected summing partners for HPGe true-coincidence-summing (TCS) corrections. Returns emission pairs with pre-computed icc_correction_factor and pure_emission_joint_intensity.\",\n {\n z: z.number().describe(\"Atomic number of the daughter nuclide (filing convention)\"),\n a: z.number().describe(\"Mass number\"),\n primary_energy_keV: z.number().optional().describe(\"Filter to pairs matching this energy on either side\"),\n tolerance_keV: z.number().optional().describe(\"Energy match tolerance in keV (default 0.5)\"),\n emission1_rad_type: z.string().optional().describe(\"Filter side 1: 'gamma', 'xray', 'auger'\"),\n max_rows: z.number().optional().describe(\"Max rows to return (default 500)\"),\n },\n async ({ z: zNum, a: aNum, primary_energy_keV, tolerance_keV, emission1_rad_type, max_rows }) => {\n const conditions = [\"Z = ?\", \"A = ?\"];\n const params: unknown[] = [zNum, aNum];\n const tol = tolerance_keV ?? 0.5;\n\n if (primary_energy_keV !== undefined) {\n conditions.push(\"(ABS(emission1_energy_keV - ?) < ? OR ABS(emission2_energy_keV - ?) < ?)\");\n params.push(primary_energy_keV, tol, primary_energy_keV, tol);\n }\n if (emission1_rad_type !== undefined) {\n conditions.push(\"emission1_rad_type = ?\");\n params.push(emission1_rad_type);\n }\n\n const result = await query(\n `SELECT * FROM summing_partners WHERE ${conditions.join(\" AND \")} ORDER BY pure_emission_joint_intensity DESC`,\n params,\n max_rows ?? 500,\n );\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ z: zNum, a: aNum, primary_energy_keV, ...result }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_emissions\",\n \"Get absolute per-decay photon emission intensities (NuDat-equivalent). Returns gamma emissions for a parent nuclide with absolute intensities (photon emission probability per decay, 0-100%). Filed by parent, not daughter.\",\n {\n parent_z: z.number().describe(\"Atomic number of the decaying parent (e.g. 27 for Co-60)\"),\n parent_a: z.number().describe(\"Mass number of the parent (e.g. 60 for Co-60)\"),\n parent_state: z.string().optional().describe(\"Nuclear state: '' (ground), 'm', 'm2'\"),\n decay_mode: z.string().optional().describe(\"Filter by decay mode: 'beta-', 'KshellEC', 'IT', etc.\"),\n energy_keV: z.number().optional().describe(\"Filter to gammas near this energy\"),\n tolerance_keV: z.number().optional().describe(\"Energy tolerance (default 0.5 keV)\"),\n min_intensity_pct: z.number().optional().describe(\"Minimum absolute intensity (%) to include\"),\n max_rows: z.number().optional().describe(\"Max rows to return (default 500)\"),\n },\n async ({ parent_z, parent_a, parent_state, decay_mode, energy_keV, tolerance_keV, min_intensity_pct, max_rows }) => {\n const conditions = [\"parent_Z = ?\", \"parent_A = ?\", \"parent_state = ?\"];\n const params: unknown[] = [parent_z, parent_a, parent_state ?? \"\"];\n const tol = tolerance_keV ?? 0.5;\n\n if (decay_mode !== undefined) {\n conditions.push(\"decay_mode = ?\");\n params.push(decay_mode);\n }\n if (energy_keV !== undefined) {\n conditions.push(\"ABS(energy_keV - ?) < ?\");\n params.push(energy_keV, tol);\n }\n if (min_intensity_pct !== undefined && min_intensity_pct > 0) {\n conditions.push(\"intensity_pct >= ?\");\n params.push(min_intensity_pct);\n }\n\n const result = await query(\n `SELECT * FROM emissions WHERE ${conditions.join(\" AND \")} ORDER BY intensity_pct DESC`,\n params,\n max_rows ?? 500,\n );\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ parent_z, parent_a, parent_state: parent_state ?? \"\", ...result }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_beta_spectrum\",\n \"Get the continuous beta-decay kinetic-energy spectrum for a nuclide. Returns pre-tabulated Fermi-function spectra (dN/dE normalized to 1).\",\n {\n z: z.number().describe(\"Atomic number of the parent nuclide\"),\n a: z.number().describe(\"Mass number of the parent nuclide\"),\n max_rows: z.number().optional().describe(\"Max rows to return (default 500)\"),\n },\n async ({ z: zNum, a: aNum, max_rows }) => {\n const result = await query(\n \"SELECT * FROM beta_spectra WHERE Z = ? AND A = ?\",\n [zNum, aNum],\n max_rows ?? 500,\n );\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ z: zNum, a: aNum, ...result }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_compound_compositions\",\n \"Get elemental compositions (weight fractions) for NIST XCOM standard materials. Useful for Bragg-additive cross-section calculations.\",\n {\n material: z.string().optional().describe(\"Material name (e.g. 'Water, Liquid'). Omit to list all materials.\"),\n },\n async ({ material }) => {\n const db = getDb();\n if (material === undefined) {\n const rows = await dbAll(db, \"SELECT DISTINCT material FROM compound_compositions ORDER BY material\");\n const materials = rows.map((r) => r.material as string);\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ count: materials.length, materials }, 2),\n }],\n };\n }\n const result = await query(\n \"SELECT * FROM compound_compositions WHERE material = ?\",\n [material],\n );\n if (result.total === 0) {\n const allRows = await dbAll(db, \"SELECT DISTINCT material FROM compound_compositions ORDER BY material\");\n const all = allRows.map((r) => r.material as string);\n throw new Error(`Unknown material: '${material}'. Available: ${all.join(\", \")}`);\n }\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ material, count: result.total, composition: result.rows }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"get_electron_stopping\",\n \"Get electron stopping power with collision/radiative split. Richer than ESTAR — includes ~183 compounds plus all elements Z=1..98.\",\n {\n target: z.string().optional().describe(\"Compound name (e.g. 'G4_WATER'). For elements use target_z instead.\"),\n target_z: z.number().optional().describe(\"Atomic number for elemental targets\"),\n max_rows: z.number().optional().describe(\"Max rows to return (default 500)\"),\n },\n async ({ target, target_z, max_rows }) => {\n if (target === undefined && target_z === undefined) {\n throw new Error(\"Provide target (compound name) or target_z (atomic number).\");\n }\n let result;\n if (target_z !== undefined) {\n result = await query(\n \"SELECT * FROM electron_stopping WHERE target_Z = ?\",\n [target_z],\n max_rows ?? 500,\n );\n } else {\n result = await query(\n \"SELECT * FROM electron_stopping WHERE name = ? OR g4_name = ?\",\n [target, target],\n max_rows ?? 500,\n );\n }\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ target, target_z, ...result }, 2),\n }],\n };\n },\n);\n\n// ---------------------------------------------------------------------------\n// SQL escape hatch (Sub-E #179)\n// ---------------------------------------------------------------------------\n\n// Patterns that indicate file-access functions — blocked in user SQL to prevent\n// read_parquet('/etc/passwd') style attacks. The pre-registered DuckDB views\n// already expose all nuclear data; there's no legitimate need for raw file access.\nconst BLOCKED_FUNCTIONS = /\\b(read_parquet|parquet_scan|parquet_metadata|parquet_schema|read_csv|read_csv_auto|read_json|read_json_auto|read_text|read_blob|glob|copy|export|attach|load|install|create|drop|alter|insert|update|delete|truncate|query_table|pragma)\\b/i;\n\nconst ALLOWED_FIRST_WORDS = new Set([\"SELECT\", \"WITH\", \"EXPLAIN\", \"DESCRIBE\", \"SHOW\", \"SUMMARIZE\"]);\n\nserver.tool(\n \"sql_query\",\n \"Execute read-only SQL against all 70+ nuclear data tables. Supports JOINs, aggregations, window functions. Use describe_schema() to discover available tables and columns.\",\n {\n sql: z.string().describe(\"Read-only SQL query. DDL/DML will be rejected.\"),\n max_rows: z.number().optional().describe(\"Max rows to return (default 10000)\"),\n },\n async ({ sql: userSql, max_rows }) => {\n const stripped = userSql.trim();\n if (!stripped) throw new Error(\"Empty SQL query\");\n\n const firstWord = stripped.split(/\\s/)[0].toUpperCase();\n if (!ALLOWED_FIRST_WORDS.has(firstWord)) {\n throw new Error(`Only read queries are allowed (SELECT, WITH, EXPLAIN, DESCRIBE). Got: ${firstWord}`);\n }\n if (BLOCKED_FUNCTIONS.test(stripped)) {\n throw new Error(\"Only read queries are allowed (SELECT, WITH, EXPLAIN, DESCRIBE). File-access and DDL functions are blocked.\");\n }\n\n const db = getDb();\n const limit = max_rows ?? 10000;\n let total: number;\n let rows: Record<string, unknown>[];\n try {\n // Get total count first, then fetch limited rows\n const countRows = await dbAll(db, `SELECT CAST(COUNT(*) AS INTEGER) AS n FROM (${stripped})`);\n total = (countRows[0]?.n as number) ?? 0;\n rows = await dbAll(db, `SELECT * FROM (${stripped}) LIMIT ${limit}`);\n } catch (e) {\n throw new Error(`SQL error: ${(e as Error).message}`);\n }\n\n const truncated = total > limit;\n const display = rows;\n\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ total, truncated, rows: display }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"describe_schema\",\n \"List all available tables/views with their column names and types. Use this to discover what data is available before writing SQL queries.\",\n {},\n async () => {\n const db = getDb();\n const tables = await dbAll(db, \"SHOW TABLES\");\n const tableNames = tables.map((r) => r.name as string).sort();\n\n const schema: Record<string, { name: string; type: string }[]> = {};\n for (const tbl of tableNames) {\n try {\n const cols = await dbAll(db, `DESCRIBE ${tbl}`);\n schema[tbl] = cols.map((c) => ({ name: c.column_name as string, type: c.column_type as string }));\n } catch {\n schema[tbl] = [];\n }\n }\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ tables: tableNames.length, schema }, 2),\n }],\n };\n },\n);\n\nserver.tool(\n \"list_tables\",\n \"List all available table/view names (short form of describe_schema).\",\n {},\n async () => {\n const db = getDb();\n const tables = await dbAll(db, \"SHOW TABLES\");\n const tableNames = tables.map((r) => r.name as string).sort();\n return {\n content: [{\n type: \"text\" as const,\n text: safeStringify({ count: tableNames.length, tables: tableNames }, 2),\n }],\n };\n },\n);\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n"],"mappings":"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","names":["require","resolve"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nucl-parquet/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for nuclear data —
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "MCP server for nuclear data — wraps local Parquet via DuckDB",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nucl-parquet-mcp": "./dist/index.js"
|
|
@@ -21,8 +21,7 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
24
|
-
"
|
|
25
|
-
"hyparquet-compressors": "^1.1.0",
|
|
24
|
+
"duckdb": "^1.4.4",
|
|
26
25
|
"zod": "^3.23.0"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|