db-mcp 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/.gitleaks.toml +9 -0
  2. package/.trivyignore +8 -0
  3. package/README.md +193 -120
  4. package/dist/{chunk-DWJXQEFA.js → chunk-5Y42NPBP.js} +4168 -2411
  5. package/dist/{chunk-AOUL5SHS.js → chunk-645ZEFLA.js} +70 -20
  6. package/dist/chunk-OKOVZ5QE.js +28 -0
  7. package/dist/chunk-SFJQCNG7.js +131 -0
  8. package/dist/{chunk-S5IDDSSB.js → chunk-VIDSICEL.js} +12 -1
  9. package/dist/chunk-WBER5YY4.js +2053 -0
  10. package/dist/{chunk-4IA3DB5C.js → chunk-X3MUUOWM.js} +19 -2
  11. package/dist/{chunk-Z2GFQU3G.js → chunk-Z7C2TM4L.js} +114 -21
  12. package/dist/cli.js +79 -5
  13. package/dist/{http-VSB7DBJR.js → http-6KF4ULDI.js} +199 -77
  14. package/dist/index.d.ts +231 -8
  15. package/dist/index.js +6 -5
  16. package/dist/{sqlite-VHBA4ABV.js → sqlite-U5KSYQXK.js} +63 -140
  17. package/dist/{sqlite-native-D2LH5ZT7.js → sqlite-native-JXMCFQBA.js} +576 -114
  18. package/dist/worker-script.js +34 -10
  19. package/logs/.gitkeep +1 -0
  20. package/mcp-config-example.json +83 -0
  21. package/package.json +9 -8
  22. package/playwright.config.ts +1 -1
  23. package/scripts/update-badges.ts +99 -0
  24. package/server.json +7 -5
  25. package/test-server/README.md +20 -23
  26. package/test-server/code-map.md +45 -33
  27. package/test-server/reset-database.ps1 +59 -17
  28. package/test-server/scripts/README.md +27 -0
  29. package/test-server/{test-help-resources.mjs → scripts/test-help-resources.mjs} +12 -5
  30. package/test-server/scripts/test-prompts.mjs +251 -0
  31. package/test-server/{test-tool-annotations.mjs → scripts/test-tool-annotations.mjs} +9 -4
  32. package/test-server/test-advanced/README.md +70 -0
  33. package/test-server/test-advanced/test-codemode-advanced-admin.md +174 -0
  34. package/test-server/test-advanced/test-codemode-advanced-core.md +193 -0
  35. package/test-server/test-advanced/test-codemode-advanced-geo.md +157 -0
  36. package/test-server/test-advanced/test-codemode-advanced-introspection.md +171 -0
  37. package/test-server/test-advanced/test-codemode-advanced-json.md +169 -0
  38. package/test-server/test-advanced/test-codemode-advanced-migration.md +160 -0
  39. package/test-server/test-advanced/test-codemode-advanced-stats.md +185 -0
  40. package/test-server/test-advanced/test-codemode-advanced-text.md +183 -0
  41. package/test-server/test-advanced/test-codemode-advanced-transactions.md +136 -0
  42. package/test-server/test-advanced/test-codemode-advanced-vector.md +141 -0
  43. package/test-server/test-codemode/README.md +121 -0
  44. package/test-server/test-codemode/test-codemode-admin.md +223 -0
  45. package/test-server/test-codemode/test-codemode-core.md +286 -0
  46. package/test-server/test-codemode/test-codemode-geo.md +177 -0
  47. package/test-server/test-codemode/test-codemode-introspection.md +190 -0
  48. package/test-server/test-codemode/test-codemode-json.md +237 -0
  49. package/test-server/test-codemode/test-codemode-migration.md +278 -0
  50. package/test-server/test-codemode/test-codemode-sandbox.md +413 -0
  51. package/test-server/test-codemode/test-codemode-stats.md +232 -0
  52. package/test-server/test-codemode/test-codemode-text.md +237 -0
  53. package/test-server/test-codemode/test-codemode-transactions.md +236 -0
  54. package/test-server/test-codemode/test-codemode-vector.md +244 -0
  55. package/test-server/test-codemode/test-codemode-wasm-degradation.md +394 -0
  56. package/test-server/test-database.sql +37 -1
  57. package/test-server/test-resources.md +43 -16
  58. package/test-server/test-tool-groups/README.md +100 -0
  59. package/test-server/test-tool-groups/test-admin-core.md +165 -0
  60. package/test-server/test-tool-groups/test-admin-extensions.md +133 -0
  61. package/test-server/{test-tools.md → test-tool-groups/test-core-data.md} +103 -17
  62. package/test-server/test-tool-groups/test-core-schema.md +240 -0
  63. package/test-server/test-tool-groups/test-geo-haversine.md +130 -0
  64. package/test-server/test-tool-groups/test-geo-spatialite.md +110 -0
  65. package/test-server/test-tool-groups/test-introspection-diagnostics.md +123 -0
  66. package/test-server/test-tool-groups/test-introspection-schema.md +133 -0
  67. package/test-server/test-tool-groups/test-json-read.md +219 -0
  68. package/test-server/test-tool-groups/test-json-write.md +157 -0
  69. package/test-server/test-tool-groups/test-migration.md +193 -0
  70. package/test-server/test-tool-groups/test-stats-advanced.md +118 -0
  71. package/test-server/test-tool-groups/test-stats-basic.md +156 -0
  72. package/test-server/test-tool-groups/test-text-advanced.md +169 -0
  73. package/test-server/test-tool-groups/test-text-basic.md +177 -0
  74. package/test-server/test-tool-groups/test-transactions.md +179 -0
  75. package/test-server/test-tool-groups/test-vector-read.md +130 -0
  76. package/test-server/test-tool-groups/test-vector-write.md +115 -0
  77. package/test-server/tool-reference.md +79 -58
  78. package/tsconfig.build.json +6 -0
  79. package/dist/chunk-DZQLDEQS.js +0 -879
  80. package/test-server/test-agent-experience.md +0 -243
  81. package/test-server/test-group-tools.md +0 -861
  82. package/test-server/test-tools-advanced-1.md +0 -517
  83. package/test-server/test-tools-advanced-2.md +0 -487
  84. package/test-server/test-tools-codemode.md +0 -629
