@nifrajs/mcp-db 2.11.0 → 2.12.1

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
@@ -31,6 +31,10 @@ export interface SqliteDatabaseLike {
31
31
  };
32
32
  /** Execute a statement for its side effect (used only for `PRAGMA query_only`). */
33
33
  run(sql: string): unknown;
34
+ /** Optional path this database was opened from; a file-backed one can be reopened read-only in a
35
+ * worker so `run_query` runs off the serving connection. `:memory:` and the empty anonymous
36
+ * database cannot, and run in process. */
37
+ readonly filename?: string;
34
38
  }
35
39
  /** Context forwarded to `authorize` - the inbound HTTP Request carrying the `run_query` call. */
36
40
  export interface McpDbAuthorizeContext {
@@ -49,6 +53,19 @@ export interface RunQueryOptions {
49
53
  readonly maxRows?: number;
50
54
  /** Max serialized result size in bytes (default 100 KB). */
51
55
  readonly maxResultBytes?: number;
56
+ /**
57
+ * Maximum wall-clock time for planning, execution, and optional counting (default 5 seconds).
58
+ * The deadline always bounds the RESPONSE. Whether it also stops the WORK depends on where the
59
+ * query runs: a file-backed database runs it in a reusable read-only worker that is terminated
60
+ * on expiry, while an in-memory database (or a shim with no `filename`) runs it on this thread,
61
+ * where a synchronous statement cannot be preempted and the overrun is only reported after it
62
+ * returns.
63
+ */
64
+ readonly queryTimeoutMs?: number;
65
+ /** Return an exact total after row truncation. Off by default because it re-executes the query. */
66
+ readonly exactTotal?: boolean;
67
+ /** Maximum simultaneous run_query calls on this database connection (default 1). */
68
+ readonly maxConcurrentQueries?: number;
52
69
  }
53
70
  export interface ServeDatabaseAsMcpOptions {
54
71
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAwD,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAEnG,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAA;KAAE,CAAA;IAC9D,mFAAmF;IACnF,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAC1B;AAED,iGAAiG;AACjG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAClF,iDAAiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,4DAA4D;IAC5D,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;IACnC,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CACpC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAwID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,yBAAyB,GACjC,SAAS,CAiOX"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAwD,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAEnG,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAA;KAAE,CAAA;IAC9D,mFAAmF;IACnF,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IACzB;;8CAE0C;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,iGAAiG;AACjG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAClF,iDAAiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,4DAA4D;IAC5D,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,mGAAmG;IACnG,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;CACvC;AAED,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;IACnC,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CACpC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAqaD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,yBAAyB,GACjC,SAAS,CA6PX"}
package/dist/index.js CHANGED
@@ -48,6 +48,130 @@ const stripSqlNoise = (sql) => sql
48
48
  .replace(/"(?:[^"]|"")*"/g, '""')
49
49
  .replace(/--[^\n]*/g, " ")
