db-mcp 1.1.1 → 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.
- package/.gitleaks.toml +9 -0
- package/.trivyignore +8 -0
- package/README.md +193 -120
- package/dist/{chunk-TVIZ3XJH.js → chunk-5Y42NPBP.js} +4168 -2411
- package/dist/{chunk-AOUL5SHS.js → chunk-645ZEFLA.js} +70 -20
- package/dist/chunk-OKOVZ5QE.js +28 -0
- package/dist/chunk-SFJQCNG7.js +131 -0
- package/dist/{chunk-FW7UCRLN.js → chunk-VIDSICEL.js} +3 -0
- package/dist/chunk-WBER5YY4.js +2053 -0
- package/dist/{chunk-4IA3DB5C.js → chunk-X3MUUOWM.js} +19 -2
- package/dist/{chunk-Z2GFQU3G.js → chunk-Z7C2TM4L.js} +114 -21
- package/dist/cli.js +79 -5
- package/dist/{http-VSB7DBJR.js → http-6KF4ULDI.js} +199 -77
- package/dist/index.d.ts +231 -8
- package/dist/index.js +6 -5
- package/dist/{sqlite-26V3Y4MK.js → sqlite-U5KSYQXK.js} +62 -138
- package/dist/{sqlite-native-5O7FZJGB.js → sqlite-native-JXMCFQBA.js} +575 -113
- package/dist/worker-script.js +34 -10
- package/logs/.gitkeep +1 -0
- package/mcp-config-example.json +83 -0
- package/package.json +9 -8
- package/playwright.config.ts +1 -1
- package/scripts/update-badges.ts +99 -0
- package/server.json +7 -5
- package/test-server/README.md +20 -23
- package/test-server/code-map.md +45 -33
- package/test-server/reset-database.ps1 +59 -17
- package/test-server/scripts/README.md +27 -0
- package/test-server/{test-help-resources.mjs → scripts/test-help-resources.mjs} +12 -5
- package/test-server/scripts/test-prompts.mjs +251 -0
- package/test-server/{test-tool-annotations.mjs → scripts/test-tool-annotations.mjs} +9 -4
- package/test-server/test-advanced/README.md +70 -0
- package/test-server/test-advanced/test-codemode-advanced-admin.md +174 -0
- package/test-server/test-advanced/test-codemode-advanced-core.md +193 -0
- package/test-server/test-advanced/test-codemode-advanced-geo.md +157 -0
- package/test-server/test-advanced/test-codemode-advanced-introspection.md +171 -0
- package/test-server/test-advanced/test-codemode-advanced-json.md +169 -0
- package/test-server/test-advanced/test-codemode-advanced-migration.md +160 -0
- package/test-server/test-advanced/test-codemode-advanced-stats.md +185 -0
- package/test-server/test-advanced/test-codemode-advanced-text.md +183 -0
- package/test-server/test-advanced/test-codemode-advanced-transactions.md +136 -0
- package/test-server/test-advanced/test-codemode-advanced-vector.md +141 -0
- package/test-server/test-codemode/README.md +121 -0
- package/test-server/test-codemode/test-codemode-admin.md +223 -0
- package/test-server/test-codemode/test-codemode-core.md +286 -0
- package/test-server/test-codemode/test-codemode-geo.md +177 -0
- package/test-server/test-codemode/test-codemode-introspection.md +190 -0
- package/test-server/test-codemode/test-codemode-json.md +237 -0
- package/test-server/test-codemode/test-codemode-migration.md +278 -0
- package/test-server/test-codemode/test-codemode-sandbox.md +413 -0
- package/test-server/test-codemode/test-codemode-stats.md +232 -0
- package/test-server/test-codemode/test-codemode-text.md +237 -0
- package/test-server/test-codemode/test-codemode-transactions.md +236 -0
- package/test-server/test-codemode/test-codemode-vector.md +244 -0
- package/test-server/test-codemode/test-codemode-wasm-degradation.md +394 -0
- package/test-server/test-database.sql +37 -1
- package/test-server/test-resources.md +43 -16
- package/test-server/test-tool-groups/README.md +100 -0
- package/test-server/test-tool-groups/test-admin-core.md +165 -0
- package/test-server/test-tool-groups/test-admin-extensions.md +133 -0
- package/test-server/{test-tools.md → test-tool-groups/test-core-data.md} +103 -17
- package/test-server/test-tool-groups/test-core-schema.md +240 -0
- package/test-server/test-tool-groups/test-geo-haversine.md +130 -0
- package/test-server/test-tool-groups/test-geo-spatialite.md +110 -0
- package/test-server/test-tool-groups/test-introspection-diagnostics.md +123 -0
- package/test-server/test-tool-groups/test-introspection-schema.md +133 -0
- package/test-server/test-tool-groups/test-json-read.md +219 -0
- package/test-server/test-tool-groups/test-json-write.md +157 -0
- package/test-server/test-tool-groups/test-migration.md +193 -0
- package/test-server/test-tool-groups/test-stats-advanced.md +118 -0
- package/test-server/test-tool-groups/test-stats-basic.md +156 -0
- package/test-server/test-tool-groups/test-text-advanced.md +169 -0
- package/test-server/test-tool-groups/test-text-basic.md +177 -0
- package/test-server/test-tool-groups/test-transactions.md +179 -0
- package/test-server/test-tool-groups/test-vector-read.md +130 -0
- package/test-server/test-tool-groups/test-vector-write.md +115 -0
- package/test-server/tool-reference.md +79 -58
- package/tsconfig.build.json +6 -0
- package/dist/chunk-RHVEZ42P.js +0 -873
- package/test-server/test-agent-experience.md +0 -243
- package/test-server/test-group-tools.md +0 -861
- package/test-server/test-tools-advanced-1.md +0 -517
- package/test-server/test-tools-advanced-2.md +0 -487
- package/test-server/test-tools-codemode.md +0 -629
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import './chunk-
|
|
2
|
-
import
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
|
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:
|
|
755
|
-
* json:
|
|
756
|
-
* text:
|
|
757
|
-
* stats:
|
|
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 /
|
|
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:
|
|
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, createServer } from './chunk-
|
|
2
|
-
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
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';
|