// Lazy download of the @reclaimprotocol/zk-symmetric-crypto circuit resources // the proof engines need (the downloader fetches every engine — stwo, snarkjs, // gnark — as one bundle). These are ~280MB and are NOT fetched in a // `postinstall` (that blocked the MCP server's first launch past the connect // timeout — see the install notes). Instead we fetch them on the first proof, // once, so the server starts immediately and only `run_proof` pays the cost. import { spawn } from 'node:child_process' import { existsSync } from 'node:fs' import { createRequire } from 'node:module' import { dirname, join } from 'node:path' const require = createRequire(import.meta.url) let inFlight: Promise | undefined /** * Ensure the zk circuit resources are on disk before a proof runs. Idempotent * and concurrency-safe: the download runs at most once per process, and a * failed attempt clears the latch so a later proof can retry. * * Resolves silently when the resources are already present (the warm npx cache, * or after a manual `npm run download:zk-files`) and when * `@reclaimprotocol/zk-symmetric-crypto` isn't installed at all (SDK-only * consumers have nothing to prove). * * All output goes to stderr (fd 2): the MCP server speaks JSON-RPC over stdout, * so a stray write to fd 1 mid-session would corrupt the protocol. */ export function ensureZkFiles() { inFlight ??= download().catch((err) => { inFlight = undefined throw err }) return inFlight } async function download() { let mainPath: string try { mainPath = require.resolve('@reclaimprotocol/zk-symmetric-crypto') } catch{ return } // mainPath is /lib/index.js → resources live at /resources, the // downloader at /lib/scripts/download-files.js. Proofs default to the // stwo (WASM) engine, so its resource dir being present is the signal that // the circuits are ready (the downloader fetches all engines together). const libDir = dirname(mainPath) const pkgDir = dirname(libDir) if(existsSync(join(pkgDir, 'resources', 'stwo'))) { return } const downloadScript = join(libDir, 'scripts', 'download-files.js') if(!existsSync(downloadScript)) { throw new Error( 'zk circuit downloader not found in ' + '@reclaimprotocol/zk-symmetric-crypto; ' + 'run `npm run download:zk-files` and retry.', ) } process.stderr.write( '[reclaim-agent] downloading zkTLS proof circuits ' + '(~280MB, one-time)…\n', ) // stdio fd 1→2: send the downloader's stdout to our stderr so its progress // never lands on the MCP server's JSON-RPC stdout. await new Promise((resolve, reject) => { const child = spawn(process.execPath, [downloadScript], { stdio: ['ignore', 2, 2], }) child.on('error', reject) child.on('exit', (code) => { if(code === 0) { resolve() } else { reject(new Error(`zk download exited ${code}`)) } }) }) }