50
50
  .replace(/\/\*[\s\S]*?\*\//g, " ");
51
+ // SQLite's bare-identifier alphabet. Non-ASCII is included because SQLite treats any byte >= 0x80 as
52
+ // an identifier character; leaving it out would end a word early and split one table name into two.
53
+ const WORD_START = /[A-Za-z_\u0080-\uffff]/;
54
+ const WORD_PART = /[A-Za-z0-9_$\u0080-\uffff]/;
55
+ /**
56
+ * Tokenize far enough to name every relation. A regex cannot do this job: SQLite needs no separator
57
+ * between a keyword and a quoted identifier (`FROM"users"` is legal), and it accepts four identifier
58
+ * quotings - `"x"`, `[x]`, `` `x` ``, and bare. A `\s+`-anchored pattern silently reads none of
59
+ * those as a table reference, which is exactly how an aliased `FROM"users"AS habits` slipped past
60
+ * the allowlist and returned a non-exposed table's rows.
61
+ *
62
+ * String literals and comments become nothing, so a `FROM` inside attacker-controlled text is inert.
63
+ */
64
+ function tokenizeSql(sql) {
65
+ const tokens = [];
66
+ let i = 0;
67
+ /** Read a quoted identifier, collapsing the doubled-delimiter escape SQLite uses for `"` and `` ` ``. */
68
+ const readQuoted = (close, escapable) => {
69
+ let value = "";
70
+ i++; // past the opening delimiter
71
+ while (i < sql.length) {
72
+ if (sql[i] === close) {
73
+ if (escapable && sql[i + 1] === close) {
74
+ value += close;
75
+ i += 2;
76
+ continue;
77
+ }
78
+ i++;
79
+ tokens.push({ kind: "word", value, bare: false });
80
+ return;
81
+ }
82
+ value += sql[i];
83
+ i++;
84
+ }
85
+ // Unterminated - emit what we have. The caller's single-statement and plan checks still run.
86
+ tokens.push({ kind: "word", value, bare: false });
87
+ };
88
+ while (i < sql.length) {
89
+ const char = sql[i];
90
+ if (char === " " || char === "\t" || char === "\n" || char === "\r" || char === "\f") {
91
+ i++;
92
+ }
93
+ else if (char === "-" && sql[i + 1] === "-") {
94
+ const end = sql.indexOf("\n", i);
95
+ i = end === -1 ? sql.length : end + 1;
96
+ }
97
+ else if (char === "/" && sql[i + 1] === "*") {
98
+ const end = sql.indexOf("*/", i + 2);
99
+ i = end === -1 ? sql.length : end + 2;
100
+ }
101
+ else if (char === "'") {
102
+ // Literal: consumed and dropped, so its contents can never be read as SQL.
103
+ i++;
104
+ while (i < sql.length) {
105
+ if (sql[i] === "'") {
106
+ if (sql[i + 1] === "'") {
107
+ i += 2;
108
+ continue;
109
+ }
110
+ i++;
111
+ break;
112
+ }
113
+ i++;
114
+ }
115
+ }
116
+ else if (char === '"') {
117
+ readQuoted('"', true);
118
+ }
119
+ else if (char === "`") {
120
+ readQuoted("`", true);
121
+ }
122
+ else if (char === "[") {
123
+ readQuoted("]", false); // MSSQL-style bracket quoting: no escape form
124
+ }
125
+ else if (WORD_START.test(char)) {
126
+ const start = i;
127
+ while (i < sql.length && WORD_PART.test(sql[i]))
128
+ i++;
129
+ tokens.push({ kind: "word", value: sql.slice(start, i), bare: true });
130
+ }
131
+ else if (char >= "0" && char <= "9") {
132
+ while (i < sql.length && /[0-9A-Za-z._]/.test(sql[i]))
133
+ i++;
134
+ }
135
+ else {
136
+ tokens.push({ kind: "punct", value: char, bare: false });
137
+ i++;
138
+ }
139
+ }
140
+ return tokens;
141
+ }
142
+ /** True when `token` is the unquoted keyword `word` (case-insensitive). */
143
+ const isKeyword = (token, word) => token?.bare === true && token.value.toLowerCase() === word;
144
+ /**
145
+ * Every relation the statement names after `FROM`/`JOIN`, lowercased, with CTE names excluded.
146
+ * A schema qualifier resolves to its table (`main.habits` -> `habits`); a subquery contributes
147
+ * nothing of its own because its inner `FROM` is tokenized alongside everything else.
148
+ */
149
+ function relationNames(sql) {
150
+ const tokens = tokenizeSql(sql);
151
+ // `name AS (` only ever introduces a CTE - a column alias cannot be followed by a paren - so this
152
+ // needs no `WITH` tracking to be exact.
153
+ const ctes = new Set();
154
+ for (let i = 1; i < tokens.length - 1; i++) {
155
+ const name = tokens[i - 1];
156
+ if (isKeyword(tokens[i], "as") && tokens[i + 1]?.value === "(" && name.kind === "word") {
157
+ ctes.add(name.value.toLowerCase());
158
+ }
159
+ }
160
+ const names = [];
161
+ for (let i = 0; i < tokens.length; i++) {
162
+ if (!isKeyword(tokens[i], "from") && !isKeyword(tokens[i], "join"))
163
+ continue;
164
+ const first = tokens[i + 1];
165
+ if (first === undefined || first.kind !== "word")
166
+ continue; // `FROM (subquery)` - nothing to name here
167
+ // A qualified reference names the table second; an unqualified one names it first.
168
+ const qualified = tokens[i + 2]?.value === "." && tokens[i + 3]?.kind === "word";
169
+ const table = qualified ? tokens[i + 3].value : first.value;
170
+ if (table !== "" && !ctes.has(table.toLowerCase()))
171
+ names.push(table.toLowerCase());
172
+ }
173
+ return names;
174
+ }
51
175
  /** Reject multi-statement input: allow one terminator only when it is the final character. */
52
176
  const isSingleStatement = (sql) => {
53
177
  let quote = null;
@@ -162,6 +286,154 @@ function assertResultLimit(value, name) {
162
286
  throw new McpDbConfigError(`${name} must be a finite positive safe integer`);
163
287
  }
164
288
  }
289
+ class QueryTimeoutError extends Error {
290
+ constructor() {
291
+ super("query exceeded the time limit");
292
+ this.name = "QueryTimeoutError";
293
+ }
294
+ }
295
+ /**
296
+ * The isolated lane. The worker reopens the SAME database FILE read-only and answers one statement
297
+ * at a time; it is spawned once and reused for the life of the server. It deliberately does not
298
+ * take a `serialize()` snapshot - that copies the whole database into memory on EVERY call, which
299
+ * is a larger availability problem than the slow query the deadline exists to bound.
300
+ */
301
+ const SQLITE_WORKER_SOURCE = `
302
+ const { Database } = require("bun:sqlite")
303
+ let db
304
+ self.onmessage = (event) => {
305
+ const { id, filename, sql } = event.data
306
+ try {
307
+ if (db === undefined) {
308
+ db = new Database(filename, { readonly: true })
309
+ db.run("PRAGMA query_only = ON")
310
+ }
311
+ self.postMessage({ id, ok: true, rows: db.prepare(sql).all() })
312
+ } catch (error) {
313
+ self.postMessage({ id, ok: false, error: String(error) })
314
+ }
315
+ }
316
+ `;
317
+ function isWorkerAvailable() {
318
+ return typeof Worker !== "undefined" && typeof Blob !== "undefined" && typeof URL !== "undefined";
319
+ }
320
+ /** Only a file-backed database can be reopened in a worker. `:memory:` and the anonymous temp
321
+ * database live in this process alone, so they take the in-process lane instead. */
322
+ function reopenableFilename(db) {
323
+ const filename = db.filename;
324
+ if (typeof filename !== "string" || filename === "" || filename === ":memory:")
325
+ return undefined;
326
+ return filename.startsWith("file::memory:") ? undefined : filename;
327
+ }
328
+ function workerSession(filename) {
329
+ let worker;
330
+ let sourceUrl;
331
+ let nextId = 0;
332
+ const pending = new Map();
333
+ let lane = Promise.resolve();
334
+ /** Drop the worker and fail everything riding on it. The only bound this side can enforce over a
335
+ * statement already running inside native SQLite is to stop owning the connection it runs on. */
336
+ const discard = (error) => {
337
+ const waiters = [...pending.values()];
338
+ pending.clear();
339
+ worker?.terminate();
340
+ if (sourceUrl !== undefined)
341
+ URL.revokeObjectURL(sourceUrl);
342
+ worker = undefined;
343
+ sourceUrl = undefined;
344
+ for (const waiter of waiters)
345
+ waiter.reject(error);
346
+ };
347
+ const ensureWorker = () => {
348
+ if (worker !== undefined)
349
+ return worker;
350
+ sourceUrl = URL.createObjectURL(new Blob([SQLITE_WORKER_SOURCE], { type: "text/javascript" }));
351
+ const spawned = new Worker(sourceUrl);
352
+ spawned.onmessage = (event) => {
353
+ const { id, ok, rows, error } = event.data;
354
+ if (typeof id !== "number")
355
+ return;
356
+ const waiter = pending.get(id);
357
+ if (waiter === undefined)
358
+ return;
359
+ pending.delete(id);
360
+ if (ok === true)
361
+ waiter.resolve(rows ?? []);
362
+ else
363
+ waiter.reject(new Error(error ?? "query failed in worker"));
364
+ };
365
+ spawned.onerror = () => discard(new Error("query failed in worker"));
366
+ spawned.unref?.();
367
+ worker = spawned;
368
+ return spawned;
369
+ };
370
+ const dispatch = (sql, deadline) => {
371
+ const remaining = deadline - Date.now();
372
+ if (remaining <= 0)
373
+ return Promise.reject(new QueryTimeoutError());
374
+ const active = ensureWorker();
375
+ const id = ++nextId;
376
+ return new Promise((resolve, reject) => {
377
+ const timer = setTimeout(() => discard(new QueryTimeoutError()), remaining);
378
+ pending.set(id, {
379
+ resolve: (rows) => {
380
+ clearTimeout(timer);
381
+ resolve(rows);
382
+ },
383
+ reject: (error) => {
384
+ clearTimeout(timer);
385
+ reject(error);
386
+ },
387
+ });
388
+ try {
389
+ active.postMessage({ id, filename, sql });
390
+ }
391
+ catch (error) {
392
+ pending.delete(id);
393
+ clearTimeout(timer);
394
+ reject(error instanceof Error ? error : new Error(String(error)));
395
+ }
396
+ });
397
+ };
398
+ return {
399
+ run(sql, deadline) {
400
+ // One worker owns the connection, so overlapping calls queue behind each other rather than
401
+ // interleaving statements on it.
402
+ const result = lane.then(() => dispatch(sql, deadline));
403
+ lane = result.catch(() => undefined);
404
+ return result;
405
+ },
406
+ };
407
+ }
408
+ /**
409
+ * The in-process lane, used when the database cannot be reopened elsewhere. The deadline is checked
410
+ * around the statement and nothing more: `prepare().all()` is synchronous, so it holds the only
411
+ * thread there is and no timer of ours can fire while it runs. That makes this lane BEST EFFORT -
412
+ * it reports an overrun, it does not cut one short.
413
+ */
414
+ function inProcessSession(db) {
415
+ return {
416
+ async run(sql, deadline) {
417
+ if (Date.now() >= deadline)
418
+ throw new QueryTimeoutError();
419
+ const rows = db.prepare(sql).all();
420
+ if (Date.now() > deadline)
421
+ throw new QueryTimeoutError();
422
+ return rows;
423
+ },
424
+ };
425
+ }
426
+ /**
427
+ * Pick the lane once, at construction. A file-backed database is isolated in a reusable read-only
428
+ * worker, which is the only arrangement that can actually stop a running statement. Anything else
429
+ * runs in process - every database keeps working, none is refused.
430
+ */
431
+ function openQuerySession(db) {
432
+ const filename = reopenableFilename(db);
433
+ if (filename !== undefined && isWorkerAvailable())
434
+ return workerSession(filename);
435
+ return inProcessSession(db);
436
+ }
165
437
  /**
166
438
  * Serve `db` as a mountable MCP server (`mcp.fetch` at `POST /mcp`). See module docs for the
167
439
  * security model. Throws {@link McpDbConfigError} on any unsafe configuration - always at
@@ -187,8 +459,13 @@ export function serveDatabaseAsMcp(db, options) {
187
459
  }
188
460
  const maxRows = options.runQuery?.maxRows ?? 100;
189
461
  const maxResultBytes = options.runQuery?.maxResultBytes ?? 100 * 1024;
462
+ const queryTimeoutMs = options.runQuery?.queryTimeoutMs ?? 5_000;
463
+ const maxConcurrentQueries = options.runQuery?.maxConcurrentQueries ?? 1;
190
464
  assertResultLimit(maxRows, "maxRows");
191
465
  assertResultLimit(maxResultBytes, "maxResultBytes");
466
+ assertResultLimit(queryTimeoutMs, "queryTimeoutMs");
467
+ assertResultLimit(maxConcurrentQueries, "maxConcurrentQueries");
468
+ let activeQueries = 0;
192
469
  const listTables = defineMcpTool({
193
470
  name: "list_tables",
194
471
  description: "List the tables this server exposes, with row counts.",
@@ -223,6 +500,8 @@ export function serveDatabaseAsMcp(db, options) {
223
500
  });
224
501
  const tools = [listTables, describeTable];
225
502
  if (options.runQuery !== undefined) {
503
+ // One lane for the life of the server: picked once here, so the handler below always has it.
504
+ const session = openQuerySession(db);
226
505
  const runQuery = defineMcpTool({
227
506
  name: "run_query",
228
507
  description: "Run one read-only SELECT against the exposed tables. Results are capped by rows and bytes.",
@@ -234,88 +513,124 @@ export function serveDatabaseAsMcp(db, options) {
234
513
  required: ["sql"],
235
514
  },
236
515
  intent: "table",
237
- handler: (args) => {
238
- const sql = String(args.sql ?? "").trim();
239
- if (sql === "")
240
- return { isError: true, text: "empty query" };
241
- if (!isSingleStatement(sql)) {
242
- return { isError: true, text: "only a single statement is allowed" };
243
- }
244
- if (!isReadStatement(sql)) {
245
- return { isError: true, text: "only SELECT (or WITH…SELECT) statements are allowed" };
516
+ handler: async (args) => {
517
+ if (activeQueries >= maxConcurrentQueries) {
518
+ return { isError: true, text: "too many concurrent queries" };
246
519
  }
247
- // Verify via the query plan that only allowlisted tables are touched. SQLite names every
248
- // scanned/searched relation in EXPLAIN QUERY PLAN detail rows.
249
- const query = executableSql(sql);
250
- let planRows;
520
+ activeQueries += 1;
251
521
  try {
252
- planRows = db.prepare(`EXPLAIN QUERY PLAN ${query}`).all();
253
- }
254
- catch (error) {
255
- return { isError: true, text: `query failed to plan: ${String(error)}` };
256
- }
257
- for (const row of planRows) {
258
- const detail = typeof row.detail === "string" ? row.detail : "";
259
- // SQLite also emits non-table nodes such as `SCAN CONSTANT ROW` for a
260
- // constant-only SELECT. Only treat a scan/search target as a relation
261
- // when it is not one of those planner pseudo-nodes.
262
- if (/^SCAN\s+CONSTANT\s+ROW$/i.test(detail.trim()) ||
263
- /^SCAN\s+SUBQUERY\s+\d+$/i.test(detail.trim())) {
264
- continue;
522
+ const sql = String(args.sql ?? "").trim();
523
+ if (sql === "")
524
+ return { isError: true, text: "empty query" };
525
+ if (!isSingleStatement(sql)) {
526
+ return { isError: true, text: "only a single statement is allowed" };
527
+ }
528
+ if (!isReadStatement(sql)) {
529
+ return { isError: true, text: "only SELECT (or WITH…SELECT) statements are allowed" };
265
530
  }
266
- const match = /(?:SCAN|SEARCH)\s+(?:TABLE\s+)?([A-Za-z_][A-Za-z0-9_]*)/i.exec(detail);
267
- if (match !== null) {
268
- const relation = match[1]?.toLowerCase() ?? "";
531
+ const deadline = Date.now() + queryTimeoutMs;
532
+ // Verify SQL relation tokens before the plan check. SQLite's plan output uses aliases as
533
+ // scan targets (e.g. `users AS habits` becomes `SCAN habits`), so plan-only validation can
534
+ // mistake an attacker-controlled alias for an allowlisted table.
535
+ const executable = executableSql(sql);
536
+ for (const relation of relationNames(executable)) {
269
537
  if (!allowlist.has(relation)) {
270
538
  return {
271
539
  isError: true,
272
- text: `query touches ${JSON.stringify(match[1])}, which is not exposed`,
540
+ text: `query touches ${JSON.stringify(relation)}, which is not exposed`,
273
541
  };
274
542
  }
275
543
  }
276
- }
277
- let limitedRows;
278
- try {
279
- // Fetch at most one row beyond the advertised cap. This keeps the database driver's
280
- // materialization bounded even when the caller submits `SELECT * FROM a_huge_table`.
281
- limitedRows = db
282
- .prepare(`SELECT * FROM (${query}) AS "__nifra_result" LIMIT ${maxRows + 1}`)
283
- .all();
284
- }
285
- catch (error) {
286
- return { isError: true, text: `query failed: ${String(error)}` };
287
- }
288
- const wasLimited = limitedRows.length > maxRows;
289
- let total = limitedRows.length;
290
- if (wasLimited) {
544
+ // Verify via the query plan that only allowlisted tables are touched. SQLite names every
545
+ // scanned/searched relation in EXPLAIN QUERY PLAN detail rows.
546
+ const query = executable;
547
+ let planRows;
291
548
  try {
292
- const countRows = db
293
- .prepare(`SELECT count(*) AS "__nifra_total" FROM (${query}) AS "__nifra_count"`)
294
- .all();
295
- const count = countRows[0]?.__nifra_total;
296
- if (typeof count !== "number" || !Number.isSafeInteger(count) || count < maxRows) {
297
- return { isError: true, text: "query failed: invalid row count" };
549
+ planRows = (await session.run(`EXPLAIN QUERY PLAN ${query}`, deadline));
550
+ }
551
+ catch (error) {
552
+ if (error instanceof QueryTimeoutError)
553
+ return { isError: true, text: error.message };
554
+ return { isError: true, text: `query failed to plan: ${String(error)}` };
555
+ }
556
+ for (const row of planRows) {
557
+ const detail = typeof row.detail === "string" ? row.detail : "";
558
+ // SQLite also emits non-table nodes such as `SCAN CONSTANT ROW` for a
559
+ // constant-only SELECT. Only treat a scan/search target as a relation
560
+ // when it is not one of those planner pseudo-nodes.
561
+ if (/^SCAN\s+CONSTANT\s+ROW$/i.test(detail.trim()) ||
562
+ /^SCAN\s+SUBQUERY\s+\d+$/i.test(detail.trim())) {
563
+ continue;
298
564
  }
299
- total = count;
565
+ // A schema-qualified reference plans as `SCAN main.habits`. The table is the last dotted
566
+ // segment; reading the first would reject `main.habits` as a table literally named "main".
567
+ const match = /(?:SCAN|SEARCH)\s+(?:TABLE\s+)?(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)/i.exec(detail);
568
+ if (match !== null) {
569
+ const relation = match[1]?.toLowerCase() ?? "";
570
+ if (!allowlist.has(relation)) {
571
+ return {
572
+ isError: true,
573
+ text: `query touches ${JSON.stringify(match[1])}, which is not exposed`,
574
+ };
575
+ }
576
+ }
577
+ }
578
+ let limitedRows;
579
+ try {
580
+ // Fetch at most one row beyond the advertised cap. This keeps the database driver's
581
+ // materialization bounded even when the caller submits `SELECT * FROM a_huge_table`.
582
+ limitedRows = await session.run(`SELECT * FROM (${query}) AS "__nifra_result" LIMIT ${maxRows + 1}`, deadline);
300
583
  }
301
584
  catch (error) {
302
- return { isError: true, text: `query failed to count results: ${String(error)}` };
585
+ if (error instanceof QueryTimeoutError)
586
+ return { isError: true, text: error.message };
587
+ return { isError: true, text: `query failed: ${String(error)}` };
588
+ }
589
+ const wasLimited = limitedRows.length > maxRows;
590
+ let total = limitedRows.length;
591
+ if (wasLimited && options.runQuery?.exactTotal === true) {
592
+ try {
593
+ const countRows = (await session.run(`SELECT count(*) AS "__nifra_total" FROM (${query}) AS "__nifra_count"`, deadline));
594
+ const count = countRows[0]?.__nifra_total;
595
+ if (typeof count !== "number" || !Number.isSafeInteger(count) || count < maxRows) {
596
+ return { isError: true, text: "query failed: invalid row count" };
597
+ }
598
+ total = count;
599
+ }
600
+ catch (error) {
601
+ if (error instanceof QueryTimeoutError)
602
+ return { isError: true, text: error.message };
603
+ return { isError: true, text: `query failed to count results: ${String(error)}` };
604
+ }
605
+ }
606
+ else if (wasLimited) {
607
+ total = null;
608
+ }
609
+ let shown = Math.min(limitedRows.length, maxRows);
610
+ let payload = limitedRows.slice(0, shown);
611
+ let result = {
612
+ rows: payload,
613
+ ...(wasLimited || shown < limitedRows.length ? { truncated: true, total } : {}),
614
+ };
615
+ let serialized = JSON.stringify(result);
616
+ const encoded = new TextEncoder();
617
+ while (encoded.encode(serialized).byteLength > maxResultBytes && shown > 0) {
618
+ shown = Math.max(0, Math.floor(shown / 2));
619
+ payload = limitedRows.slice(0, shown);
620
+ result = {
621
+ rows: payload,
622
+ ...(wasLimited || shown < limitedRows.length ? { truncated: true, total } : {}),
623
+ };
624
+ serialized = JSON.stringify(result);
625
+ }
626
+ if (encoded.encode(serialized).byteLength > maxResultBytes) {
627
+ return { isError: true, text: "query result exceeds maxResultBytes" };
303
628
  }
629
+ return { text: serialized, structuredContent: result };
304
630
  }
305
- let shown = Math.min(limitedRows.length, maxRows);
306
- let payload = limitedRows.slice(0, shown);
307
- let serialized = JSON.stringify(payload);
308
- while (serialized.length > maxResultBytes && shown > 0) {
309
- shown = Math.max(0, Math.floor(shown / 2));
310
- payload = limitedRows.slice(0, shown);
311
- serialized = JSON.stringify(payload);
631
+ finally {
632
+ activeQueries -= 1;
312
633
  }
313
- const truncated = shown < total;
314
- const result = {
315
- rows: payload,
316
- ...(truncated ? { truncated: { shown, total } } : {}),
317
- };
318
- return { text: JSON.stringify(result), structuredContent: result };
319
634
  },
320
635
  });
321
636
  tools.push(runQuery);
@@ -331,45 +646,21 @@ export function serveDatabaseAsMcp(db, options) {
331
646
  // are visible - the handler itself performs no auth, so every path to it must pass this gate:
332
647
  // the wrapped fetch authorizes, and direct handle() dispatch fails closed (no Request → no auth).
333
648
  const { authorize } = options.runQuery;
334
- const rpcId = (id) => typeof id === "string" || typeof id === "number" ? id : null;
335
- const unauthorizedResult = (id) => ({
336
- jsonrpc: "2.0",
337
- id: rpcId(id),
338
- result: {
339
- content: [{ type: "text", text: "unauthorized" }],
340
- isError: true,
341
- },
342
- });
343
- const isRunQueryCall = (message) => {
344
- if (typeof message !== "object" || message === null)
345
- return false;
346
- const { method, params } = message;
347
- return method === "tools/call" && params?.name === "run_query";
348
- };
349
649
  return {
350
650
  ...server,
351
- async fetch(request) {
352
- if (request.method === "POST") {
353
- let body;
354
- try {
355
- body = await request.clone().json();
356
- }
357
- catch {
358
- return server.fetch(request); // malformed JSON → the protocol layer's error handling
359
- }
360
- const messages = Array.isArray(body) ? body : [body];
361
- const queryCall = messages.find(isRunQueryCall);
362
- if (queryCall !== undefined) {
363
- const ok = await authorize({ toolName: "run_query", request });
364
- if (!ok)
365
- return Response.json(unauthorizedResult(queryCall.id));
366
- }
367
- }
368
- return server.fetch(request);
369
- },
651
+ fetch: (request) => server.fetch(request, {
652
+ authorizeMessage: async (message, inbound) => message.method !== "tools/call" || message.params?.name !== "run_query"
653
+ ? true
654
+ : authorize({ toolName: "run_query", request: inbound }),
655
+ }),
370
656
  async handle(message) {
371
- if (isRunQueryCall(message))
372
- return unauthorizedResult(message.id);
657
+ if (message.method === "tools/call" && message.params?.name === "run_query") {
658
+ return {
659
+ jsonrpc: "2.0",
660
+ id: typeof message.id === "string" || typeof message.id === "number" ? message.id : null,
661
+ result: { content: [{ type: "text", text: "unauthorized" }], isError: true },
662
+ };
663
+ }
373
664
  return server.handle(message);
374
665
  },
375
666
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nifrajs/mcp-db",
3
- "version": "2.11.0",
3
+ "version": "2.12.1",
4
4
  "description": "Serve a SQLite database as a fail-closed MCP server - allowlisted schema tools by default, opt-in read-only queries with plan verification.",
5
5
  "keywords": [
6
6
  "bun",
@@ -35,7 +35,7 @@
35
35
  "access": "public"
36
36
  },
37
37
  "dependencies": {
38
- "@nifrajs/mcp": "2.11.0"
38
+ "@nifrajs/mcp": "2.12.1"
39
39
  },
40
40
  "repository": {
41
41
  "type": "git",
package/src/index.ts CHANGED
@@ -31,6 +31,10 @@ export interface SqliteDatabaseLike {
31
31
  prepare(sql: string): { all(...params: unknown[]): unknown[] }
32
32
  /** Execute a statement for its side effect (used only for `PRAGMA query_only`). */
33
33
  run(sql: string): unknown
34
+ /** Optional path this database was opened from; a file-backed one can be reopened read-only in a
35
+ * worker so `run_query` runs off the serving connection. `:memory:` and the empty anonymous
36
+ * database cannot, and run in process. */
37
+ readonly filename?: string
34
38
  }
