@nucl-parquet/mcp 0.14.0 → 0.15.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 CHANGED
@@ -1,5 +1,3 @@
1
- import duckdb from 'duckdb';
2
-
3
1
  /**
4
2
  * nucl-parquet MCP server — wraps local data via DuckDB.
5
3
  *
@@ -7,8 +5,8 @@ import duckdb from 'duckdb';
7
5
  * DuckDB-backed Parquet views, not a re-implementation of parquet I/O.
8
6
  * All data is read from the local data directory — no HTTP fetching.
9
7
  */
10
-
11
- declare const PKG_VERSION: string;
8
+ import duckdb from "duckdb";
9
+ export declare const PKG_VERSION: string;
12
10
  interface Library {
13
11
  name: string;
14
12
  description: string;
@@ -41,7 +39,7 @@ interface Catalog {
41
39
  views?: Record<string, ViewDef>;
42
40
  [key: string]: unknown;
43
41
  }
44
- declare function ensureCatalog(): Catalog;
42
+ export declare function ensureCatalog(): Catalog;
45
43
  /**
46
44
  * Parse and validate a catalog.
47
45
  *
@@ -49,7 +47,7 @@ declare function ensureCatalog(): Catalog;
49
47
  * singleton, which a test cannot re-enter — so the validation below would be
50
48
  * unreachable from a test and, in practice, unverified.
51
49
  */
52
- declare function parseCatalog(raw: string, dataDir: string): Catalog;
50
+ export declare function parseCatalog(raw: string, dataDir: string): Catalog;
53
51
  /**
54
52
  * The `list_libraries` payload: the data release, then the libraries.
55
53
  *
@@ -57,11 +55,11 @@ declare function parseCatalog(raw: string, dataDir: string): Catalog;
57
55
  * the tool actually returns. Asserting only that the catalog *has* a
58
56
  * data_version would pass while the tool still returned a bare array.
59
57
  */
60
- declare function libraryListPayload(cat: Catalog): {
58
+ export declare function libraryListPayload(cat: Catalog): {
61
59
  data_version: string;
62
60
  libraries: Array<Record<string, unknown>>;
63
61
  };
64
- declare function getDb(): duckdb.Database;
62
+ export declare function getDb(): duckdb.Database;
65
63
  /**
66
64
  * Server instructions, naming the data release in the client's context.
67
65
  *
@@ -76,6 +74,7 @@ declare function getDb(): duckdb.Database;
76
74
  * Kept word-for-word in step with the Rust server's `instructions()`; a test
77
75
  * pins that the two do not drift.
78
76
  */
79
- declare function instructions(dataVersion: string): string;
80
-
81
- export { PKG_VERSION, ensureCatalog, getDb, instructions, libraryListPayload, parseCatalog };
77
+ export declare function instructions(dataVersion: string): string;
78
+ export declare const BLOCKED_FUNCTIONS: RegExp;
79
+ export declare const ALLOWED_FIRST_WORDS: Set<string>;
80
+ export {};
package/dist/index.js CHANGED
@@ -109,11 +109,20 @@ function registerViews(db, dataDir) {
109
109
  db.run("CREATE VIEW fluorescence AS SELECT * FROM atomic_relaxation WHERE transition_type = 'radiative'");
110
110
  }
111
111
  }
112
+ function harden(db, dataDir) {
113
+ const quoted = dataDir.replaceAll("'", "''");
114
+ db.run(`SET allowed_directories = ['${quoted}']`);
115
+ db.run("SET enable_external_access = false");
116
+ db.run("SET autoinstall_known_extensions = false");
117
+ db.run("SET autoload_known_extensions = false");
118
+ db.run("SET lock_configuration = true");
119
+ }
112
120
  function getDb() {
113
121
  if (_db) return _db;
114
122
  const dataDir = resolveDataDir();
115
123
  const db = new duckdb.Database(":memory:");
116
124
  registerViews(db, dataDir);
125
+ harden(db, dataDir);
117
126
  _db = db;
118
127
  return db;
119
128
  }
@@ -484,7 +493,7 @@ server.tool(
484
493
  "get_compound_compositions",
485
494
  "Get elemental compositions (weight fractions) for NIST XCOM standard materials. Useful for Bragg-additive cross-section calculations.",
486
495
  {
487
- material: z.string().optional().describe("Material name (e.g. 'Water, Liquid'). Omit to list all materials.")
496
+ material: z.string().optional().describe("Material key (e.g. 'water', 'air', 'concrete'). Omit to list all materials.")
488
497
  },
489
498
  async ({ material }) => {
490
499
  const db = getDb();
@@ -633,6 +642,8 @@ server.tool(
633
642
  var transport = new StdioServerTransport();
634
643
  await server.connect(transport);
635
644
  export {
645
+ ALLOWED_FIRST_WORDS,
646
+ BLOCKED_FUNCTIONS,
636
647
  PKG_VERSION,
637
648
  ensureCatalog,
638
649
  getDb,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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"]}
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\n/**\n * Confine the connection to reading the data tree, then freeze its settings.\n *\n * `sql_query` hands callers a SQL prompt, and its keyword blocklist cannot list\n * every DuckDB function that opens a file: `sniff_csv('/etc/passwd')` and\n * `read_json_objects(...)` both got past it and read outside the data tree. The\n * connection itself must not be able to, whatever the SQL says:\n *\n * - `allowed_directories` first, because the views read Parquet files, which is\n * itself external access; without the exception every tool fails.\n * - `enable_external_access = false`: no other files, no network, no INSTALL.\n * - extension auto-install and auto-load off.\n * - `lock_configuration` last, so a caller cannot `SET` any of it back.\n *\n * Queued on the same connection after `registerViews`, so it takes effect\n * before the first caller query.\n */\nfunction harden(db: duckdb.Database, dataDir: string): void {\n const quoted = dataDir.replaceAll(\"'\", \"''\");\n db.run(`SET allowed_directories = ['${quoted}']`);\n db.run(\"SET enable_external_access = false\");\n db.run(\"SET autoinstall_known_extensions = false\");\n db.run(\"SET autoload_known_extensions = false\");\n db.run(\"SET lock_configuration = true\");\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 harden(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 key (e.g. 'water', 'air', 'concrete'). 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.\nexport const 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\nexport const 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,6 +1,6 @@
1
1
  {
2
2
  "name": "@nucl-parquet/mcp",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "MCP server for nuclear data — wraps local Parquet via DuckDB",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  "dist"
13
13
  ],
14
14
  "scripts": {
15
- "build": "tsup",
15
+ "build": "tsup && tsc --emitDeclarationOnly",
16
16
  "test": "vitest run",
17
17
  "test:watch": "vitest",
18
18
  "start": "node dist/index.js"
@@ -20,14 +20,14 @@
20
20
  "repository": "https://github.com/exoma-ch/nucl-parquet",
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
- "@modelcontextprotocol/sdk": "^1.12.0",
23
+ "@modelcontextprotocol/sdk": "^1.30.1",
24
24
  "duckdb": "^1.4.4",
25
- "zod": "^3.23.0"
25
+ "zod": "^4.6.5"
26
26
  },
27
27
  "devDependencies": {
28
- "@types/node": "^22.0.0",
28
+ "@types/node": "^26.6.3",
29
29
  "tsup": "^8.0.0",
30
- "typescript": "^5.4.0",
31
- "vitest": "^2.0.0"
30
+ "typescript": "^7.0.2",
31
+ "vitest": "^5.0.2"
32
32
  }
33
33
  }