@@ -1,5 +1,6 @@
1
- import './chunk-4IA3DB5C.js';
2
- import { createModuleLogger, DbMcpError, ERROR_CODES } from './chunk-AOUL5SHS.js';
1
+ import { runWithAuthContext, SUPPORTED_SCOPES, scopesGrantToolAccess, BASE_SCOPES, SCOPE_PATTERNS, parseScopes } from './chunk-SFJQCNG7.js';
2
+ import './chunk-X3MUUOWM.js';
3
+ import { createModuleLogger, DbMcpError, ERROR_CODES } from './chunk-645ZEFLA.js';
3
4
  import express from 'express';
4
5
  import { localhostHostValidation } from '@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js';
5
6
  import cors from 'cors';
@@ -88,6 +89,20 @@ var InvalidSignatureError = class extends OAuthError {
88
89
  this.name = "InvalidSignatureError";
89
90
  }
90
91
  };
92
+ var InsufficientScopeError = class extends OAuthError {
93
+ constructor(requiredScope, providedScopes) {
94
+ const required = Array.isArray(requiredScope) ? requiredScope : [requiredScope];
95
+ const scopeValue = required.join(" ");
96
+ super(
97
+ `Insufficient scope. Required: ${scopeValue}`,
98
+ ERROR_CODES.AUTH.SCOPE_DENIED.full,
99
+ 403,
100
+ { requiredScope: required, providedScopes },
101
+ `Bearer error="insufficient_scope", scope="${scopeValue}"`
102
+ );
103
+ this.name = "InsufficientScopeError";
104
+ }
105
+ };
91
106
  var AuthServerDiscoveryError = class extends OAuthError {
92
107
  constructor(serverUrl, cause) {
93
108
  super(
@@ -120,60 +135,6 @@ function isOAuthError(error) {
120
135
  return error instanceof OAuthError;
121
136
  }
122
137
 
123
- // src/auth/scopes/constants.ts
124
- var SCOPES = {
125
- /** Read-only access to all databases */
126
- READ: "read",
127
- /** Read and write access to all databases */
128
- WRITE: "write",
129
- /** Administrative access */
130
- ADMIN: "admin"};
131
- var BASE_SCOPES = ["read", "write", "admin", "full"];
132
- var SCOPE_PATTERNS = {
133
- /** Database-specific access pattern */
134
- DATABASE: /^db:([a-zA-Z0-9_-]+)$/,
135
- /** Table-specific access pattern */
136
- TABLE: /^table:([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)$/
137
- };
138
- var SUPPORTED_SCOPES = [
139
- "read",
140
- "write",
141
- "admin",
142
- "full",
143
- "db:{database}",
144
- "table:{database}:{table}"
145
- ];
146
- function parseScopes(scopeString) {
147
- return scopeString.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0);
148
- }
149
-
150
- // src/auth/scopes/mapping.ts
151
- var TOOL_GROUP_SCOPES = {
152
- core: SCOPES.READ,
153
- json: SCOPES.READ,
154
- text: SCOPES.READ,
155
- stats: SCOPES.READ,
156
- vector: SCOPES.READ,
157
- geo: SCOPES.READ,
158
- introspection: SCOPES.READ,
159
- migration: SCOPES.WRITE,
160
- admin: SCOPES.ADMIN,
161
- codemode: SCOPES.ADMIN
162
- };
163
- var groupsForScope = (maxScope) => {
164
- const hierarchy = {
165
- read: 0,
166
- write: 1,
167
- admin: 2,
168
- full: 3
169
- };
170
- const maxLevel = hierarchy[maxScope];
171
- return Object.entries(TOOL_GROUP_SCOPES).filter(([, scope]) => hierarchy[scope] <= maxLevel).map(([group]) => group);
172
- };
173
- groupsForScope(SCOPES.READ);
174
- groupsForScope(SCOPES.WRITE);
175
- groupsForScope(SCOPES.ADMIN);
176
-
177
138
  // src/auth/middleware/express-auth.ts
178
139
  var logger = createModuleLogger("AUTH");
179
140
  function isPublicPath(path, publicPaths) {
@@ -412,6 +373,30 @@ function asServerResponse(res) {
412
373
 
413
374
  // src/transports/http/session.ts
414
375
  var logger3 = createModuleLogger("HTTP");
376
+ var Mutex = class {
377
+ queue = [];
378
+ locked = false;
379
+ async acquire() {
380
+ return new Promise((resolve) => {
381
+ const lock = () => {
382
+ this.locked = true;
383
+ resolve(() => {
384
+ this.locked = false;
385
+ if (this.queue.length > 0) {
386
+ const next = this.queue.shift();
387
+ next?.();
388
+ }
389
+ });
390
+ };
391
+ if (this.locked) {
392
+ this.queue.push(lock);
393
+ } else {
394
+ lock();
395
+ }
396
+ });
397
+ }
398
+ };
399
+ var connectionMutex = new Mutex();
415
400
  async function setupStatelessEndpoints(state) {
416
401
  if (!state.app || !state.mcpServer) {
417
402
  throw new DbMcpError(
@@ -514,16 +499,47 @@ function setupStatefulEndpoints(state) {
514
499
  state.transports.delete(sid);
515
500
  }
516
501
  };
517
- try {
518
- await server.connect(
519
- newTransport
520
- );
521
- } catch {
522
- await server.close();
523
- await server.connect(
524
- newTransport
525
- );
526
- }
502
+ await connectionMutex.acquire().then(async (release) => {
503
+ try {
504
+ if (server.server.transport) {
505
+ const oldTransport = server.server.transport;
506
+ logger3.info("Captured oldTransport in /mcp", {
507
+ hasOldTransport: oldTransport !== void 0,
508
+ hasOnclose: oldTransport.onclose !== void 0
509
+ });
510
+ try {
511
+ await Promise.race([
512
+ server.close(),
513
+ new Promise(
514
+ (_, reject) => setTimeout(
515
+ () => reject(new Error("server.close timeout")),
516
+ 2e3
517
+ )
518
+ )
519
+ ]);
520
+ logger3.info("server.close() succeeded in /mcp");
521
+ } catch (closeErr) {
522
+ logger3.error("server.close() failed or timed out in /mcp", {
523
+ error: closeErr instanceof Error ? closeErr : new Error(String(closeErr))
524
+ });
525
+ }
526
+ if (server.server.transport === oldTransport && oldTransport.onclose !== void 0) {
527
+ logger3.info(
528
+ "Manually invoking oldTransport.onclose() to clear Protocol state in /mcp"
529
+ );
530
+ oldTransport.onclose();
531
+ }
532
+ delete oldTransport.onclose;
533
+ }
534
+ logger3.info("Attempting server.connect(newTransport)");
535
+ await server.connect(
536
+ newTransport
537
+ );
538
+ logger3.info("server.connect succeeded in /mcp");
539
+ } finally {
540
+ release();
541
+ }
542
+ });
527
543
  await newTransport.handleRequest(
528
544
  asIncoming(req),
529
545
  asServerResponse(res),
@@ -622,16 +638,59 @@ function setupLegacySSEEndpoints(state) {
622
638
  });
623
639
  state.sseTransports.delete(sseTransport.sessionId);
624
640
  };
625
- try {
626
- await server.connect(
627
- sseTransport
628
- );
629
- } catch {
630
- await server.close();
631
- await server.connect(
632
- sseTransport
633
- );
634
- }
641
+ const origSend = sseTransport.send.bind(sseTransport);
642
+ sseTransport.send = async (message) => {
643
+ try {
644
+ logger3.info("SSE SEND", {
645
+ sessionId: sseTransport.sessionId,
646
+ msg: JSON.stringify(message).substring(0, 1e3)
647
+ });
648
+ } catch (e) {
649
+ logger3.error("SSE SEND Stringify Error", {
650
+ error: e instanceof Error ? e : new Error(String(e))
651
+ });
652
+ }
653
+ return origSend(message);
654
+ };
655
+ await connectionMutex.acquire().then(async (release) => {
656
+ try {
657
+ if (server.server.transport) {
658
+ const oldTransport = server.server.transport;
659
+ logger3.info("Captured oldTransport", {
660
+ hasOldTransport: oldTransport !== void 0,
661
+ hasOnclose: oldTransport.onclose !== void 0
662
+ });
663
+ try {
664
+ await Promise.race([
665
+ server.close(),
666
+ new Promise(
667
+ (_, reject) => setTimeout(
668
+ () => reject(new Error("server.close timeout")),
669
+ 2e3
670
+ )
671
+ )
672
+ ]);
673
+ logger3.info("server.close() succeeded");
674
+ } catch (closeErr) {
675
+ logger3.error("server.close() failed or timed out", {
676
+ error: closeErr instanceof Error ? closeErr : new Error(String(closeErr))
677
+ });
678
+ }
679
+ if (server.server.transport === oldTransport && oldTransport.onclose !== void 0) {
680
+ logger3.info(
681
+ "Manually invoking oldTransport.onclose() to clear Protocol state"
682
+ );
683
+ oldTransport.onclose();
684
+ }
685
+ delete oldTransport.onclose;
686
+ }
687
+ logger3.info("Attempting server.connect(sseTransport)");
688
+ await server.connect(sseTransport);
689
+ logger3.info("server.connect succeeded");
690
+ } finally {
691
+ release();
692
+ }
693
+ });
635
694
  } catch (error) {
636
695
  logger3.error("Error starting SSE transport", {
637
696
  code: ERROR_CODES.SERVER.TRANSPORT_ERROR.full,
@@ -675,7 +734,13 @@ function setupLegacySSEEndpoints(state) {
675
734
  asIncoming(req),
676
735
  asServerResponse(res),
677
736
  req.body
678
- );
737
+ ).then(() => {
738
+ logger3.info("handlePostMessage completed", { sessionId });
739
+ }).catch((e) => {
740
+ logger3.error("handlePostMessage error", {
741
+ error: e instanceof Error ? e : new Error(String(e))
742
+ });
743
+ });
679
744
  });
680
745
  }
681
746
 
@@ -1256,6 +1321,48 @@ function createSimpleBearerAuth(expectedToken) {
1256
1321
  next();
1257
1322
  };
1258
1323
  }
1324
+ function applyScopeEnforcementMiddleware(state) {
1325
+ if (!state.app) return;
1326
+ state.app.use((req, res, next) => {
1327
+ const body = req.body;
1328
+ if (req.method !== "POST" || body?.method !== "tools/call") {
1329
+ next();
1330
+ return;
1331
+ }
1332
+ const toolName = body.params?.name;
1333
+ if (!toolName) {
1334
+ next();
1335
+ return;
1336
+ }
1337
+ if (!req.auth) {
1338
+ next();
1339
+ return;
1340
+ }
1341
+ const hasAccess = scopesGrantToolAccess(req.auth.scopes, toolName);
1342
+ if (!hasAccess) {
1343
+ const error = new InsufficientScopeError(
1344
+ `Tool access: ${toolName}`,
1345
+ req.auth.scopes
1346
+ );
1347
+ logger7.warning(`Insufficient scope for tool: ${toolName}`, {
1348
+ code: ERROR_CODES.AUTH.SCOPE_DENIED.full,
1349
+ toolName,
1350
+ providedScopes: req.auth.scopes
1351
+ });
1352
+ res.status(error.httpStatus);
1353
+ if (error.wwwAuthenticate) {
1354
+ res.setHeader("WWW-Authenticate", error.wwwAuthenticate);
1355
+ }
1356
+ res.json({
1357
+ error: "insufficient_scope",
1358
+ error_description: `Access to tool '${toolName}' denied`,
1359
+ tool: toolName
1360
+ });
1361
+ return;
1362
+ }
1363
+ next();
1364
+ });
1365
+ }
1259
1366
  async function setupOAuth(state, resourceUri) {
1260
1367
  logger7.info("Setting up OAuth 2.1...", { code: "HTTP_OAUTH_SETUP" });
1261
1368
  state.resourceServer = new OAuthResourceServer({
@@ -1382,6 +1489,21 @@ var HttpTransport = class {
1382
1489
  });
1383
1490
  });
1384
1491
  applyAuthMiddleware(this.state);
1492
+ applyScopeEnforcementMiddleware(this.state);
1493
+ this.state.app.use((req, _res, next) => {
1494
+ if (req.auth) {
1495
+ const authCtx = {
1496
+ authenticated: true,
1497
+ claims: req.auth,
1498
+ scopes: req.auth.scopes
1499
+ };
1500
+ runWithAuthContext(authCtx, () => {
1501
+ next();
1502
+ });
1503
+ } else {
1504
+ next();
1505
+ }
1506
+ });
1385
1507
  if (this.state.config.stateless) {
1386
1508
  await setupStatelessEndpoints(this.state);
1387
1509
  } else {
package/dist/index.d.ts CHANGED
@@ -174,6 +174,67 @@ interface RequestContext {
174
174
  progressToken?: string | number;
175
175
  }
176
176
 
177
+ /** Audit log configuration */
178
+ interface AuditConfig {
179
+ /** Master switch — false means no interceptor is created */
180
+ enabled: boolean;
181
+ /** Absolute path to the JSONL output file */
182
+ logPath: string;
183
+ /** When true, tool arguments are omitted from entries */
184
+ redact: boolean;
185
+ /** When true, read-scoped tools are also logged (default: false) */
186
+ auditReads: boolean;
187
+ /** Maximum log file size in bytes before rotation (default: 10MB). 0 = no rotation. */
188
+ maxSizeBytes: number;
189
+ /** Pre-mutation backup configuration (optional — backup disabled when absent). */
190
+ backup?: BackupConfig | undefined;
191
+ }
192
+ /** Pre-mutation backup configuration */
193
+ interface BackupConfig {
194
+ /** Enable pre-mutation snapshots */
195
+ enabled: boolean;
196
+ /** Include sample data rows in snapshots (default: schema-only) */
197
+ includeData: boolean;
198
+ /** Maximum snapshot age in days before cleanup (default: 30) */
199
+ maxAgeDays: number;
200
+ /** Maximum number of snapshots to retain (default: 1000) */
201
+ maxCount: number;
202
+ /** Maximum table size in bytes for data capture (default: 50MB). Tables exceeding this get DDL-only snapshots. */
203
+ maxDataSizeBytes: number;
204
+ }
205
+ /** Snapshot metadata stored alongside the DDL capture */
206
+ interface SnapshotMetadata {
207
+ /** ISO 8601 timestamp */
208
+ timestamp: string;
209
+ /** Tool that triggered the snapshot */
210
+ tool: string;
211
+ /** Target object (table, index, view) */
212
+ target: string;
213
+ /** Snapshot type: ddl-only or ddl+data */
214
+ type: "ddl" | "ddl+data";
215
+ /** Original audit requestId for correlation */
216
+ requestId: string;
217
+ /** Size of snapshot file in bytes */
218
+ sizeBytes: number;
219
+ /** Snapshot filename (populated by listSnapshots for getSnapshot lookup) */
220
+ filename?: string;
221
+ /** Approximate row count at snapshot time */
222
+ rowCount?: number;
223
+ /** Whether data capture was skipped due to size exceeding threshold */
224
+ dataSkipped?: boolean;
225
+ /** Reason data capture was skipped */
226
+ dataSkippedReason?: string;
227
+ }
228
+ /** Stored snapshot file content */
229
+ interface SnapshotContent {
230
+ /** Snapshot metadata */
231
+ metadata: SnapshotMetadata;
232
+ /** CREATE TABLE / CREATE INDEX / CREATE VIEW DDL from sqlite_master */
233
+ ddl: string;
234
+ /** Optional INSERT statements for sample data */
235
+ data?: string | undefined;
236
+ }
237
+
177
238
  /**
178
239
  * db-mcp — MCP Server Types
179
240
  *
@@ -214,6 +275,8 @@ interface McpServerConfig {
214
275
  */
215
276
  statelessHttp?: boolean;
216
277
  enableHSTS?: boolean;
278
+ /** Audit logging configuration */
279
+ audit?: AuditConfig;
217
280
  }
218
281
 
219
282
  /**
@@ -224,7 +287,7 @@ interface McpServerConfig {
224
287
  /**
225
288
  * Tool group identifiers
226
289
  */
227
- type ToolGroup = "core" | "json" | "text" | "stats" | "vector" | "admin" | "geo" | "introspection" | "migration" | "codemode";
290
+ type ToolGroup = "core" | "json" | "text" | "stats" | "vector" | "admin" | "transactions" | "geo" | "introspection" | "migration" | "codemode";
228
291
  /**
229
292
  * Meta-group identifiers for common multi-group selections.
230
293
  * These are shortcuts that expand to multiple ToolGroups.
@@ -328,7 +391,7 @@ interface ToolDefinition {
328
391
  /** Tool group for filtering */
329
392
  group: ToolGroup;
330
393
  /** Zod schema for input validation */
331
- inputSchema: unknown;
394
+ inputSchema?: unknown;
332
395
  /** Zod schema for output validation (MCP 2025-11-25) */
333
396
  outputSchema?: unknown;
334
397
  /** Required OAuth scopes */
@@ -524,6 +587,134 @@ declare class AuthorizationError extends DbMcpError {
524
587
  });
525
588
  }
526
589
 
590
+ /**
591
+ * db-mcp — Backup Manager
592
+ *
593
+ * Pre-mutation snapshot capture for the audit trail.
594
+ * Creates DDL snapshots (+ optional data) of database objects
595
+ * before write/admin tools modify them. Snapshots are stored
596
+ * as gzip-compressed JSON files in a `snapshots/` directory
597
+ * alongside the audit log.
598
+ *
599
+ * DDL is captured from sqlite_master (`SELECT sql FROM sqlite_master`).
600
+ * Volume metadata uses `COUNT(*)` and page-count pragmas.
601
+ *
602
+ * Non-throwing by design: snapshot failures log to stderr
603
+ * but never block tool execution.
604
+ */
605
+
606
+ /**
607
+ * Interface for database queries needed by the backup manager.
608
+ * Avoids circular imports from the full adapter.
609
+ */
610
+ interface SnapshotQueryAdapter {
611
+ executeQuery(sql: string, params?: unknown[]): Promise<{
612
+ rows?: Record<string, unknown>[];
613
+ }>;
614
+ }
615
+ declare class BackupManager {
616
+ readonly config: BackupConfig;
617
+ private readonly snapshotDir;
618
+ private dirEnsured;
619
+ private readonly pendingWrites;
620
+ constructor(config: BackupConfig, auditLogPath: string);
621
+ /**
622
+ * Check if a tool should receive a pre-mutation snapshot.
623
+ */
624
+ shouldSnapshot(toolName: string): boolean;
625
+ /**
626
+ * Create a pre-mutation snapshot of the target object.
627
+ *
628
+ * @returns Relative path to the snapshot file, or undefined if skipped/failed
629
+ */
630
+ createSnapshot(toolName: string, args: Record<string, unknown>, requestId: string, adapter: SnapshotQueryAdapter, logAs?: string): Promise<string | undefined>;
631
+ /**
632
+ * List available snapshots with metadata.
633
+ */
634
+ listSnapshots(): Promise<SnapshotMetadata[]>;
635
+ /**
636
+ * Read a specific snapshot by filename.
637
+ */
638
+ getSnapshot(filename: string): Promise<SnapshotContent | null>;
639
+ /**
640
+ * Apply retention policy — delete oldest snapshots that exceed limits.
641
+ */
642
+ cleanup(): Promise<number>;
643
+ /**
644
+ * Flush all pending async snapshot writes.
645
+ * Call during graceful shutdown to ensure all snapshots are persisted.
646
+ */
647
+ flush(): Promise<void>;
648
+ getStats(): Promise<{
649
+ count: number;
650
+ oldestAge?: string;
651
+ totalSizeKB: number;
652
+ }>;
653
+ private captureObjectSnapshot;
654
+ /**
655
+ * Build a DDL string from sqlite_master for the given object.
656
+ * Works for tables, views, indexes, and triggers.
657
+ */
658
+ private buildDdl;
659
+ /**
660
+ * Capture row count using COUNT(*).
661
+ * Near-zero cost for small tables; failures are silently ignored (best-effort).
662
+ */
663
+ private captureVolumeMetadata;
664
+ /**
665
+ * Capture row data as INSERT statements, subject to config limits.
666
+ * Returns empty output when `includeData` is disabled.
667
+ */
668
+ private captureTableData;
669
+ private writeSnapshot;
670
+ /**
671
+ * Read and decompress a snapshot file (supports both gzip and legacy JSON).
672
+ */
673
+ private readSnapshotFile;
674
+ private ensureDirectory;
675
+ }
676
+
677
+ /**
678
+ * db-mcp — Audit Interceptor
679
+ *
680
+ * Wraps tool execution to produce audit entries for all tool
681
+ * invocations. Write/admin tools are always logged; read-scoped
682
+ * tools are logged only when `--audit-reads` is enabled.
683
+ *
684
+ * Each entry includes a `tokenEstimate` (~4 bytes per token)
685
+ * computed from the serialized result size.
686
+ *
687
+ * When a BackupManager is provided, captures pre-mutation
688
+ * snapshots of target objects before destructive tool execution.
689
+ *
690
+ * The interceptor is injected into the registration layer
691
+ * so that all tool handlers are audited without per-handler changes.
692
+ *
693
+ * OAuth identity (`user`/`scopes`) is read from AsyncLocalStorage
694
+ * via `getAuthContext()`. When OAuth is configured, the HTTP
695
+ * transport binds the validated auth context before MCP dispatch.
696
+ * When OAuth is not configured (stdio, no auth), fields are `null`/`[]`.
697
+ */
698
+
699
+ /**
700
+ * Audit interceptor interface — used by the registration layer.
701
+ */
702
+ interface AuditInterceptor {
703
+ /**
704
+ * Wrap a tool invocation with audit logging.
705
+ * Returns the tool result unchanged; re-throws any errors.
706
+ *
707
+ * @param toolName MCP tool name
708
+ * @param args Tool input arguments
709
+ * @param requestId Request ID from RequestContext
710
+ * @param fn The actual tool handler to execute
711
+ * @param options Optional configuration, such as overriding the recorded tool name
712
+ */
713
+ around<T>(toolName: string, args: unknown, requestId: string, fn: () => Promise<T>, options?: {
714
+ logAs?: string;
715
+ }): Promise<T>;
716
+ }
717
+
527
718
  /**
528
719
  * db-mcp - Database Adapter Interface
529
720
  *
@@ -611,6 +802,20 @@ declare abstract class DatabaseAdapter {
611
802
  * Get supported tool groups for this adapter
612
803
  */
613
804
  abstract getSupportedToolGroups(): ToolGroup[];
805
+ protected auditInterceptor: AuditInterceptor | null;
806
+ protected backupManager: BackupManager | null;
807
+ /**
808
+ * Inject the audit interceptor.
809
+ */
810
+ setAuditInterceptor(interceptor: AuditInterceptor): void;
811
+ /**
812
+ * Inject the backup manager for tools to use natively.
813
+ */
814
+ setBackupManager(manager: BackupManager): void;
815
+ /**
816
+ * Get the audit interceptor (used by registration layer and Code Mode).
817
+ */
818
+ getAuditInterceptor(): AuditInterceptor | null;
614
819
  /**
615
820
  * Get all tool definitions for this adapter
616
821
  */
@@ -696,6 +901,10 @@ declare class DbMcpServer {
696
901
  private adapters;
697
902
  private toolFilter;
698
903
  private config;
904
+ private auditLogger;
905
+ private backupManager;
906
+ private auditInterceptor;
907
+ private auditInitPromise;
699
908
  constructor(config: McpServerConfig);
700
909
  /**
701
910
  * Register a database adapter
@@ -735,6 +944,19 @@ declare class DbMcpServer {
735
944
  * Gracefully shut down the server
736
945
  */
737
946
  shutdown(): Promise<void>;
947
+ /**
948
+ * Initialize the audit subsystem: logger, interceptor, backup manager,
949
+ * sqlite://audit resource, and audit backup tools.
950
+ */
951
+ private initializeAudit;
952
+ /**
953
+ * Register the sqlite://audit resource for agent access to audit log.
954
+ */
955
+ private registerAuditResource;
956
+ /**
957
+ * Register audit backup tools for snapshot management.
958
+ */
959
+ private registerAuditBackupTools;
738
960
  }
739
961
  /**
740
962
  * Create and configure a db-mcp server instance
@@ -751,17 +973,18 @@ declare const DEFAULT_CONFIG: Partial<McpServerConfig>;
751
973
  * Defines the tool groups and meta-groups used for filtering.
752
974
  *
753
975
  * Actual tool groups (from code audit):
754
- * core: 9 tools (core/queries.ts, core/tables.ts, core/indexes.ts)
755
- * json: 23 tools (json-operations/crud+query+transform.ts, json-helpers/read+write.ts)
756
- * text: 13 WASM / 17 Native (text/regex+formatting+search+validate.ts, fts.ts)
757
- * stats: 13 WASM / 19 Native (stats/basic+advanced.ts, native: window.ts)
976
+ * core: 14 tools (core/queries.ts, core/tables.ts, core/indexes.ts, core/convenience.ts)
977
+ * json: 24 tools (json-operations/crud+query+transform+security.ts, json-helpers/read+write.ts)
978
+ * text: 14 WASM / 19 Native (text/regex+formatting+search+validate+sentiment.ts, fts.ts)
979
+ * stats: 16 WASM / 22 Native (stats/basic+advanced.ts, inference/, anomaly-detection.ts, schema-risks.ts, native: window.ts)
758
980
  * vector: 11 tools (vector/storage+search+metadata.ts)
759
- * admin: 26 WASM / 33 Native (admin/backup+verify+pragma.ts, virtual/views+vtable+extensions+analysis.ts, native: transactions.ts)
981
+ * admin: 26 WASM / 26 Native (admin/backup+verify+pragma.ts, virtual/views+vtable+extensions+analysis.ts)
982
+ * transactions: 8 Native (native: transactions.ts)
760
983
  * geo: 4 WASM / 11 Native (geo.ts, native: spatialite/tools+analysis.ts)
761
984
  * introspection: 9 tools (introspection/graph/tools.ts, analysis/constraints+risks+snapshot.ts, diagnostics/storage+indexes+query-plan.ts)
762
985
  * migration: 6 tools (migration/tracking.ts) — opt-in
763
986
  * codemode: 1 tool (codemode.ts)
764
- * Total: 115 WASM / 139 Native tools
987
+ * Total: 125 WASM / 151 Native tools
765
988
  *
766
989
  * Note: 3 built-in server tools (server_info, server_health, list_adapters)
767
990
  * are always available regardless of filter settings.
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- export { DEFAULT_CONFIG, DbMcpServer, NAME, VERSION, createServer } from './chunk-DZQLDEQS.js';
2
- export { DatabaseAdapter } from './chunk-Z2GFQU3G.js';
3
- import './chunk-S5IDDSSB.js';
4
- export { AuthenticationError, AuthorizationError, ConnectionError, QueryError, ValidationError } from './chunk-4IA3DB5C.js';
5
- export { ALL_TOOL_GROUPS, DbMcpError, META_GROUPS, TOOL_GROUPS, filterTools, getFilterSummary, getToolFilterFromEnv, isToolEnabled, parseToolFilter } from './chunk-AOUL5SHS.js';
1
+ export { DEFAULT_CONFIG, DbMcpServer, createServer } from './chunk-WBER5YY4.js';
2
+ import './chunk-SFJQCNG7.js';
3
+ export { DatabaseAdapter } from './chunk-Z7C2TM4L.js';
4
+ export { NAME, VERSION } from './chunk-VIDSICEL.js';
5
+ export { AuthenticationError, AuthorizationError, ConnectionError, QueryError, ValidationError } from './chunk-X3MUUOWM.js';
6
+ export { ALL_TOOL_GROUPS, DbMcpError, META_GROUPS, TOOL_GROUPS, filterTools, getFilterSummary, getToolFilterFromEnv, isToolEnabled, parseToolFilter } from './chunk-645ZEFLA.js';