35
39
 
36
40
  /** Context forwarded to `authorize` - the inbound HTTP Request carrying the `run_query` call. */
@@ -51,6 +55,19 @@ export interface RunQueryOptions {
51
55
  readonly maxRows?: number
52
56
  /** Max serialized result size in bytes (default 100 KB). */
53
57
  readonly maxResultBytes?: number
58
+ /**
59
+ * Maximum wall-clock time for planning, execution, and optional counting (default 5 seconds).
60
+ * The deadline always bounds the RESPONSE. Whether it also stops the WORK depends on where the
61
+ * query runs: a file-backed database runs it in a reusable read-only worker that is terminated
62
+ * on expiry, while an in-memory database (or a shim with no `filename`) runs it on this thread,
63
+ * where a synchronous statement cannot be preempted and the overrun is only reported after it
64
+ * returns.
65
+ */
66
+ readonly queryTimeoutMs?: number
67
+ /** Return an exact total after row truncation. Off by default because it re-executes the query. */
68
+ readonly exactTotal?: boolean
69
+ /** Maximum simultaneous run_query calls on this database connection (default 1). */
70
+ readonly maxConcurrentQueries?: number
54
71
  }
55
72
 
56
73
  export interface ServeDatabaseAsMcpOptions {
@@ -104,6 +121,131 @@ const stripSqlNoise = (sql: string): string =>
104
121
  .replace(/--[^\n]*/g, " ")
105
122
  .replace(/\/\*[\s\S]*?\*\//g, " ")
106
123
 
124
+ // SQLite's bare-identifier alphabet. Non-ASCII is included because SQLite treats any byte >= 0x80 as
125
+ // an identifier character; leaving it out would end a word early and split one table name into two.
126
+ const WORD_START = /[A-Za-z_\u0080-\uffff]/
127
+ const WORD_PART = /[A-Za-z0-9_$\u0080-\uffff]/
128
+
129
+ /** One lexical unit of SQL. `bare` distinguishes a keyword-capable word from a quoted identifier, so
130
+ * `FROM "from"` reads the second token as a table name and not as another clause. */
131
+ interface SqlToken {
132
+ readonly kind: "word" | "punct"
133
+ /** Identifier text with quoting removed and escapes collapsed; the raw character for punctuation. */
134
+ readonly value: string
135
+ /** True for an unquoted word - only these can be SQL keywords. */
136
+ readonly bare: boolean
137
+ }
138
+
139
+ /**
140
+ * Tokenize far enough to name every relation. A regex cannot do this job: SQLite needs no separator
141
+ * between a keyword and a quoted identifier (`FROM"users"` is legal), and it accepts four identifier
142
+ * quotings - `"x"`, `[x]`, `` `x` ``, and bare. A `\s+`-anchored pattern silently reads none of
143
+ * those as a table reference, which is exactly how an aliased `FROM"users"AS habits` slipped past
144
+ * the allowlist and returned a non-exposed table's rows.
145
+ *
146
+ * String literals and comments become nothing, so a `FROM` inside attacker-controlled text is inert.
147
+ */
148
+ function tokenizeSql(sql: string): SqlToken[] {
149
+ const tokens: SqlToken[] = []
150
+ let i = 0
151
+ /** Read a quoted identifier, collapsing the doubled-delimiter escape SQLite uses for `"` and `` ` ``. */
152
+ const readQuoted = (close: string, escapable: boolean): void => {
153
+ let value = ""
154
+ i++ // past the opening delimiter
155
+ while (i < sql.length) {
156
+ if (sql[i] === close) {
157
+ if (escapable && sql[i + 1] === close) {
158
+ value += close
159
+ i += 2
160
+ continue
161
+ }
162
+ i++
163
+ tokens.push({ kind: "word", value, bare: false })
164
+ return
165
+ }
166
+ value += sql[i]
167
+ i++
168
+ }
169
+ // Unterminated - emit what we have. The caller's single-statement and plan checks still run.
170
+ tokens.push({ kind: "word", value, bare: false })
171
+ }
172
+ while (i < sql.length) {
173
+ const char = sql[i] as string
174
+ if (char === " " || char === "\t" || char === "\n" || char === "\r" || char === "\f") {
175
+ i++
176
+ } else if (char === "-" && sql[i + 1] === "-") {
177
+ const end = sql.indexOf("\n", i)
178
+ i = end === -1 ? sql.length : end + 1
179
+ } else if (char === "/" && sql[i + 1] === "*") {
180
+ const end = sql.indexOf("*/", i + 2)
181
+ i = end === -1 ? sql.length : end + 2
182
+ } else if (char === "'") {
183
+ // Literal: consumed and dropped, so its contents can never be read as SQL.
184
+ i++
185
+ while (i < sql.length) {
186
+ if (sql[i] === "'") {
187
+ if (sql[i + 1] === "'") {
188
+ i += 2
189
+ continue
190
+ }
191
+ i++
192
+ break
193
+ }
194
+ i++
195
+ }
196
+ } else if (char === '"') {
197
+ readQuoted('"', true)
198
+ } else if (char === "`") {
199
+ readQuoted("`", true)
200
+ } else if (char === "[") {
201
+ readQuoted("]", false) // MSSQL-style bracket quoting: no escape form
202
+ } else if (WORD_START.test(char)) {
203
+ const start = i
204
+ while (i < sql.length && WORD_PART.test(sql[i] as string)) i++
205
+ tokens.push({ kind: "word", value: sql.slice(start, i), bare: true })
206
+ } else if (char >= "0" && char <= "9") {
207
+ while (i < sql.length && /[0-9A-Za-z._]/.test(sql[i] as string)) i++
208
+ } else {
209
+ tokens.push({ kind: "punct", value: char, bare: false })
210
+ i++
211
+ }
212
+ }
213
+ return tokens
214
+ }
215
+
216
+ /** True when `token` is the unquoted keyword `word` (case-insensitive). */
217
+ const isKeyword = (token: SqlToken | undefined, word: string): boolean =>
218
+ token?.bare === true && token.value.toLowerCase() === word
219
+
220
+ /**
221
+ * Every relation the statement names after `FROM`/`JOIN`, lowercased, with CTE names excluded.
222
+ * A schema qualifier resolves to its table (`main.habits` -> `habits`); a subquery contributes
223
+ * nothing of its own because its inner `FROM` is tokenized alongside everything else.
224
+ */
225
+ function relationNames(sql: string): string[] {
226
+ const tokens = tokenizeSql(sql)
227
+ // `name AS (` only ever introduces a CTE - a column alias cannot be followed by a paren - so this
228
+ // needs no `WITH` tracking to be exact.
229
+ const ctes = new Set<string>()
230
+ for (let i = 1; i < tokens.length - 1; i++) {
231
+ const name = tokens[i - 1] as SqlToken
232
+ if (isKeyword(tokens[i], "as") && tokens[i + 1]?.value === "(" && name.kind === "word") {
233
+ ctes.add(name.value.toLowerCase())
234
+ }
235
+ }
236
+ const names: string[] = []
237
+ for (let i = 0; i < tokens.length; i++) {
238
+ if (!isKeyword(tokens[i], "from") && !isKeyword(tokens[i], "join")) continue
239
+ const first = tokens[i + 1]
240
+ if (first === undefined || first.kind !== "word") continue // `FROM (subquery)` - nothing to name here
241
+ // A qualified reference names the table second; an unqualified one names it first.
242
+ const qualified = tokens[i + 2]?.value === "." && tokens[i + 3]?.kind === "word"
243
+ const table = qualified ? (tokens[i + 3] as SqlToken).value : first.value
244
+ if (table !== "" && !ctes.has(table.toLowerCase())) names.push(table.toLowerCase())
245
+ }
246
+ return names
247
+ }
248
+
107
249
  /** Reject multi-statement input: allow one terminator only when it is the final character. */
108
250
  const isSingleStatement = (sql: string): boolean => {
109
251
  let quote: "'" | '"' | null = null
@@ -215,6 +357,166 @@ function assertResultLimit(value: number, name: string): void {
215
357
  }
216
358
  }
217
359
 
360
+ class QueryTimeoutError extends Error {
361
+ constructor() {
362
+ super("query exceeded the time limit")
363
+ this.name = "QueryTimeoutError"
364
+ }
365
+ }
366
+
367
+ /** One execution lane for `run_query`: `run` resolves rows, or rejects once the deadline passes. */
368
+ interface QuerySession {
369
+ run(sql: string, deadline: number): Promise<unknown[]>
370
+ }
371
+
372
+ /**
373
+ * The isolated lane. The worker reopens the SAME database FILE read-only and answers one statement
374
+ * at a time; it is spawned once and reused for the life of the server. It deliberately does not
375
+ * take a `serialize()` snapshot - that copies the whole database into memory on EVERY call, which
376
+ * is a larger availability problem than the slow query the deadline exists to bound.
377
+ */
378
+ const SQLITE_WORKER_SOURCE = `
379
+ const { Database } = require("bun:sqlite")
380
+ let db
381
+ self.onmessage = (event) => {
382
+ const { id, filename, sql } = event.data
383
+ try {
384
+ if (db === undefined) {
385
+ db = new Database(filename, { readonly: true })
386
+ db.run("PRAGMA query_only = ON")
387
+ }
388
+ self.postMessage({ id, ok: true, rows: db.prepare(sql).all() })
389
+ } catch (error) {
390
+ self.postMessage({ id, ok: false, error: String(error) })
391
+ }
392
+ }
393
+ `
394
+
395
+ function isWorkerAvailable(): boolean {
396
+ return typeof Worker !== "undefined" && typeof Blob !== "undefined" && typeof URL !== "undefined"
397
+ }
398
+
399
+ /** Only a file-backed database can be reopened in a worker. `:memory:` and the anonymous temp
400
+ * database live in this process alone, so they take the in-process lane instead. */
401
+ function reopenableFilename(db: SqliteDatabaseLike): string | undefined {
402
+ const filename = db.filename
403
+ if (typeof filename !== "string" || filename === "" || filename === ":memory:") return undefined
404
+ return filename.startsWith("file::memory:") ? undefined : filename
405
+ }
406
+
407
+ interface PendingQuery {
408
+ resolve(rows: unknown[]): void
409
+ reject(error: Error): void
410
+ }
411
+
412
+ function workerSession(filename: string): QuerySession {
413
+ let worker: Worker | undefined
414
+ let sourceUrl: string | undefined
415
+ let nextId = 0
416
+ const pending = new Map<number, PendingQuery>()
417
+ let lane: Promise<unknown> = Promise.resolve()
418
+
419
+ /** Drop the worker and fail everything riding on it. The only bound this side can enforce over a
420
+ * statement already running inside native SQLite is to stop owning the connection it runs on. */
421
+ const discard = (error: Error): void => {
422
+ const waiters = [...pending.values()]
423
+ pending.clear()
424
+ worker?.terminate()
425
+ if (sourceUrl !== undefined) URL.revokeObjectURL(sourceUrl)
426
+ worker = undefined
427
+ sourceUrl = undefined
428
+ for (const waiter of waiters) waiter.reject(error)
429
+ }
430
+
431
+ const ensureWorker = (): Worker => {
432
+ if (worker !== undefined) return worker
433
+ sourceUrl = URL.createObjectURL(new Blob([SQLITE_WORKER_SOURCE], { type: "text/javascript" }))
434
+ const spawned = new Worker(sourceUrl)
435
+ spawned.onmessage = (
436
+ event: MessageEvent<{ id?: number; ok?: boolean; rows?: unknown[]; error?: string }>,
437
+ ) => {
438
+ const { id, ok, rows, error } = event.data
439
+ if (typeof id !== "number") return
440
+ const waiter = pending.get(id)
441
+ if (waiter === undefined) return
442
+ pending.delete(id)
443
+ if (ok === true) waiter.resolve(rows ?? [])
444
+ else waiter.reject(new Error(error ?? "query failed in worker"))
445
+ }
446
+ spawned.onerror = () => discard(new Error("query failed in worker"))
447
+ // An in-flight query holds its own deadline timer on the loop, so an IDLE worker must not be
448
+ // what keeps the process alive.
449
+ ;(spawned as { unref?: () => void }).unref?.()
450
+ worker = spawned
451
+ return spawned
452
+ }
453
+
454
+ const dispatch = (sql: string, deadline: number): Promise<unknown[]> => {
455
+ const remaining = deadline - Date.now()
456
+ if (remaining <= 0) return Promise.reject(new QueryTimeoutError())
457
+ const active = ensureWorker()
458
+ const id = ++nextId
459
+ return new Promise<unknown[]>((resolve, reject) => {
460
+ const timer = setTimeout(() => discard(new QueryTimeoutError()), remaining)
461
+ pending.set(id, {
462
+ resolve: (rows) => {
463
+ clearTimeout(timer)
464
+ resolve(rows)
465
+ },
466
+ reject: (error) => {
467
+ clearTimeout(timer)
468
+ reject(error)
469
+ },
470
+ })
471
+ try {
472
+ active.postMessage({ id, filename, sql })
473
+ } catch (error) {
474
+ pending.delete(id)
475
+ clearTimeout(timer)
476
+ reject(error instanceof Error ? error : new Error(String(error)))
477
+ }
478
+ })
479
+ }
480
+
481
+ return {
482
+ run(sql, deadline) {
483
+ // One worker owns the connection, so overlapping calls queue behind each other rather than
484
+ // interleaving statements on it.
485
+ const result = lane.then(() => dispatch(sql, deadline))
486
+ lane = result.catch(() => undefined)
487
+ return result
488
+ },
489
+ }
490
+ }
491
+
492
+ /**
493
+ * The in-process lane, used when the database cannot be reopened elsewhere. The deadline is checked
494
+ * around the statement and nothing more: `prepare().all()` is synchronous, so it holds the only
495
+ * thread there is and no timer of ours can fire while it runs. That makes this lane BEST EFFORT -
496
+ * it reports an overrun, it does not cut one short.
497
+ */
498
+ function inProcessSession(db: SqliteDatabaseLike): QuerySession {
499
+ return {
500
+ async run(sql, deadline) {
501
+ if (Date.now() >= deadline) throw new QueryTimeoutError()
502
+ const rows = db.prepare(sql).all()
503
+ if (Date.now() > deadline) throw new QueryTimeoutError()
504
+ return rows
505
+ },
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Pick the lane once, at construction. A file-backed database is isolated in a reusable read-only
511
+ * worker, which is the only arrangement that can actually stop a running statement. Anything else
512
+ * runs in process - every database keeps working, none is refused.
513
+ */
514
+ function openQuerySession(db: SqliteDatabaseLike): QuerySession {
515
+ const filename = reopenableFilename(db)
516
+ if (filename !== undefined && isWorkerAvailable()) return workerSession(filename)
517
+ return inProcessSession(db)
518
+ }
519
+
218
520
  /**
219
521
  * Serve `db` as a mountable MCP server (`mcp.fetch` at `POST /mcp`). See module docs for the
220
522
  * security model. Throws {@link McpDbConfigError} on any unsafe configuration - always at
@@ -250,8 +552,13 @@ export function serveDatabaseAsMcp(
250
552
 
251
553
  const maxRows = options.runQuery?.maxRows ?? 100
252
554
  const maxResultBytes = options.runQuery?.maxResultBytes ?? 100 * 1024
555
+ const queryTimeoutMs = options.runQuery?.queryTimeoutMs ?? 5_000
556
+ const maxConcurrentQueries = options.runQuery?.maxConcurrentQueries ?? 1
253
557
  assertResultLimit(maxRows, "maxRows")
254
558
  assertResultLimit(maxResultBytes, "maxResultBytes")
559
+ assertResultLimit(queryTimeoutMs, "queryTimeoutMs")
560
+ assertResultLimit(maxConcurrentQueries, "maxConcurrentQueries")
561
+ let activeQueries = 0
255
562
 
256
563
  const listTables = defineMcpTool({
257
564
  name: "list_tables",
@@ -292,6 +599,8 @@ export function serveDatabaseAsMcp(
292
599
  const tools = [listTables, describeTable]
293
600
 
294
601
  if (options.runQuery !== undefined) {
602
+ // One lane for the life of the server: picked once here, so the handler below always has it.
603
+ const session = openQuerySession(db)
295
604
  const runQuery = defineMcpTool({
296
605
  name: "run_query",
297
606
  description:
@@ -304,89 +613,133 @@ export function serveDatabaseAsMcp(
304
613
  required: ["sql"],
305
614
  },
306
615
  intent: "table",
307
- handler: (args) => {
308
- const sql = String(args.sql ?? "").trim()
309
- if (sql === "") return { isError: true, text: "empty query" }
310
- if (!isSingleStatement(sql)) {
311
- return { isError: true, text: "only a single statement is allowed" }
616
+ handler: async (args) => {
617
+ if (activeQueries >= maxConcurrentQueries) {
618
+ return { isError: true, text: "too many concurrent queries" }
312
619
  }
313
- if (!isReadStatement(sql)) {
314
- return { isError: true, text: "only SELECT (or WITH…SELECT) statements are allowed" }
315
- }
316
-
317
- // Verify via the query plan that only allowlisted tables are touched. SQLite names every
318
- // scanned/searched relation in EXPLAIN QUERY PLAN detail rows.
319
- const query = executableSql(sql)
320
- let planRows: Array<{ detail?: unknown }>
620
+ activeQueries += 1
321
621
  try {
322
- planRows = db.prepare(`EXPLAIN QUERY PLAN ${query}`).all() as Array<{ detail?: unknown }>
323
- } catch (error) {
324
- return { isError: true, text: `query failed to plan: ${String(error)}` }
325
- }
326
- for (const row of planRows) {
327
- const detail = typeof row.detail === "string" ? row.detail : ""
328
- // SQLite also emits non-table nodes such as `SCAN CONSTANT ROW` for a
329
- // constant-only SELECT. Only treat a scan/search target as a relation
330
- // when it is not one of those planner pseudo-nodes.
331
- if (
332
- /^SCAN\s+CONSTANT\s+ROW$/i.test(detail.trim()) ||
333
- /^SCAN\s+SUBQUERY\s+\d+$/i.test(detail.trim())
334
- ) {
335
- continue
622
+ const sql = String(args.sql ?? "").trim()
623
+ if (sql === "") return { isError: true, text: "empty query" }
624
+ if (!isSingleStatement(sql)) {
625
+ return { isError: true, text: "only a single statement is allowed" }
336
626
  }
337
- const match = /(?:SCAN|SEARCH)\s+(?:TABLE\s+)?([A-Za-z_][A-Za-z0-9_]*)/i.exec(detail)
338
- if (match !== null) {
339
- const relation = match[1]?.toLowerCase() ?? ""
627
+ if (!isReadStatement(sql)) {
628
+ return { isError: true, text: "only SELECT (or WITH…SELECT) statements are allowed" }
629
+ }
630
+
631
+ const deadline = Date.now() + queryTimeoutMs
632
+
633
+ // Verify SQL relation tokens before the plan check. SQLite's plan output uses aliases as
634
+ // scan targets (e.g. `users AS habits` becomes `SCAN habits`), so plan-only validation can
635
+ // mistake an attacker-controlled alias for an allowlisted table.
636
+ const executable = executableSql(sql)
637
+ for (const relation of relationNames(executable)) {
340
638
  if (!allowlist.has(relation)) {
341
639
  return {
342
640
  isError: true,
343
- text: `query touches ${JSON.stringify(match[1])}, which is not exposed`,
641
+ text: `query touches ${JSON.stringify(relation)}, which is not exposed`,
344
642
  }
345
643
  }
346
644
  }
347
- }
348
-
349
- let limitedRows: unknown[]
350
- try {
351
- // Fetch at most one row beyond the advertised cap. This keeps the database driver's
352
- // materialization bounded even when the caller submits `SELECT * FROM a_huge_table`.
353
- limitedRows = db
354
- .prepare(`SELECT * FROM (${query}) AS "__nifra_result" LIMIT ${maxRows + 1}`)
355
- .all()
356
- } catch (error) {
357
- return { isError: true, text: `query failed: ${String(error)}` }
358
- }
359
645
 
360
- const wasLimited = limitedRows.length > maxRows
361
- let total = limitedRows.length
362
- if (wasLimited) {
646
+ // Verify via the query plan that only allowlisted tables are touched. SQLite names every
647
+ // scanned/searched relation in EXPLAIN QUERY PLAN detail rows.
648
+ const query = executable
649
+ let planRows: Array<{ detail?: unknown }>
363
650
  try {
364
- const countRows = db
365
- .prepare(`SELECT count(*) AS "__nifra_total" FROM (${query}) AS "__nifra_count"`)
366
- .all() as Array<{ __nifra_total?: unknown }>
367
- const count = countRows[0]?.__nifra_total
368
- if (typeof count !== "number" || !Number.isSafeInteger(count) || count < maxRows) {
369
- return { isError: true, text: "query failed: invalid row count" }
651
+ planRows = (await session.run(`EXPLAIN QUERY PLAN ${query}`, deadline)) as Array<{
652
+ detail?: unknown
653
+ }>
654
+ } catch (error) {
655
+ if (error instanceof QueryTimeoutError) return { isError: true, text: error.message }
656
+ return { isError: true, text: `query failed to plan: ${String(error)}` }
657
+ }
658
+ for (const row of planRows) {
659
+ const detail = typeof row.detail === "string" ? row.detail : ""
660
+ // SQLite also emits non-table nodes such as `SCAN CONSTANT ROW` for a
661
+ // constant-only SELECT. Only treat a scan/search target as a relation
662
+ // when it is not one of those planner pseudo-nodes.
663
+ if (
664
+ /^SCAN\s+CONSTANT\s+ROW$/i.test(detail.trim()) ||
665
+ /^SCAN\s+SUBQUERY\s+\d+$/i.test(detail.trim())
666
+ ) {
667
+ continue
370
668
  }
371
- total = count
669
+ // A schema-qualified reference plans as `SCAN main.habits`. The table is the last dotted
670
+ // segment; reading the first would reject `main.habits` as a table literally named "main".
671
+ const match =
672
+ /(?:SCAN|SEARCH)\s+(?:TABLE\s+)?(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)/i.exec(
673
+ detail,
674
+ )
675
+ if (match !== null) {
676
+ const relation = match[1]?.toLowerCase() ?? ""
677
+ if (!allowlist.has(relation)) {
678
+ return {
679
+ isError: true,
680
+ text: `query touches ${JSON.stringify(match[1])}, which is not exposed`,
681
+ }
682
+ }
683
+ }
684
+ }
685
+
686
+ let limitedRows: unknown[]
687
+ try {
688
+ // Fetch at most one row beyond the advertised cap. This keeps the database driver's
689
+ // materialization bounded even when the caller submits `SELECT * FROM a_huge_table`.
690
+ limitedRows = await session.run(
691
+ `SELECT * FROM (${query}) AS "__nifra_result" LIMIT ${maxRows + 1}`,
692
+ deadline,
693
+ )
372
694
  } catch (error) {
373
- return { isError: true, text: `query failed to count results: ${String(error)}` }
695
+ if (error instanceof QueryTimeoutError) return { isError: true, text: error.message }
696
+ return { isError: true, text: `query failed: ${String(error)}` }
374
697
  }
698
+
699
+ const wasLimited = limitedRows.length > maxRows
700
+ let total: number | null = limitedRows.length
701
+ if (wasLimited && options.runQuery?.exactTotal === true) {
702
+ try {
703
+ const countRows = (await session.run(
704
+ `SELECT count(*) AS "__nifra_total" FROM (${query}) AS "__nifra_count"`,
705
+ deadline,
706
+ )) as Array<{ __nifra_total?: unknown }>
707
+ const count = countRows[0]?.__nifra_total
708
+ if (typeof count !== "number" || !Number.isSafeInteger(count) || count < maxRows) {
709
+ return { isError: true, text: "query failed: invalid row count" }
710
+ }
711
+ total = count
712
+ } catch (error) {
713
+ if (error instanceof QueryTimeoutError) return { isError: true, text: error.message }
714
+ return { isError: true, text: `query failed to count results: ${String(error)}` }
715
+ }
716
+ } else if (wasLimited) {
717
+ total = null
718
+ }
719
+ let shown = Math.min(limitedRows.length, maxRows)
720
+ let payload = limitedRows.slice(0, shown)
721
+ let result: Record<string, unknown> = {
722
+ rows: payload,
723
+ ...(wasLimited || shown < limitedRows.length ? { truncated: true, total } : {}),
724
+ }
725
+ let serialized = JSON.stringify(result)
726
+ const encoded = new TextEncoder()
727
+ while (encoded.encode(serialized).byteLength > maxResultBytes && shown > 0) {
728
+ shown = Math.max(0, Math.floor(shown / 2))
729
+ payload = limitedRows.slice(0, shown)
730
+ result = {
731
+ rows: payload,
732
+ ...(wasLimited || shown < limitedRows.length ? { truncated: true, total } : {}),
733
+ }
734
+ serialized = JSON.stringify(result)
735
+ }
736
+ if (encoded.encode(serialized).byteLength > maxResultBytes) {
737
+ return { isError: true, text: "query result exceeds maxResultBytes" }
738
+ }
739
+ return { text: serialized, structuredContent: result }
740
+ } finally {
741
+ activeQueries -= 1
375
742
  }
376
- let shown = Math.min(limitedRows.length, maxRows)
377
- let payload = limitedRows.slice(0, shown)
378
- let serialized = JSON.stringify(payload)
379
- while (serialized.length > maxResultBytes && shown > 0) {
380
- shown = Math.max(0, Math.floor(shown / 2))
381
- payload = limitedRows.slice(0, shown)
382
- serialized = JSON.stringify(payload)
383
- }
384
- const truncated = shown < total
385
- const result: Record<string, unknown> = {
386
- rows: payload,
387
- ...(truncated ? { truncated: { shown, total } } : {}),
388
- }
389
- return { text: JSON.stringify(result), structuredContent: result }
390
743
  },
391
744
  })
392
745
  tools.push(runQuery)
@@ -405,46 +758,23 @@ export function serveDatabaseAsMcp(
405
758
  // the wrapped fetch authorizes, and direct handle() dispatch fails closed (no Request → no auth).
406
759
  const { authorize } = options.runQuery
407
760
 
408
- const rpcId = (id: unknown): string | number | null =>
409
- typeof id === "string" || typeof id === "number" ? id : null
410
-
411
- const unauthorizedResult = (id: unknown): JsonRpcResponse =>
412
- ({
413
- jsonrpc: "2.0",
414
- id: rpcId(id),
415
- result: {
416
- content: [{ type: "text", text: "unauthorized" }],
417
- isError: true,
418
- },
419
- }) as JsonRpcResponse
420
-
421
- const isRunQueryCall = (message: unknown): message is { id?: unknown } => {
422
- if (typeof message !== "object" || message === null) return false
423
- const { method, params } = message as { method?: unknown; params?: { name?: unknown } }
424
- return method === "tools/call" && params?.name === "run_query"
425
- }
426
-
427
761
  return {
428
762
  ...server,
429
- async fetch(request: Request): Promise<Response> {
430
- if (request.method === "POST") {
431
- let body: unknown
432
- try {
433
- body = await request.clone().json()
434
- } catch {
435
- return server.fetch(request) // malformed JSON → the protocol layer's error handling
436
- }
437
- const messages = Array.isArray(body) ? body : [body]
438
- const queryCall = messages.find(isRunQueryCall)
439
- if (queryCall !== undefined) {
440
- const ok = await authorize({ toolName: "run_query", request })
441
- if (!ok) return Response.json(unauthorizedResult(queryCall.id))
442
- }
443
- }
444
- return server.fetch(request)
445
- },
763
+ fetch: (request: Request) =>
764
+ server.fetch(request, {
765
+ authorizeMessage: async (message, inbound) =>
766
+ message.method !== "tools/call" || message.params?.name !== "run_query"
767
+ ? true
768
+ : authorize({ toolName: "run_query", request: inbound }),
769
+ }),
446
770
  async handle(message) {
447
- if (isRunQueryCall(message)) return unauthorizedResult(message.id)
771
+ if (message.method === "tools/call" && message.params?.name === "run_query") {
772
+ return {
773
+ jsonrpc: "2.0",
774
+ id: typeof message.id === "string" || typeof message.id === "number" ? message.id : null,
775
+ result: { content: [{ type: "text", text: "unauthorized" }], isError: true },
776
+ } as JsonRpcResponse
777
+ }
448
778
  return server.handle(message)
449
779
  },
450
780
  }