db-mcp 3.0.2 → 5.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/README.md +112 -75
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/database-adapter.d.ts +11 -1
- package/dist/adapters/database-adapter.d.ts.map +1 -1
- package/dist/adapters/registration/resources.d.ts.map +1 -1
- package/dist/adapters/registration/tools.d.ts.map +1 -1
- package/dist/adapters/sqlite/prompts/analysis.d.ts.map +1 -1
- package/dist/adapters/sqlite/query-executor.d.ts.map +1 -1
- package/dist/adapters/sqlite/read-write-lock.d.ts +49 -0
- package/dist/adapters/sqlite/read-write-lock.d.ts.map +1 -0
- package/dist/adapters/sqlite/resources.d.ts +1 -1
- package/dist/adapters/sqlite/resources.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/admin.d.ts +33 -19
- package/dist/adapters/sqlite/schemas/admin.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/core.d.ts +96 -0
- package/dist/adapters/sqlite/schemas/core.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/fts.d.ts +4 -0
- package/dist/adapters/sqlite/schemas/fts.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/introspection.d.ts +25 -15
- package/dist/adapters/sqlite/schemas/introspection.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/migration.d.ts +2 -2
- package/dist/adapters/sqlite/schemas/migration.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/spatialite.d.ts +1 -0
- package/dist/adapters/sqlite/schemas/spatialite.d.ts.map +1 -1
- package/dist/adapters/sqlite/schemas/text.d.ts +36 -0
- package/dist/adapters/sqlite/schemas/text.d.ts.map +1 -1
- package/dist/adapters/sqlite/sqlite-adapter.d.ts +6 -0
- package/dist/adapters/sqlite/sqlite-adapter.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/admin/backup/create.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/admin/backup/dump.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/admin/backup/restore.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/admin/index.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/admin/pragma.d.ts +0 -4
- package/dist/adapters/sqlite/tools/admin/pragma.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/admin/verify.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/codemode.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/core/convenience.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/core/index.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/core/queries.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/core/tables.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/core/versioning.d.ts +19 -0
- package/dist/adapters/sqlite/tools/core/versioning.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/fts.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/introspection/diagnostics/indexes.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/introspection/graph/tools.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/json-helpers/read.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/json-operations/crud.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/json-operations/query.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/migration/helpers.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/text/helpers.d.ts +9 -0
- package/dist/adapters/sqlite/tools/text/helpers.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/text/index.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/text/search.d.ts +4 -0
- package/dist/adapters/sqlite/tools/text/search.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/vector/metadata.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/vector/search.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/vector/storage.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/virtual/vtable/analyze-csv.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/virtual/vtable/csv.d.ts.map +1 -1
- package/dist/adapters/sqlite/tools/virtual/vtable/info.d.ts.map +1 -1
- package/dist/adapters/sqlite/types.d.ts +5 -1
- package/dist/adapters/sqlite/types.d.ts.map +1 -1
- package/dist/adapters/sqlite-native/index.d.ts +1 -1
- package/dist/adapters/sqlite-native/index.d.ts.map +1 -1
- package/dist/adapters/sqlite-native/native-sqlite-adapter.d.ts.map +1 -1
- package/dist/adapters/sqlite-native/tools/spatialite/tools.d.ts.map +1 -1
- package/dist/adapters/sqlite-native/tools/window/helpers.d.ts +52 -0
- package/dist/adapters/sqlite-native/tools/window/helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/window/index.d.ts +7 -0
- package/dist/adapters/sqlite-native/tools/window/index.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/window/lag-lead.d.ts +7 -0
- package/dist/adapters/sqlite-native/tools/window/lag-lead.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/window/moving-average.d.ts +7 -0
- package/dist/adapters/sqlite-native/tools/window/moving-average.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/window/ntile.d.ts +7 -0
- package/dist/adapters/sqlite-native/tools/window/ntile.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/window/rank.d.ts +7 -0
- package/dist/adapters/sqlite-native/tools/window/rank.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/window/row-number.d.ts +7 -0
- package/dist/adapters/sqlite-native/tools/window/row-number.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/window/running-total.d.ts +7 -0
- package/dist/adapters/sqlite-native/tools/window/running-total.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/window/schemas.d.ts +159 -0
- package/dist/adapters/sqlite-native/tools/window/schemas.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/transaction-methods.d.ts.map +1 -1
- package/dist/audit/interceptor.d.ts.map +1 -1
- package/dist/audit/logger.d.ts +26 -25
- package/dist/audit/logger.d.ts.map +1 -1
- package/dist/auth/authorization-server-discovery.d.ts.map +1 -1
- package/dist/auth/scopes/mapping.d.ts.map +1 -1
- package/dist/auth/token-validator.d.ts.map +1 -1
- package/dist/{chunk-W5WQVNVX.js → chunk-27C5EE6U.js} +23 -17
- package/dist/chunk-2TYICG7B.js +1183 -0
- package/dist/{chunk-37G4DLL6.js → chunk-LLIAIJPO.js} +1146 -539
- package/dist/{chunk-NV34JSQM.js → chunk-UE7GVOBV.js} +1757 -1331
- package/dist/{chunk-L552U3QS.js → chunk-XLIYO3YA.js} +401 -77
- package/dist/cli.js +116 -11
- package/dist/codemode/api-constants.d.ts.map +1 -1
- package/dist/codemode/api.d.ts.map +1 -1
- package/dist/codemode/sandbox-factory.d.ts.map +1 -1
- package/dist/codemode/sandbox.d.ts +0 -3
- package/dist/codemode/sandbox.d.ts.map +1 -1
- package/dist/codemode/security.d.ts.map +1 -1
- package/dist/codemode/types.d.ts +2 -0
- package/dist/codemode/types.d.ts.map +1 -1
- package/dist/constants/server-instructions.d.ts +1 -1
- package/dist/constants/server-instructions.d.ts.map +1 -1
- package/dist/filtering/tool-constants.d.ts +11 -11
- package/dist/filtering/tool-constants.d.ts.map +1 -1
- package/dist/{http-HWTUVFIA.js → http-ZDMK46QU.js} +195 -32
- package/dist/index.js +4 -4
- package/dist/observability/metrics.d.ts +36 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/system-db.d.ts +13 -0
- package/dist/observability/system-db.d.ts.map +1 -0
- package/dist/server/mcp-server.d.ts +3 -0
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/registration/admin-tools.d.ts +6 -0
- package/dist/server/registration/admin-tools.d.ts.map +1 -0
- package/dist/server/registration/audit-tools/backup.d.ts +8 -0
- package/dist/server/registration/audit-tools/backup.d.ts.map +1 -0
- package/dist/server/registration/audit-tools/helpers.d.ts +11 -0
- package/dist/server/registration/audit-tools/helpers.d.ts.map +1 -0
- package/dist/server/registration/audit-tools/index.d.ts +4 -0
- package/dist/server/registration/audit-tools/index.d.ts.map +1 -0
- package/dist/server/registration/audit-tools/resource.d.ts +8 -0
- package/dist/server/registration/audit-tools/resource.d.ts.map +1 -0
- package/dist/server/registration/audit-tools/search.d.ts +7 -0
- package/dist/server/registration/audit-tools/search.d.ts.map +1 -0
- package/dist/server/registration/help-resources.d.ts.map +1 -1
- package/dist/server/registration/index.d.ts +3 -1
- package/dist/server/registration/index.d.ts.map +1 -1
- package/dist/server/registration/observability-resources.d.ts +11 -0
- package/dist/server/registration/observability-resources.d.ts.map +1 -0
- package/dist/server/subscription-manager.d.ts +35 -0
- package/dist/server/subscription-manager.d.ts.map +1 -0
- package/dist/{sqlite-TB56Z7G3.js → sqlite-XWZPEZJO.js} +195 -19
- package/dist/{sqlite-native-IF75BB3O.js → sqlite-native-5GQ3WKCZ.js} +311 -147
- package/dist/transports/http/middleware.d.ts.map +1 -1
- package/dist/transports/http/oauth.d.ts.map +1 -1
- package/dist/transports/http/sessions/legacy-sse.d.ts.map +1 -1
- package/dist/transports/http/sessions/stateful.d.ts +5 -1
- package/dist/transports/http/sessions/stateful.d.ts.map +1 -1
- package/dist/transports/http/transport.d.ts +2 -0
- package/dist/transports/http/transport.d.ts.map +1 -1
- package/dist/transports/http/types.d.ts +11 -0
- package/dist/transports/http/types.d.ts.map +1 -1
- package/dist/types/auth.d.ts +4 -0
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/server.d.ts +5 -0
- package/dist/types/server.d.ts.map +1 -1
- package/dist/utils/annotations.d.ts +8 -0
- package/dist/utils/annotations.d.ts.map +1 -1
- package/dist/utils/errors/categories.d.ts +16 -11
- package/dist/utils/errors/categories.d.ts.map +1 -1
- package/dist/utils/errors/classes.d.ts +37 -0
- package/dist/utils/errors/classes.d.ts.map +1 -1
- package/dist/utils/errors/format.d.ts +2 -2
- package/dist/utils/errors/format.d.ts.map +1 -1
- package/dist/utils/errors/index.d.ts +1 -1
- package/dist/utils/errors/index.d.ts.map +1 -1
- package/dist/utils/errors/suggestions.d.ts +1 -0
- package/dist/utils/errors/suggestions.d.ts.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/logger/types.d.ts +1 -1
- package/dist/utils/logger/types.d.ts.map +1 -1
- package/dist/utils/security-utils.d.ts +29 -0
- package/dist/utils/security-utils.d.ts.map +1 -0
- package/dist/utils/stream-utils.d.ts +14 -0
- package/dist/utils/stream-utils.d.ts.map +1 -0
- package/dist/utils/tokens.d.ts +15 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/dist/utils/where-clause.d.ts +0 -13
- package/dist/utils/where-clause.d.ts.map +1 -1
- package/package.json +43 -44
- package/dist/adapters/sqlite-native/tools/window.d.ts +0 -7
- package/dist/adapters/sqlite-native/tools/window.d.ts.map +0 -1
- package/dist/chunk-E5IESRTK.js +0 -489
- package/dist/server/registration/audit-tools.d.ts +0 -13
- package/dist/server/registration/audit-tools.d.ts.map +0 -1
- package/dist/utils/insights-manager.d.ts +0 -39
- package/dist/utils/insights-manager.d.ts.map +0 -1
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import { VERSION, NAME, READ_ONLY, SERVER_ICONS, ASSISTANT_FOCUSED, redactObject, getRequiredScope, AuditListBackupsOutputSchema, AuditListBackupsSchema, AuditGetBackupOutputSchema, AuditCleanupOutputSchema, AuditDiffBackupOutputSchema, AuditRestoreBackupOutputSchema } from './chunk-
|
|
2
|
-
import { parseToolFilter, getToolFilterFromEnv,
|
|
1
|
+
import { VERSION, NAME, READ_ONLY, SERVER_ICONS, ASSISTANT_FOCUSED, toSDK, redactObject, getRequiredScope, estimateTokens, AuditListBackupsOutputSchema, AuditListBackupsSchema, AuditGetBackupOutputSchema, AuditCleanupOutputSchema, AuditDiffBackupOutputSchema, AuditRestoreBackupOutputSchema, AuditSearchOutputSchema, AuditSearchSchema } from './chunk-2TYICG7B.js';
|
|
2
|
+
import { ErrorCategory, logger, parseToolFilter, getToolFilterFromEnv, ValidationError, getFilterSummary, DbMcpError, getAuthContext, scopesGrantToolAccess, registerToolScopes, metrics, InsufficientScopeError, formatHandlerError, InternalError, sanitizeErrorMessage } from './chunk-XLIYO3YA.js';
|
|
3
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
-
import { open, stat, mkdir, rename, readdir, unlink, mkdtemp, writeFile, rmdir, readFile, appendFile } from 'fs/promises';
|
|
6
5
|
import { dirname, join, basename, sep } from 'path';
|
|
6
|
+
import { mkdirSync } from 'fs';
|
|
7
|
+
import { readdir, stat, unlink, mkdtemp, writeFile, rename, rmdir, readFile, mkdir } from 'fs/promises';
|
|
7
8
|
import { gzip, gunzipSync } from 'zlib';
|
|
8
9
|
import { promisify } from 'util';
|
|
9
10
|
import { performance } from 'perf_hooks';
|
|
10
11
|
import { z } from 'zod';
|
|
12
|
+
import { SubscribeRequestSchema, UnsubscribeRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
11
13
|
|
|
12
14
|
// src/constants/server-instructions.ts
|
|
13
15
|
var INSTRUCTIONS = `# db-mcp (SQLite MCP Server)
|
|
14
16
|
|
|
15
17
|
## Quick Access
|
|
16
18
|
|
|
17
|
-
| Purpose
|
|
18
|
-
|
|
|
19
|
-
| Health check
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
19
|
+
| Purpose | Action |
|
|
20
|
+
| ------------------ | -------------------------- |
|
|
21
|
+
| Health check | \`server_health\` tool |
|
|
22
|
+
| Live server health | \`sqlite://health\` resource |
|
|
23
|
+
| Server info | \`server_info\` tool |
|
|
24
|
+
| Database schema | \`sqlite://schema\` resource |
|
|
25
|
+
| Tool help | \`sqlite://help\` resource |
|
|
23
26
|
|
|
24
27
|
## Built-in Tools
|
|
25
28
|
|
|
@@ -31,9 +34,7 @@ Read \`sqlite://help\` for gotchas and critical usage patterns.
|
|
|
31
34
|
Read \`sqlite://help/{group}\` for group-specific tool reference (core, json, text, stats, vector, geo, admin, transactions, introspection, migration).
|
|
32
35
|
Only help resources for your enabled tool groups are registered.`;
|
|
33
36
|
var HELP_CONTENT = /* @__PURE__ */ new Map([
|
|
34
|
-
[
|
|
35
|
-
"admin",
|
|
36
|
-
`# db-mcp Help \u2014 Database Administration (32N/31W tools) + Server Audit (5 tools)
|
|
37
|
+
["admin", `# db-mcp Help \u2014 Database Administration (31N/30W tools) + Server Audit (7 tools)
|
|
37
38
|
|
|
38
39
|
## Maintenance
|
|
39
40
|
|
|
@@ -42,7 +43,7 @@ sqlite_integrity_check({ maxErrors: 10 }); // check for corruption
|
|
|
42
43
|
sqlite_optimize({ analyze: true, reindex: true }); // optimize performance
|
|
43
44
|
sqlite_vacuum(); // reclaim space
|
|
44
45
|
sqlite_analyze({ table: "orders" }); // update statistics for query planner
|
|
45
|
-
sqlite_dbstat(
|
|
46
|
+
sqlite_dbstat(); // storage stats (\u26A0\uFE0F summarize native-only; WASM returns counts only)
|
|
46
47
|
sqlite_reindex(); // rebuild all indexes
|
|
47
48
|
sqlite_reindex({ target: "idx_users_email" }); // rebuild specific index
|
|
48
49
|
sqlite_reindex({ target: "orders" }); // rebuild all indexes for a table
|
|
@@ -50,6 +51,8 @@ sqlite_reindex({ target: "orders" }); // rebuild all indexes for a table
|
|
|
50
51
|
|
|
51
52
|
## Backup/Restore (Native only)
|
|
52
53
|
|
|
54
|
+
\u26A0\uFE0F Paths must be absolute and within explicitly authorized \`ALLOWED_IO_ROOTS\`.
|
|
55
|
+
|
|
53
56
|
\`\`\`javascript
|
|
54
57
|
sqlite_backup({ targetPath: "/path/to/backup.db" });
|
|
55
58
|
sqlite_vacuum_into({ outputPath: "/path/to/compact.db" }); // create defragmented copy
|
|
@@ -58,14 +61,16 @@ sqlite_verify_backup({ backupPath: "/path/to/backup.db" }); // check integrity w
|
|
|
58
61
|
sqlite_restore({ sourcePath: "/path/to/backup.db" }); // \u26A0\uFE0F WARNING: Replaces current database
|
|
59
62
|
\`\`\`
|
|
60
63
|
|
|
61
|
-
## Audit
|
|
64
|
+
## Server Management & Audit
|
|
62
65
|
|
|
63
66
|
\`\`\`javascript
|
|
67
|
+
sqlite_server_config({ action: "set", setting: "logLevel", value: "debug" }); // get or update runtime server configuration
|
|
64
68
|
sqlite_audit_list_backups({ limit: 10, offset: 0 }); // list pre-mutation DDL snapshots
|
|
65
69
|
sqlite_audit_get_backup({ filename: "snapshot_123.json" }); // retrieve specific snapshot
|
|
66
70
|
sqlite_audit_diff_backup({ filename: "snapshot_123.json" }); // compare snapshot against live schema
|
|
67
71
|
sqlite_audit_restore_backup({ filename: "snapshot_123.json", dryRun: true }); // restore schema from snapshot
|
|
68
72
|
sqlite_audit_cleanup(); // apply retention policy and delete old snapshots
|
|
73
|
+
sqlite_audit_search({ tool: "sqlite_drop_table", limit: 10 }); // search and filter structured audit logs
|
|
69
74
|
\`\`\`
|
|
70
75
|
|
|
71
76
|
## PRAGMA
|
|
@@ -76,7 +81,7 @@ sqlite_pragma_settings({ pragma: "cache_size", value: 10000 }); // set value
|
|
|
76
81
|
sqlite_pragma_table_info({ table: "users" }); // column details
|
|
77
82
|
sqlite_pragma_compile_options({ filter: "FTS" }); // \u26A0\uFE0F WASM may show FTS3, not FTS5
|
|
78
83
|
sqlite_pragma_database_list(); // list attached databases
|
|
79
|
-
sqlite_attach_database({ filepath: "/path/to/other.db", alias: "archive" }); // attach external DB
|
|
84
|
+
sqlite_attach_database({ filepath: "/path/to/other.db", alias: "archive" }); // attach external DB (\u26A0\uFE0F subject to ALLOWED_IO_ROOTS)
|
|
80
85
|
sqlite_detach_database({ alias: "archive" }); // detach DB
|
|
81
86
|
sqlite_pragma_optimize(); // run PRAGMA optimize
|
|
82
87
|
\`\`\`
|
|
@@ -132,7 +137,7 @@ sqlite_create_rtree_table({ tableName: "locations_idx", dimensions: 2 }); // 2D:
|
|
|
132
137
|
|
|
133
138
|
## CSV Virtual Tables (Native only)
|
|
134
139
|
|
|
135
|
-
\u26A0\uFE0F Requires ABSOLUTE file paths
|
|
140
|
+
\u26A0\uFE0F Requires ABSOLUTE file paths that must fall within explicitly authorized \`ALLOWED_IO_ROOTS\`.
|
|
136
141
|
|
|
137
142
|
\`\`\`javascript
|
|
138
143
|
sqlite_analyze_csv_schema({ filePath: "/absolute/path/to/data.csv" }); // analyze CSV structure
|
|
@@ -140,22 +145,13 @@ sqlite_create_csv_table({
|
|
|
140
145
|
tableName: "csv_data",
|
|
141
146
|
filePath: "/absolute/path/to/data.csv",
|
|
142
147
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
## Business Insights
|
|
146
|
-
|
|
147
|
-
\`\`\`javascript
|
|
148
|
-
sqlite_append_insight({ insight: "Q4 revenue increased 23% YoY" }); // add to memo://insights
|
|
149
|
-
\`\`\``
|
|
150
|
-
],
|
|
151
|
-
[
|
|
152
|
-
"core",
|
|
153
|
-
`# db-mcp Help \u2014 Core Operations (21 tools)
|
|
148
|
+
\`\`\``],
|
|
149
|
+
["core", `# db-mcp Help \u2014 Core Operations (25 tools)
|
|
154
150
|
|
|
155
151
|
## Basic Queries
|
|
156
152
|
|
|
157
|
-
- \`sqlite_read_query({ query: "SELECT * FROM users LIMIT 10" })\` \u2014 execute SELECT, PRAGMA, EXPLAIN, or WITH statements
|
|
158
|
-
- \`sqlite_write_query({ query: "INSERT INTO users (name) VALUES ('Alice')" })\` \u2014 execute INSERT, UPDATE, DELETE, REPLACE, or trigger DDL (CREATE/DROP TRIGGER)
|
|
153
|
+
- \`sqlite_read_query({ query: "SELECT * FROM users LIMIT 10", cursor: "...", stream: true, chunkSize: 10 })\` \u2014 execute SELECT, PRAGMA, EXPLAIN, or WITH statements. Supports \`cursor\` for offset-based pagination (returns \`nextCursor\`). Set \`stream: true\` to return row-by-row chunks via progress notifications instead of full response buffering (requires client progressToken support; gracefully falls back if unavailable). **Agent Tip:** Avoid \`SELECT *\` on wide tables with large text/JSON columns to conserve token context; use \`sqlite_describe_table\` first and select specific columns.
|
|
154
|
+
- \`sqlite_write_query({ query: "INSERT INTO users (name) VALUES ('Alice')" })\` \u2014 execute INSERT, UPDATE, DELETE, REPLACE, or trigger DDL (CREATE/DROP TRIGGER). Supports \`expectedVersion\` for OCC.
|
|
159
155
|
|
|
160
156
|
## Tables & Schema
|
|
161
157
|
|
|
@@ -175,19 +171,30 @@ sqlite_append_insight({ insight: "Q4 revenue increased 23% YoY" }); // add to me
|
|
|
175
171
|
- \`sqlite_create_index({ indexName: "idx_users_email", table: "users", columns: ["email"], unique?: true })\` \u2014 create a new index
|
|
176
172
|
- \`sqlite_drop_index({ indexName: "idx_users_email", ifExists?: true })\` \u2014 drop an existing index
|
|
177
173
|
|
|
174
|
+
## Versioning & Concurrency (OCC)
|
|
175
|
+
|
|
176
|
+
- \`sqlite_enable_versioning({ table: "users" })\` \u2014 add a \`_version\` column and a BEFORE UPDATE trigger to enforce optimistic concurrency control on a table
|
|
177
|
+
- \`sqlite_disable_versioning({ table: "users" })\` \u2014 remove the \`_version\` column and concurrency trigger
|
|
178
|
+
- \`sqlite_check_version({ table: "users", rowId: 1 })\` \u2014 get the current \`_version\` of a row
|
|
179
|
+
- \`sqlite_conditional_update({ table: "users", conditions: [{ column: "id", operator: "=", value: 1 }], expectedVersion: 2, data: { name: "Bob" } })\` \u2014 safely update a row, incrementing \`_version\` atomically. Will fail if \`expectedVersion\` does not match
|
|
180
|
+
|
|
178
181
|
## Convenience Tools (High-Level Data Operations)
|
|
179
182
|
|
|
180
|
-
- \`sqlite_upsert({ table: "users", data: { id: 1, name: "Alice" }, conflictColumns: ["id"], updateColumns: ["name"], returning: true })\` \u2014 insert or update a row using \`ON CONFLICT\` (or \`REPLACE\` fallback). Supports \`returning: true\` or array of columns.
|
|
183
|
+
- \`sqlite_upsert({ table: "users", data: { id: 1, name: "Alice" }, conflictColumns: ["id"], updateColumns: ["name"], returning: true })\` \u2014 insert or update a row using \`ON CONFLICT\` (or \`REPLACE\` fallback). Supports \`returning: true\` or array of columns, and \`expectedVersion\` for OCC.
|
|
181
184
|
- \`sqlite_batch_insert({ table: "users", rows: [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }], returning: true })\` \u2014 insert multiple rows in a single batch. Supports \`returning: true\` or array of columns.
|
|
182
185
|
- \`sqlite_count({ table: "users", where?: "status = 'active'" })\` \u2014 count rows in a table (faster than a full query)
|
|
183
186
|
- \`sqlite_exists({ table: "users", where: "email = 'test@example.com'" })\` \u2014 check if a row exists (stops at first match)
|
|
184
187
|
- \`sqlite_truncate({ table: "users" })\` \u2014 quickly delete all rows from a table (executes \`DELETE FROM table\`)
|
|
185
188
|
- \`sqlite_date_add({ table: "users", column: "created_at", amount: 7, unit: "days", whereClause: "id = 1" })\` \u2014 add or subtract time intervals from a date column. By default returns only the computed column; use \`selectColumns\` to return additional context.
|
|
186
|
-
- \`sqlite_date_diff({ table: "users", column1: "ended_at", column2: "started_at", unit: "days", whereClause: "id = 1" })\` \u2014 calculate the difference between two date columns. By default returns only the computed column; use \`selectColumns\` to return additional context
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
- \`sqlite_date_diff({ table: "users", column1: "ended_at", column2: "started_at", unit: "days", whereClause: "id = 1" })\` \u2014 calculate the difference between two date columns. By default returns only the computed column; use \`selectColumns\` to return additional context.
|
|
190
|
+
|
|
191
|
+
## \u26A0\uFE0F Core Gotchas
|
|
192
|
+
|
|
193
|
+
1. **sqlite_write_query**: DML only (INSERT/UPDATE/DELETE/REPLACE) \u2014 use \`sqlite_read_query\` for SELECT, and dedicated DDL tools for schema modifications.
|
|
194
|
+
2. **sqlite_upsert**: Always specify \`conflictColumns\` \u2014 without it, falls back to \`REPLACE\` which deletes and re-inserts the row, potentially losing columns not included in \`data\`.
|
|
195
|
+
3. **Optimistic Concurrency Control (OCC)**: When a table is versioned (\`sqlite_enable_versioning\`), you MUST supply \`expectedVersion\` to \`sqlite_write_query\`, \`sqlite_upsert\`, and \`sqlite_conditional_update\`. Omitting it will result in a \`ConflictError\` to strictly prevent lost updates.
|
|
196
|
+
4. **sqlite_batch_insert**: All rows must have the same keys \u2014 inconsistent column sets across rows will cause errors or unexpected NULLs.`],
|
|
197
|
+
["geo", `# db-mcp Help \u2014 Geospatial Operations (11N/4W: 4 basic + 7 SpatiaLite [NATIVE ONLY])
|
|
191
198
|
|
|
192
199
|
## Basic Geo (always available \u2014 Haversine formula)
|
|
193
200
|
|
|
@@ -292,35 +299,15 @@ sqlite_spatialite_index({
|
|
|
292
299
|
geometryColumn: "geom",
|
|
293
300
|
action: "create",
|
|
294
301
|
}); // create, drop, or check
|
|
295
|
-
\`\`\``
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
2. **Regex patterns**: Double-escape backslashes (\`\\\\\\\\\`) when passing through JSON/MCP
|
|
305
|
-
3. **FTS5 virtual tables**: \`*_fts\` and shadow tables \`*_fts_*\` are hidden from \`sqlite_list_tables\` for cleaner output
|
|
306
|
-
4. **FTS5 boolean logic**: Uses AND by default \u2014 \`"machine learning"\` = rows with BOTH words. Use OR explicitly: \`"machine OR learning"\`
|
|
307
|
-
5. **json_each row multiplication**: Expands arrays to rows \u2014 use \`limit\` param for large arrays
|
|
308
|
-
6. **json_group_object without groupByColumn**: Each row creates a key-value pair; duplicate keys result if key values aren't unique
|
|
309
|
-
7. **allowExpressions**: For column extraction ONLY (e.g., \`json_extract\`), NOT aggregate functions \u2014 use \`aggregateFunction\` param instead
|
|
310
|
-
8. **sqlite_json_normalize_column**: Defaults to \`preserve\` (maintains original format); use \`outputFormat: 'text'\` to force text
|
|
311
|
-
9. **Fuzzy matching tokenization**: Matches WORD TOKENS by default \u2014 \`"laptop"\` matches \`"Laptop Pro 15"\` (distance 0 on first token). Use \`tokenize: false\` for full-string matching
|
|
312
|
-
10. **SpatiaLite distances**: \`nearest_neighbor\`/\`distance_matrix\` return CARTESIAN distance (degrees), not geodetic (km/miles)
|
|
313
|
-
11. **SpatiaLite buffer**: Auto-simplifies output by default (tolerance=0.0001). Use \`simplifyTolerance: 0\` to disable
|
|
314
|
-
12. **sqlite_stats_top_n**: Returns all columns by default which creates large payloads for wide tables \u2014 always pass \`selectColumns\` to control output size
|
|
315
|
-
13. **CSV virtual tables**: Require ABSOLUTE file paths
|
|
316
|
-
14. **sqlite_create_series_table**: Creates a REGULAR table (not virtual) \u2014 use \`sqlite_drop_table\` to remove
|
|
317
|
-
15. **sqlite_dbstat**: \`summarize\` only works in native; WASM returns counts only
|
|
318
|
-
16. **PRAGMA compile options**: WASM may show FTS3, not FTS5
|
|
319
|
-
17. **Vector tool schemas**: Vector tools use distinct schemas for specific operations. E.g., \`sqlite.vector.dimensions\` requires \`vectorColumn\`. Additionally, \`sqlite.vector.get\` wraps metadata inside a \`metadata\` object (e.g., \`metadata.content\`), and \`sqlite.vector.stats\` returns \`sampleSize\` and \`magnitudeStats\` (not \`count\` and \`stats\`).
|
|
320
|
-
18. **FTS5 trigger cleanup**: Dropping an FTS5 table with \`sqlite_drop_table\` automatically finds and removes the associated \`_ai\`, \`_ad\`, and \`_au\` sync triggers from the source table.
|
|
321
|
-
19. **sqlite_batch_insert**: All rows must have the same keys \u2014 inconsistent column sets across rows will cause errors or unexpected NULLs
|
|
322
|
-
20. **sqlite_schema_diff**: \`baseline\` and \`target\` accept either the string \`"current"\` (queries live DB) or an inline snapshot object from a prior \`sqlite_schema_snapshot\` call. At least one side must be \`"current"\` unless doing an offline comparison
|
|
323
|
-
21. **sqlite_upsert**: Always specify \`conflictColumns\` \u2014 without it, falls back to \`REPLACE\` which deletes and re-inserts the row, potentially losing columns not included in \`data\`
|
|
302
|
+
\`\`\``],
|
|
303
|
+
["gotchas", `# db-mcp Help \u2014 Gotchas & Code Mode
|
|
304
|
+
|
|
305
|
+
## Server-Level Rules
|
|
306
|
+
|
|
307
|
+
1. **CSV virtual tables & Backups**: Require ABSOLUTE file paths. Operations will be strictly blocked if paths do not fall within the explicitly authorized \`ALLOWED_IO_ROOTS\` directory list. Unconfigured stdio transports default to no filesystem access.
|
|
308
|
+
2. **Resource Subscriptions**: The \`sqlite://schema\` and \`sqlite://health\` resources support MCP subscriptions, allowing the client to receive real-time push notifications when DDL changes occur or health metrics update without needing to poll.
|
|
309
|
+
3. **HTTP Session Timeouts**: Stateful HTTP sessions automatically expire after 30 minutes of inactivity, or 24 hours total. Sessions are swept automatically; ensure client workflows re-authenticate or handle broken sessions gracefully.
|
|
310
|
+
4. **Structured Errors**: The server returns strongly-typed JSON errors instead of generic stack traces. Execution timeouts surface as recoverable \`TimeoutError\` (category: \`timeout\`). Rate limit violations surface as \`RateLimitError\` (category: \`rate_limit\`). Input/schema rejections surface as \`ValidationError\` (category: \`validation\`). Use this metadata (\`code\`, \`category\`, \`recoverable\`) to programmatically retry or backoff.
|
|
324
311
|
|
|
325
312
|
## WASM vs Native
|
|
326
313
|
|
|
@@ -333,6 +320,7 @@ sqlite_spatialite_index({
|
|
|
333
320
|
| Backup/Restore/Dump/VacuumInto/Verify (5 tools) | \u2705 | \u274C | Graceful error |
|
|
334
321
|
| R-Tree spatial indexing | \u2705 | \u274C | Graceful error |
|
|
335
322
|
| CSV virtual tables | \u2705 | \u274C | Graceful error |
|
|
323
|
+
| SQLCipher Encryption at Rest | \u2705 | \u274C | Graceful error |
|
|
336
324
|
| generate_series | JS fallback | JS fallback | \u2014 |
|
|
337
325
|
| dbstat | \u2705 native (per-table) | \u274C | JS (counts only) |
|
|
338
326
|
| soundex() | \u2705 native | \u274C | JS |
|
|
@@ -346,22 +334,19 @@ sqlite_spatialite_index({
|
|
|
346
334
|
**Usage**: \`sqlite_execute_code({ code: "const tables = await sqlite.core.listTables(); return tables;" })\`
|
|
347
335
|
**Discover**: \`sqlite.help()\` for all groups, \`sqlite.<group>.help()\` for methods.
|
|
348
336
|
**Progress**: Use \`await sqlite.reportProgress(current, total, "Message")\` for custom long-running tasks.
|
|
349
|
-
**Groups**: \`sqlite.core\`, \`sqlite.json\`, \`sqlite.text\`, \`sqlite.stats\`, \`sqlite.vector\`, \`sqlite.admin\`, \`sqlite.transactions\` (Native-only), \`sqlite.geo\`, \`sqlite.introspection\`, \`sqlite.migration\`
|
|
337
|
+
**Groups**: \`sqlite.core\`, \`sqlite.json\`, \`sqlite.text\`, \`sqlite.stats\`, \`sqlite.vector\`, \`sqlite.admin\`, \`sqlite.transactions\` (Native-only), \`sqlite.geo\`, \`sqlite.introspection\`, \`sqlite.migration\` (opt-in)
|
|
350
338
|
|
|
351
339
|
> **Note**: Code Mode dynamically filters capabilities. In WASM environments, \`sqlite.help()\` will omit unsupported groups (e.g., \`transactions\`) and tools (e.g., FTS5) to accurately reflect the active runtime.
|
|
352
340
|
|
|
353
341
|
## Code Mode API Mapping
|
|
354
342
|
|
|
355
|
-
|
|
356
|
-
**Exception**: \`stats\` and \`migration\` keep their prefix: \`sqlite_stats_basic\` \u2192 \`sqlite.stats.statsBasic()\`, \`sqlite_migration_apply\` \u2192 \`sqlite.migration.migrationApply()\`
|
|
343
|
+
Code Mode maps standard tools to an object-oriented sandbox API (\`sqlite_group_action\` \u2192 \`sqlite.group.action()\`).
|
|
357
344
|
|
|
358
|
-
**
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
[
|
|
363
|
-
"introspection",
|
|
364
|
-
`# db-mcp Help \u2014 Schema Introspection (10 tools)
|
|
345
|
+
- **Flexible Parameters**: Parameters are forgiven for casing discrepancies (e.g. \`conflict_columns\` vs \`conflictColumns\`).
|
|
346
|
+
- **Flexible Methods**: Methods mapped into the Code Mode proxy forgive casing discrepancies (e.g. \`sqlite.core.read_query\` executes \`sqlite.core.readQuery()\`).
|
|
347
|
+
- Methods support both an options object and **positional arguments** (e.g. \`sqlite.core.readQuery("SELECT...")\`, \`sqlite.json.insert("docs", "data", {...})\`).
|
|
348
|
+
- **Discovery**: Call \`sqlite.help()\` or \`sqlite.core.help()\` to see available methods directly within the sandbox.`],
|
|
349
|
+
["introspection", `# db-mcp Help \u2014 Schema Introspection (10 tools)
|
|
365
350
|
|
|
366
351
|
All introspection tools are **read-only** \u2014 they query PRAGMAs and sqlite_master, never modify data.
|
|
367
352
|
|
|
@@ -434,6 +419,13 @@ sqlite_storage_analysis({ limit: 10 }); // top 10 tables only
|
|
|
434
419
|
// Audit index effectiveness \u2014 find redundant, missing FK, unindexed large tables
|
|
435
420
|
sqlite_index_audit({ excludeSystemTables: true });
|
|
436
421
|
sqlite_index_audit({ table: "orders", minSeverity: "warning" }); // reduce payload
|
|
422
|
+
// Run EXPLAIN QUERY PLAN on target queries to recommend composite/partial indexes
|
|
423
|
+
sqlite_index_audit({
|
|
424
|
+
recommendComposite: true,
|
|
425
|
+
queriesToAnalyze: [
|
|
426
|
+
"SELECT * FROM orders WHERE user_id = 1 AND status = 'active'",
|
|
427
|
+
],
|
|
428
|
+
});
|
|
437
429
|
|
|
438
430
|
// EXPLAIN QUERY PLAN with scan-type classification and optimization suggestions (SELECT/WITH only)
|
|
439
431
|
sqlite_query_plan({ sql: "SELECT * FROM orders WHERE status = 'active'" });
|
|
@@ -442,11 +434,9 @@ sqlite_query_plan({ sql: "SELECT * FROM orders WHERE status = 'active'" });
|
|
|
442
434
|
## \u26A0\uFE0F Gotchas
|
|
443
435
|
|
|
444
436
|
- \`excludeSystemTables\` defaults to \`true\` \u2014 SpatiaLite system tables are hidden for cleaner output. Pass \`false\` to include them
|
|
445
|
-
- \`sqlite_migration_risks\` analyzes DDL text statically \u2014 it does NOT execute the statements
|
|
446
|
-
|
|
447
|
-
[
|
|
448
|
-
"json",
|
|
449
|
-
`# db-mcp Help \u2014 JSON Operations (25 tools)
|
|
437
|
+
- \`sqlite_migration_risks\` analyzes DDL text statically \u2014 it does NOT execute the statements
|
|
438
|
+
- \`sqlite_schema_diff\`: \`baseline\` and \`target\` accept either the string \`"current"\` (queries live DB) or an inline snapshot object from a prior \`sqlite_schema_snapshot\` call. At least one side must be \`"current"\` unless doing an offline comparison.`],
|
|
439
|
+
["json", `# db-mcp Help \u2014 JSON Operations (25 tools)
|
|
450
440
|
|
|
451
441
|
## Collection & CRUD
|
|
452
442
|
|
|
@@ -520,11 +510,8 @@ sqlite_json_group_object({
|
|
|
520
510
|
|
|
521
511
|
- \`sqlite_json_storage_info({ table, column })\` \u2014 check text vs JSONB format
|
|
522
512
|
- \`sqlite_jsonb_convert({ table, column })\` \u2014 convert to JSONB for faster queries
|
|
523
|
-
- \`sqlite_json_normalize_column({ table, column, outputFormat? })\` \u2014 normalize JSON (sort keys, compact). \u26A0\uFE0F Defaults to \`"preserve"\` (maintains original format); use \`outputFormat: "text"\` to force text output`
|
|
524
|
-
|
|
525
|
-
[
|
|
526
|
-
"migration",
|
|
527
|
-
`# db-mcp Help \u2014 Migration Tracking (6 tools)
|
|
513
|
+
- \`sqlite_json_normalize_column({ table, column, outputFormat? })\` \u2014 normalize JSON (sort keys, compact). \u26A0\uFE0F Defaults to \`"preserve"\` (maintains original format); use \`outputFormat: "text"\` to force text output`],
|
|
514
|
+
["migration", `# db-mcp Help \u2014 Migration Tracking (6 tools)
|
|
528
515
|
|
|
529
516
|
\u26A0\uFE0F Must call \`sqlite_migration_init()\` before using any other migration tool \u2014 it creates the tracking table.
|
|
530
517
|
|
|
@@ -566,11 +553,8 @@ sqlite_migration_status();
|
|
|
566
553
|
## \u26A0\uFE0F Gotchas
|
|
567
554
|
|
|
568
555
|
- Rollback requires \`rollbackSql\` to have been provided when the migration was recorded/applied
|
|
569
|
-
- Migration group is **opt-in** \u2014 not included by default. Enable via \`--tool-filter\` flag: use \`dev-schema\` (core+introspection+migration+codemode) or \`full\` (all groups)`
|
|
570
|
-
]
|
|
571
|
-
[
|
|
572
|
-
"stats",
|
|
573
|
-
`# db-mcp Help \u2014 Statistical Analysis (23N/17W: 17 core + 6 window [NATIVE ONLY])
|
|
556
|
+
- Migration group is **opt-in** \u2014 not included by default. Enable via \`--tool-filter\` flag: use \`dev-schema\` (core+introspection+migration+codemode) or \`full\` (all groups)`],
|
|
557
|
+
["stats", `# db-mcp Help \u2014 Statistical Analysis (23N/17W: 17 core + 6 window [NATIVE ONLY])
|
|
574
558
|
|
|
575
559
|
## Core Statistics (always available)
|
|
576
560
|
|
|
@@ -689,11 +673,8 @@ sqlite_window_moving_avg({
|
|
|
689
673
|
windowSize: 7,
|
|
690
674
|
selectColumns: ["date"],
|
|
691
675
|
});
|
|
692
|
-
\`\`\``
|
|
693
|
-
]
|
|
694
|
-
[
|
|
695
|
-
"text",
|
|
696
|
-
`# db-mcp Help \u2014 Text Processing & FTS5 (19N/14W: 14 text + 5 FTS5 [NATIVE ONLY])
|
|
676
|
+
\`\`\``],
|
|
677
|
+
["text", `# db-mcp Help \u2014 Text Processing & FTS5 (20N/15W: 15 text + 5 FTS5 [NATIVE ONLY])
|
|
697
678
|
|
|
698
679
|
## Full-Text Search / FTS5 (5 tools, Native only)
|
|
699
680
|
|
|
@@ -713,6 +694,8 @@ sqlite_fts_search({
|
|
|
713
694
|
table: "articles_fts",
|
|
714
695
|
query: "machine learning",
|
|
715
696
|
limit: 10,
|
|
697
|
+
includeFacets: true, // optionally return breakdown by technique
|
|
698
|
+
cursor: "...", // Optional offset-based cursor for pagination (returns nextCursor)
|
|
716
699
|
});
|
|
717
700
|
sqlite_fts_match_info({ table: "articles_fts", query: "machine learning" }); // bm25 ranking info
|
|
718
701
|
|
|
@@ -732,7 +715,7 @@ sqlite_fts_headline({
|
|
|
732
715
|
|
|
733
716
|
\u26A0\uFE0F FTS5 virtual tables (\`*_fts\`) and shadow tables (\`*_fts_*\`) are hidden from \`sqlite_list_tables\` for cleaner output
|
|
734
717
|
|
|
735
|
-
## Text Processing (
|
|
718
|
+
## Text Processing (15 tools)
|
|
736
719
|
|
|
737
720
|
\`\`\`javascript
|
|
738
721
|
// Regex patterns: \u26A0\uFE0F Double-escape backslashes (\\\\) when passing through JSON/MCP
|
|
@@ -818,7 +801,19 @@ sqlite_advanced_search({
|
|
|
818
801
|
searchTerm: "laptop",
|
|
819
802
|
techniques: ["exact", "fuzzy", "phonetic"],
|
|
820
803
|
fuzzyThreshold: 0.4,
|
|
804
|
+
includeFacets: true,
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
// Hybrid Search \u2014 combines FTS5 text search and vector embedding search via Reciprocal Rank Fusion (RRF)
|
|
808
|
+
sqlite_hybrid_search({
|
|
809
|
+
table: "articles_fts",
|
|
810
|
+
query: "machine learning",
|
|
811
|
+
vectorColumn: "embedding",
|
|
812
|
+
queryVector: [0.1, 0.2, 0.3], // vector data from external embedding API
|
|
813
|
+
metric: "cosine",
|
|
814
|
+
limit: 10,
|
|
821
815
|
});
|
|
816
|
+
// \u26A0\uFE0F Requires an FTS5 table (Native only) and a vector column in the source table
|
|
822
817
|
|
|
823
818
|
// Sentiment analysis \u2014 text analysis (can analyze raw text or database columns)
|
|
824
819
|
sqlite_text_sentiment({ text: "This product is amazing and wonderful!" });
|
|
@@ -831,11 +826,8 @@ sqlite_text_sentiment({
|
|
|
831
826
|
returnWords: true,
|
|
832
827
|
});
|
|
833
828
|
// \u2192 { sentiment: "neutral", score: 0, matchedPositive: ["great"], matchedNegative: ["slow"] }
|
|
834
|
-
\`\`\``
|
|
835
|
-
|
|
836
|
-
[
|
|
837
|
-
"transactions",
|
|
838
|
-
`# db-mcp Help \u2014 Transactions (8 tools, Native only)
|
|
829
|
+
\`\`\``],
|
|
830
|
+
["transactions", `# db-mcp Help \u2014 Transactions (8 tools, Native only)
|
|
839
831
|
|
|
840
832
|
## Atomic Execution (preferred for simple cases)
|
|
841
833
|
|
|
@@ -866,11 +858,8 @@ sqlite_transaction_status(); // \u2192 { status: "active" | "none", active: true
|
|
|
866
858
|
|
|
867
859
|
- Transaction tools are **Native only** \u2014 WASM adapter does not support transactions
|
|
868
860
|
- Use \`sqlite_transaction_execute\` for simple multi-statement operations; manual \`begin\`/\`commit\` for complex flows with savepoints
|
|
869
|
-
- \`sqlite_transaction_status\` is read-only and requires only \`read\` scope; all other transaction tools require \`write\` scope`
|
|
870
|
-
|
|
871
|
-
[
|
|
872
|
-
"vector",
|
|
873
|
-
`# db-mcp Help \u2014 Vector/Semantic Search (11 tools)
|
|
861
|
+
- \`sqlite_transaction_status\` is read-only and requires only \`read\` scope; all other transaction tools require \`write\` scope`],
|
|
862
|
+
["vector", `# db-mcp Help \u2014 Vector/Semantic Search (11 tools)
|
|
874
863
|
|
|
875
864
|
\`\`\`javascript
|
|
876
865
|
// Create vector table with metadata columns
|
|
@@ -896,24 +885,29 @@ sqlite_vector_stats({ table: "docs", vectorColumn: "emb" }); // returns sampleSi
|
|
|
896
885
|
// Utility tools for preprocessing
|
|
897
886
|
sqlite_vector_normalize({ vector: [3, 4, 0, 0] }); // returns unit vector [0.6, 0.8, 0, 0]
|
|
898
887
|
sqlite_vector_distance({ vector1: [...], vector2: [...], metric: "cosine" }); // returns { value: <number> }
|
|
899
|
-
|
|
900
|
-
|
|
888
|
+
\`\`\`
|
|
889
|
+
|
|
890
|
+
## \u26A0\uFE0F Vector Gotchas
|
|
891
|
+
|
|
892
|
+
- **Vector tool schemas**: Vector tools use distinct schemas for specific operations. E.g., \`sqlite.vector.dimensions\` requires \`vectorColumn\`. Additionally, \`sqlite_vector_get\` wraps metadata inside a \`metadata\` object (e.g., \`metadata.content\`), and \`sqlite_vector_stats\` returns \`sampleSize\` and \`magnitudeStats\` (not \`count\` and \`stats\`).`]
|
|
901
893
|
]);
|
|
894
|
+
|
|
895
|
+
// src/audit/logger.ts
|
|
902
896
|
var BUFFER_HIGH_WATER = 50;
|
|
903
897
|
var FLUSH_INTERVAL_MS = 100;
|
|
904
898
|
var DEFAULT_RECENT_COUNT = 50;
|
|
905
899
|
var STDERR_SENTINEL = "stderr";
|
|
906
|
-
var TAIL_READ_BYTES = 65536;
|
|
907
900
|
var AuditLogger = class {
|
|
908
901
|
config;
|
|
909
902
|
buffer = [];
|
|
910
903
|
flushTimer = null;
|
|
911
904
|
activeFlush = null;
|
|
912
905
|
closed = false;
|
|
913
|
-
dirEnsured = false;
|
|
914
906
|
stderrMode;
|
|
915
|
-
|
|
907
|
+
systemDb;
|
|
908
|
+
constructor(config, systemDb = null) {
|
|
916
909
|
this.config = config;
|
|
910
|
+
this.systemDb = systemDb;
|
|
917
911
|
this.stderrMode = config.logPath.toLowerCase() === STDERR_SENTINEL;
|
|
918
912
|
if (config.enabled) {
|
|
919
913
|
this.flushTimer = setInterval(() => {
|
|
@@ -923,39 +917,27 @@ var AuditLogger = class {
|
|
|
923
917
|
}
|
|
924
918
|
}
|
|
925
919
|
/**
|
|
926
|
-
*
|
|
927
|
-
* Provides immediate confirmation that audit is configured correctly.
|
|
928
|
-
* Non-throwing — falls back silently if the path is inaccessible.
|
|
920
|
+
* Initialization is handled externally by SystemDb.
|
|
929
921
|
*/
|
|
930
|
-
|
|
931
|
-
if (this.stderrMode || !this.config.enabled) return;
|
|
932
|
-
|
|
933
|
-
await this.ensureDirectory();
|
|
934
|
-
const fh = await open(this.config.logPath, "a");
|
|
935
|
-
await fh.close();
|
|
936
|
-
} catch (err) {
|
|
937
|
-
process.stderr.write(
|
|
938
|
-
`[AUDIT] Failed to initialise log file: ${this.config.logPath}
|
|
939
|
-
`
|
|
940
|
-
);
|
|
941
|
-
throw err;
|
|
942
|
-
}
|
|
922
|
+
init() {
|
|
923
|
+
if (this.stderrMode || !this.config.enabled) return Promise.resolve();
|
|
924
|
+
return Promise.resolve();
|
|
943
925
|
}
|
|
944
926
|
/**
|
|
945
927
|
* Append an audit entry to the buffer.
|
|
946
|
-
* Non-blocking — the entry is
|
|
947
|
-
* actual
|
|
928
|
+
* Non-blocking — the entry is queued; the
|
|
929
|
+
* actual DB write happens on the next flush cycle.
|
|
948
930
|
*/
|
|
949
931
|
log(entry) {
|
|
950
932
|
if (this.closed || !this.config.enabled) return;
|
|
951
933
|
const safeEntry = redactObject(entry, 0, 5);
|
|
952
|
-
this.buffer.push(
|
|
934
|
+
this.buffer.push(safeEntry);
|
|
953
935
|
if (this.buffer.length >= BUFFER_HIGH_WATER) {
|
|
954
936
|
void this.flush();
|
|
955
937
|
}
|
|
956
938
|
}
|
|
957
939
|
/**
|
|
958
|
-
* Flush the buffer to
|
|
940
|
+
* Flush the buffer to SystemDb or stderr.
|
|
959
941
|
* Safe to call concurrently — serialises via `this.activeFlush` Promise.
|
|
960
942
|
*/
|
|
961
943
|
async flush() {
|
|
@@ -965,25 +947,45 @@ var AuditLogger = class {
|
|
|
965
947
|
}
|
|
966
948
|
if (this.buffer.length === 0) return;
|
|
967
949
|
const doFlush = async () => {
|
|
968
|
-
await
|
|
969
|
-
const
|
|
950
|
+
await Promise.resolve();
|
|
951
|
+
const entries = this.buffer;
|
|
970
952
|
this.buffer = [];
|
|
971
953
|
try {
|
|
972
954
|
if (this.stderrMode) {
|
|
955
|
+
const lines = entries.map((e) => JSON.stringify(e));
|
|
973
956
|
process.stderr.write(lines.join("\n") + "\n");
|
|
974
|
-
} else {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
)
|
|
957
|
+
} else if (this.systemDb) {
|
|
958
|
+
const db = this.systemDb.getDb();
|
|
959
|
+
const stmt = db.prepare(`
|
|
960
|
+
INSERT INTO audit_logs (timestamp, requestId, tool, category, scope, user, scopesJson, durationMs, success, tokenEstimate, error, argsJson, backupPath)
|
|
961
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
962
|
+
`);
|
|
963
|
+
const transaction = db.transaction((logs) => {
|
|
964
|
+
for (const log of logs) {
|
|
965
|
+
stmt.run(
|
|
966
|
+
log.timestamp,
|
|
967
|
+
log.requestId,
|
|
968
|
+
log.tool,
|
|
969
|
+
log.category,
|
|
970
|
+
log.scope,
|
|
971
|
+
log.user ?? null,
|
|
972
|
+
JSON.stringify(log.scopes),
|
|
973
|
+
log.durationMs,
|
|
974
|
+
log.success ? 1 : 0,
|
|
975
|
+
log.tokenEstimate ?? null,
|
|
976
|
+
log.error ?? null,
|
|
977
|
+
log.args ? JSON.stringify(log.args) : null,
|
|
978
|
+
log.backup ?? null
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
transaction(entries);
|
|
981
983
|
}
|
|
982
984
|
} catch (err) {
|
|
983
985
|
const message = err instanceof Error ? err.message : String(err);
|
|
984
986
|
process.stderr.write(`[AUDIT] Write failed: ${message}
|
|
985
987
|
`);
|
|
986
|
-
this.buffer.unshift(...
|
|
988
|
+
this.buffer.unshift(...entries);
|
|
987
989
|
}
|
|
988
990
|
};
|
|
989
991
|
this.activeFlush = doFlush();
|
|
@@ -1005,79 +1007,197 @@ var AuditLogger = class {
|
|
|
1005
1007
|
await this.flush();
|
|
1006
1008
|
}
|
|
1007
1009
|
/**
|
|
1008
|
-
* Read the most recent audit entries from the
|
|
1009
|
-
*
|
|
1010
|
-
* read from disk, preventing O(n) memory spikes for large audit logs.
|
|
1011
|
-
* Used by the `sqlite://audit` resource.
|
|
1012
|
-
*
|
|
1013
|
-
* @param count Maximum number of entries to return (default 50)
|
|
1010
|
+
* Read the most recent audit entries from the SystemDb.
|
|
1011
|
+
* Retrieve recent entries directly from the SystemDb.
|
|
1014
1012
|
*/
|
|
1015
|
-
async recent(
|
|
1016
|
-
if (this.stderrMode)
|
|
1013
|
+
async recent(limit = DEFAULT_RECENT_COUNT) {
|
|
1014
|
+
if (this.stderrMode || !this.systemDb) {
|
|
1015
|
+
return [];
|
|
1016
|
+
}
|
|
1017
1017
|
await this.flush();
|
|
1018
1018
|
try {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
return acc;
|
|
1043
|
-
}, []);
|
|
1044
|
-
} finally {
|
|
1045
|
-
await fh.close();
|
|
1046
|
-
}
|
|
1019
|
+
const db = this.systemDb.getDb();
|
|
1020
|
+
const rows = db.prepare(
|
|
1021
|
+
`
|
|
1022
|
+
SELECT * FROM audit_logs
|
|
1023
|
+
ORDER BY timestamp DESC
|
|
1024
|
+
LIMIT ?
|
|
1025
|
+
`
|
|
1026
|
+
).all(limit);
|
|
1027
|
+
return rows.map((row) => ({
|
|
1028
|
+
timestamp: row.timestamp,
|
|
1029
|
+
requestId: row.requestId,
|
|
1030
|
+
tool: row.tool,
|
|
1031
|
+
category: row.category,
|
|
1032
|
+
scope: row.scope,
|
|
1033
|
+
user: row.user,
|
|
1034
|
+
scopes: row.scopesJson ? JSON.parse(row.scopesJson) : [],
|
|
1035
|
+
durationMs: row.durationMs,
|
|
1036
|
+
success: row.success === 1,
|
|
1037
|
+
tokenEstimate: row.tokenEstimate ?? void 0,
|
|
1038
|
+
error: row.error ?? void 0,
|
|
1039
|
+
args: row.argsJson ? JSON.parse(row.argsJson) : void 0,
|
|
1040
|
+
backup: row.backupPath ?? void 0
|
|
1041
|
+
}));
|
|
1047
1042
|
} catch {
|
|
1048
1043
|
return [];
|
|
1049
1044
|
}
|
|
1050
1045
|
}
|
|
1051
1046
|
/**
|
|
1052
|
-
*
|
|
1047
|
+
* Search and filter audit entries from the SystemDb.
|
|
1053
1048
|
*/
|
|
1054
|
-
async
|
|
1055
|
-
if (this.
|
|
1049
|
+
async search(filters) {
|
|
1050
|
+
if (this.stderrMode || !this.systemDb) {
|
|
1051
|
+
return { entries: [], totalCount: 0 };
|
|
1052
|
+
}
|
|
1053
|
+
await this.flush();
|
|
1056
1054
|
try {
|
|
1057
|
-
|
|
1058
|
-
|
|
1055
|
+
const db = this.systemDb.getDb();
|
|
1056
|
+
let sql = "SELECT * FROM audit_logs WHERE 1=1";
|
|
1057
|
+
let countSql = "SELECT COUNT(*) as c FROM audit_logs WHERE 1=1";
|
|
1058
|
+
const params = [];
|
|
1059
|
+
if (filters.tool) {
|
|
1060
|
+
sql += " AND tool = ?";
|
|
1061
|
+
countSql += " AND tool = ?";
|
|
1062
|
+
params.push(filters.tool);
|
|
1063
|
+
}
|
|
1064
|
+
if (filters.category) {
|
|
1065
|
+
sql += " AND category = ?";
|
|
1066
|
+
countSql += " AND category = ?";
|
|
1067
|
+
params.push(filters.category);
|
|
1068
|
+
}
|
|
1069
|
+
if (filters.success !== void 0) {
|
|
1070
|
+
sql += " AND success = ?";
|
|
1071
|
+
countSql += " AND success = ?";
|
|
1072
|
+
params.push(filters.success ? 1 : 0);
|
|
1073
|
+
}
|
|
1074
|
+
if (filters.requestId) {
|
|
1075
|
+
sql += " AND requestId = ?";
|
|
1076
|
+
countSql += " AND requestId = ?";
|
|
1077
|
+
params.push(filters.requestId);
|
|
1078
|
+
}
|
|
1079
|
+
if (filters.fromTimestamp) {
|
|
1080
|
+
sql += " AND timestamp >= ?";
|
|
1081
|
+
countSql += " AND timestamp >= ?";
|
|
1082
|
+
params.push(filters.fromTimestamp);
|
|
1083
|
+
}
|
|
1084
|
+
if (filters.toTimestamp) {
|
|
1085
|
+
sql += " AND timestamp <= ?";
|
|
1086
|
+
countSql += " AND timestamp <= ?";
|
|
1087
|
+
params.push(filters.toTimestamp);
|
|
1088
|
+
}
|
|
1089
|
+
const totalCount = db.prepare(countSql).get(...params).c;
|
|
1090
|
+
sql += " ORDER BY timestamp DESC LIMIT ? OFFSET ?";
|
|
1091
|
+
params.push(filters.limit ?? 50);
|
|
1092
|
+
params.push(filters.offset ?? 0);
|
|
1093
|
+
const rows = db.prepare(sql).all(...params);
|
|
1094
|
+
const entries = rows.map((row) => ({
|
|
1095
|
+
timestamp: row.timestamp,
|
|
1096
|
+
requestId: row.requestId,
|
|
1097
|
+
tool: row.tool,
|
|
1098
|
+
category: row.category,
|
|
1099
|
+
scope: row.scope,
|
|
1100
|
+
user: row.user,
|
|
1101
|
+
scopes: row.scopesJson ? JSON.parse(row.scopesJson) : [],
|
|
1102
|
+
durationMs: row.durationMs,
|
|
1103
|
+
success: row.success === 1,
|
|
1104
|
+
tokenEstimate: row.tokenEstimate ?? void 0,
|
|
1105
|
+
error: row.error ?? void 0,
|
|
1106
|
+
args: row.argsJson ? JSON.parse(row.argsJson) : void 0,
|
|
1107
|
+
backup: row.backupPath ?? void 0
|
|
1108
|
+
}));
|
|
1109
|
+
return { entries, totalCount };
|
|
1059
1110
|
} catch {
|
|
1060
|
-
|
|
1111
|
+
return { entries: [], totalCount: 0 };
|
|
1061
1112
|
}
|
|
1062
1113
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1114
|
+
};
|
|
1115
|
+
var SystemDb = class {
|
|
1116
|
+
db = null;
|
|
1117
|
+
config;
|
|
1118
|
+
constructor(config) {
|
|
1119
|
+
this.config = config;
|
|
1120
|
+
}
|
|
1121
|
+
async init() {
|
|
1070
1122
|
try {
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1123
|
+
const encryptionKey = process.env["DB_ENCRYPTION_KEY"];
|
|
1124
|
+
let BetterSqlite3;
|
|
1125
|
+
if (encryptionKey) {
|
|
1126
|
+
BetterSqlite3 = (await import('better-sqlite3-multiple-ciphers')).default;
|
|
1127
|
+
} else {
|
|
1128
|
+
BetterSqlite3 = (await import('better-sqlite3')).default;
|
|
1077
1129
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1130
|
+
if (this.config.dbPath !== ":memory:") {
|
|
1131
|
+
mkdirSync(dirname(this.config.dbPath), { recursive: true });
|
|
1132
|
+
}
|
|
1133
|
+
this.db = new BetterSqlite3(this.config.dbPath);
|
|
1134
|
+
if (encryptionKey && this.config.dbPath !== ":memory:" && this.config.dbPath !== "") {
|
|
1135
|
+
const escapedKey = encryptionKey.replace(/"/g, '""');
|
|
1136
|
+
this.db.pragma(`key = "${escapedKey}"`);
|
|
1137
|
+
}
|
|
1138
|
+
this.db.pragma("journal_mode = WAL");
|
|
1139
|
+
this.db.pragma("synchronous = NORMAL");
|
|
1140
|
+
this.db.exec(`
|
|
1141
|
+
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
1142
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1143
|
+
timestamp TEXT NOT NULL,
|
|
1144
|
+
requestId TEXT NOT NULL,
|
|
1145
|
+
tool TEXT NOT NULL,
|
|
1146
|
+
category TEXT NOT NULL,
|
|
1147
|
+
scope TEXT NOT NULL,
|
|
1148
|
+
user TEXT,
|
|
1149
|
+
durationMs INTEGER NOT NULL,
|
|
1150
|
+
success INTEGER NOT NULL,
|
|
1151
|
+
tokenEstimate INTEGER,
|
|
1152
|
+
error TEXT,
|
|
1153
|
+
argsJson TEXT,
|
|
1154
|
+
scopesJson TEXT,
|
|
1155
|
+
backupPath TEXT
|
|
1156
|
+
);
|
|
1157
|
+
|
|
1158
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_timestamp ON audit_logs(timestamp);
|
|
1159
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_tool ON audit_logs(tool);
|
|
1160
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_category ON audit_logs(category);
|
|
1161
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_success ON audit_logs(success);
|
|
1162
|
+
|
|
1163
|
+
CREATE TABLE IF NOT EXISTS metrics_snapshots (
|
|
1164
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1165
|
+
timestamp TEXT NOT NULL,
|
|
1166
|
+
tool TEXT NOT NULL,
|
|
1167
|
+
calls INTEGER NOT NULL,
|
|
1168
|
+
errors INTEGER NOT NULL,
|
|
1169
|
+
p50 INTEGER NOT NULL,
|
|
1170
|
+
p95 INTEGER NOT NULL,
|
|
1171
|
+
p99 INTEGER NOT NULL,
|
|
1172
|
+
tokens INTEGER NOT NULL
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
CREATE INDEX IF NOT EXISTS idx_metrics_snapshots_timestamp ON metrics_snapshots(timestamp);
|
|
1176
|
+
`);
|
|
1177
|
+
logger.info(`System database initialized at ${this.config.dbPath}`, {
|
|
1178
|
+
module: "SYSTEM_DB"
|
|
1179
|
+
});
|
|
1180
|
+
} catch (err) {
|
|
1181
|
+
logger.error(
|
|
1182
|
+
"Failed to initialize SystemDb. better-sqlite3 may not be installed.",
|
|
1183
|
+
{
|
|
1184
|
+
module: "SYSTEM_DB",
|
|
1185
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1186
|
+
}
|
|
1187
|
+
);
|
|
1188
|
+
throw err;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
getDb() {
|
|
1192
|
+
if (!this.db) {
|
|
1193
|
+
throw new InternalError("SystemDb not initialized");
|
|
1194
|
+
}
|
|
1195
|
+
return this.db;
|
|
1196
|
+
}
|
|
1197
|
+
close() {
|
|
1198
|
+
if (this.db) {
|
|
1199
|
+
this.db.close();
|
|
1200
|
+
this.db = null;
|
|
1081
1201
|
}
|
|
1082
1202
|
}
|
|
1083
1203
|
};
|
|
@@ -1538,11 +1658,14 @@ function createAuditInterceptor(auditLogger, backupManager) {
|
|
|
1538
1658
|
...result,
|
|
1539
1659
|
_meta: { tokenEstimate: 0 }
|
|
1540
1660
|
});
|
|
1541
|
-
tokenEstimate =
|
|
1661
|
+
tokenEstimate = estimateTokens(json, "json");
|
|
1542
1662
|
} catch {
|
|
1543
1663
|
}
|
|
1544
1664
|
} else if (typeof result === "string") {
|
|
1545
|
-
|
|
1665
|
+
const isSql = /^\s*(?:SELECT|INSERT|UPDATE|DELETE|WITH|CREATE|ALTER|DROP|PRAGMA)\b/i.test(
|
|
1666
|
+
result
|
|
1667
|
+
);
|
|
1668
|
+
tokenEstimate = estimateTokens(result, isSql ? "sql" : "text");
|
|
1546
1669
|
}
|
|
1547
1670
|
return result;
|
|
1548
1671
|
} catch (err) {
|
|
@@ -1561,10 +1684,16 @@ function createAuditInterceptor(auditLogger, backupManager) {
|
|
|
1561
1684
|
...errorResult,
|
|
1562
1685
|
_meta: { tokenEstimate: 0 }
|
|
1563
1686
|
});
|
|
1564
|
-
tokenEstimate =
|
|
1687
|
+
tokenEstimate = estimateTokens(enriched, "json");
|
|
1565
1688
|
throw err;
|
|
1566
1689
|
} finally {
|
|
1567
1690
|
const durationMs = Math.round(performance.now() - start);
|
|
1691
|
+
metrics.recordToolCall(
|
|
1692
|
+
options?.logAs ?? toolName,
|
|
1693
|
+
durationMs,
|
|
1694
|
+
success,
|
|
1695
|
+
tokenEstimate ?? 0
|
|
1696
|
+
);
|
|
1568
1697
|
if (isReadScope) {
|
|
1569
1698
|
auditLogger.log({
|
|
1570
1699
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1599,280 +1728,95 @@ function createAuditInterceptor(auditLogger, backupManager) {
|
|
|
1599
1728
|
};
|
|
1600
1729
|
}
|
|
1601
1730
|
|
|
1602
|
-
// src/server/registration/
|
|
1603
|
-
function
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1731
|
+
// src/server/registration/audit-tools/helpers.ts
|
|
1732
|
+
function redactSqlLiterals(text) {
|
|
1733
|
+
return text.replace(/'(?:''|[^'])*'/g, "'***'");
|
|
1734
|
+
}
|
|
1735
|
+
function validateDdl(sql) {
|
|
1736
|
+
const cleanSql = sql.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--.*$/gm, "");
|
|
1737
|
+
const upperSql = cleanSql.toUpperCase();
|
|
1738
|
+
if (upperSql.includes("ATTACH ") || upperSql.includes("DETACH ") || upperSql.includes("PRAGMA ") || upperSql.includes("LOAD_EXTENSION(")) {
|
|
1739
|
+
throw new ValidationError(
|
|
1740
|
+
`DDL validation failed: unauthorized command or function call`
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
if (upperSql.includes(" ON MAIN.") || upperSql.includes(" ON TEMP.")) {
|
|
1744
|
+
throw new ValidationError(
|
|
1745
|
+
`DDL validation failed: trigger attempts to target a specific database`
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// src/server/registration/audit-tools/resource.ts
|
|
1751
|
+
function registerAuditResource(server, auditLogger, backupManager) {
|
|
1752
|
+
if (!auditLogger) return;
|
|
1753
|
+
server.registerResource(
|
|
1754
|
+
"sqlite_audit",
|
|
1755
|
+
"sqlite://audit",
|
|
1756
|
+
{
|
|
1757
|
+
description: "Recent audit log entries and backup statistics. Shows the last 50 tool invocations with timing, outcomes, and token estimates.",
|
|
1758
|
+
mimeType: "application/json"
|
|
1759
|
+
},
|
|
1760
|
+
async () => {
|
|
1761
|
+
metrics.recordResourceRead("sqlite://audit");
|
|
1762
|
+
const recent = await auditLogger.recent(50);
|
|
1763
|
+
const backupStats = backupManager ? await backupManager.getStats() : void 0;
|
|
1764
|
+
const payload = {
|
|
1765
|
+
entries: recent,
|
|
1766
|
+
stats: {
|
|
1767
|
+
totalEntries: recent.length,
|
|
1768
|
+
...backupStats && { backups: backupStats }
|
|
1640
1769
|
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "server_health")) {
|
|
1653
|
-
throw new InsufficientScopeError(["read"], authCtx.scopes);
|
|
1770
|
+
};
|
|
1771
|
+
const payloadStr = JSON.stringify(payload, null, 2);
|
|
1772
|
+
return {
|
|
1773
|
+
contents: [
|
|
1774
|
+
{
|
|
1775
|
+
uri: "sqlite://audit",
|
|
1776
|
+
mimeType: "application/json",
|
|
1777
|
+
text: redactSqlLiterals(payloadStr)
|
|
1778
|
+
}
|
|
1779
|
+
]
|
|
1780
|
+
};
|
|
1654
1781
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1784
|
+
function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
1785
|
+
if (!backupManager) return;
|
|
1786
|
+
server.registerTool(
|
|
1787
|
+
"sqlite_audit_list_backups",
|
|
1788
|
+
{
|
|
1789
|
+
title: "List Audit Backups",
|
|
1790
|
+
description: "List pre-mutation DDL snapshots captured before destructive operations. Returns metadata for each snapshot including timestamp, tool, target, and size.",
|
|
1791
|
+
inputSchema: AuditListBackupsSchema,
|
|
1792
|
+
outputSchema: AuditListBackupsOutputSchema,
|
|
1793
|
+
annotations: toSDK({
|
|
1794
|
+
readOnlyHint: true,
|
|
1795
|
+
destructiveHint: false,
|
|
1796
|
+
idempotentHint: true,
|
|
1797
|
+
openWorldHint: false,
|
|
1798
|
+
sensitiveHint: true
|
|
1799
|
+
})
|
|
1800
|
+
},
|
|
1801
|
+
async (args) => {
|
|
1802
|
+
const authCtx = getAuthContext();
|
|
1803
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "sqlite_audit_list_backups")) {
|
|
1804
|
+
throw new InsufficientScopeError(["admin", "full"], authCtx.scopes);
|
|
1805
|
+
}
|
|
1806
|
+
let parsed;
|
|
1661
1807
|
try {
|
|
1662
|
-
|
|
1663
|
-
health["adapters"][id] = adapterHealth;
|
|
1808
|
+
parsed = AuditListBackupsSchema.parse(args ?? {});
|
|
1664
1809
|
} catch (error) {
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
{
|
|
1676
|
-
type: "text",
|
|
1677
|
-
text: JSON.stringify(health, null, 2)
|
|
1678
|
-
}
|
|
1679
|
-
]
|
|
1680
|
-
};
|
|
1681
|
-
});
|
|
1682
|
-
const listAdaptersOpts = {
|
|
1683
|
-
title: "List Adapters",
|
|
1684
|
-
description: "List all registered database adapters",
|
|
1685
|
-
icons: SERVER_ICONS,
|
|
1686
|
-
annotations: READ_ONLY
|
|
1687
|
-
};
|
|
1688
|
-
server.registerTool("list_adapters", listAdaptersOpts, () => {
|
|
1689
|
-
const authCtx = getAuthContext();
|
|
1690
|
-
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "list_adapters")) {
|
|
1691
|
-
throw new InsufficientScopeError(["read"], authCtx.scopes);
|
|
1692
|
-
}
|
|
1693
|
-
const adapters = [];
|
|
1694
|
-
for (const [, adapter] of adaptersMap) {
|
|
1695
|
-
adapters.push({
|
|
1696
|
-
id: `${adapter.type}:default`,
|
|
1697
|
-
type: adapter.type,
|
|
1698
|
-
name: adapter.name,
|
|
1699
|
-
version: adapter.version,
|
|
1700
|
-
connected: adapter.isConnected()
|
|
1701
|
-
});
|
|
1702
|
-
}
|
|
1703
|
-
return {
|
|
1704
|
-
content: [
|
|
1705
|
-
{
|
|
1706
|
-
type: "text",
|
|
1707
|
-
text: JSON.stringify(adapters, null, 2)
|
|
1708
|
-
}
|
|
1709
|
-
]
|
|
1710
|
-
};
|
|
1711
|
-
});
|
|
1712
|
-
registerToolScopes(
|
|
1713
|
-
/* @__PURE__ */ new Map([
|
|
1714
|
-
["server_info", ["read"]],
|
|
1715
|
-
["server_health", ["read"]],
|
|
1716
|
-
["list_adapters", ["read"]]
|
|
1717
|
-
])
|
|
1718
|
-
);
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
// src/server/registration/help-resources.ts
|
|
1722
|
-
function registerHelpResources(server, toolFilter) {
|
|
1723
|
-
const gotchasContent = HELP_CONTENT.get("gotchas");
|
|
1724
|
-
if (gotchasContent) {
|
|
1725
|
-
server.registerResource(
|
|
1726
|
-
"sqlite_help",
|
|
1727
|
-
"sqlite://help",
|
|
1728
|
-
{
|
|
1729
|
-
description: "Critical gotchas, WASM vs Native comparison, and Code Mode API reference",
|
|
1730
|
-
mimeType: "text/markdown",
|
|
1731
|
-
annotations: ASSISTANT_FOCUSED
|
|
1732
|
-
},
|
|
1733
|
-
() => ({
|
|
1734
|
-
contents: [
|
|
1735
|
-
{
|
|
1736
|
-
uri: "sqlite://help",
|
|
1737
|
-
mimeType: "text/markdown",
|
|
1738
|
-
text: gotchasContent
|
|
1739
|
-
}
|
|
1740
|
-
]
|
|
1741
|
-
})
|
|
1742
|
-
);
|
|
1743
|
-
}
|
|
1744
|
-
const groupHelpKeys = [
|
|
1745
|
-
{ group: "core", key: "core" },
|
|
1746
|
-
{ group: "json", key: "json" },
|
|
1747
|
-
{ group: "text", key: "text" },
|
|
1748
|
-
{ group: "stats", key: "stats" },
|
|
1749
|
-
{ group: "vector", key: "vector" },
|
|
1750
|
-
{ group: "geo", key: "geo" },
|
|
1751
|
-
{ group: "admin", key: "admin" },
|
|
1752
|
-
{ group: "transactions", key: "transactions" },
|
|
1753
|
-
{ group: "introspection", key: "introspection" },
|
|
1754
|
-
{ group: "migration", key: "migration" }
|
|
1755
|
-
];
|
|
1756
|
-
for (const { group, key } of groupHelpKeys) {
|
|
1757
|
-
const isCodemodeOnly = toolFilter.enabledGroups.size === 1 && toolFilter.enabledGroups.has("codemode");
|
|
1758
|
-
if (!toolFilter.enabledGroups.has(group) && !isCodemodeOnly) {
|
|
1759
|
-
continue;
|
|
1760
|
-
}
|
|
1761
|
-
const content = HELP_CONTENT.get(key);
|
|
1762
|
-
if (!content) continue;
|
|
1763
|
-
server.registerResource(
|
|
1764
|
-
`sqlite_help_${key}`,
|
|
1765
|
-
`sqlite://help/${key}`,
|
|
1766
|
-
{
|
|
1767
|
-
description: `Tool reference for the ${group} tool group`,
|
|
1768
|
-
mimeType: "text/markdown",
|
|
1769
|
-
annotations: ASSISTANT_FOCUSED
|
|
1770
|
-
},
|
|
1771
|
-
() => ({
|
|
1772
|
-
contents: [
|
|
1773
|
-
{
|
|
1774
|
-
uri: `sqlite://help/${key}`,
|
|
1775
|
-
mimeType: "text/markdown",
|
|
1776
|
-
text: content
|
|
1777
|
-
}
|
|
1778
|
-
]
|
|
1779
|
-
})
|
|
1780
|
-
);
|
|
1781
|
-
}
|
|
1782
|
-
const registeredHelp = ["sqlite://help"];
|
|
1783
|
-
for (const { group, key } of groupHelpKeys) {
|
|
1784
|
-
if (toolFilter.enabledGroups.has(group)) {
|
|
1785
|
-
registeredHelp.push(`sqlite://help/${key}`);
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
logger.info(`Help resources: ${registeredHelp.join(", ")}`, {
|
|
1789
|
-
module: "SERVER"
|
|
1790
|
-
});
|
|
1791
|
-
}
|
|
1792
|
-
function redactSqlLiterals(text) {
|
|
1793
|
-
return text.replace(/'(?:''|[^'])*'/g, "'***'");
|
|
1794
|
-
}
|
|
1795
|
-
function validateDdl(sql) {
|
|
1796
|
-
const cleanSql = sql.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--.*$/gm, "");
|
|
1797
|
-
const upperSql = cleanSql.toUpperCase();
|
|
1798
|
-
if (upperSql.includes("ATTACH ") || upperSql.includes("DETACH ") || upperSql.includes("PRAGMA ") || upperSql.includes("LOAD_EXTENSION(")) {
|
|
1799
|
-
throw new Error(
|
|
1800
|
-
`DDL validation failed: unauthorized command or function call`
|
|
1801
|
-
);
|
|
1802
|
-
}
|
|
1803
|
-
if (upperSql.includes(" ON MAIN.") || upperSql.includes(" ON TEMP.")) {
|
|
1804
|
-
throw new Error(
|
|
1805
|
-
`DDL validation failed: trigger attempts to target a specific database`
|
|
1806
|
-
);
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
function registerAuditResource(server, auditLogger, backupManager) {
|
|
1810
|
-
if (!auditLogger) return;
|
|
1811
|
-
server.registerResource(
|
|
1812
|
-
"sqlite_audit",
|
|
1813
|
-
"sqlite://audit",
|
|
1814
|
-
{
|
|
1815
|
-
description: "Recent audit log entries and backup statistics. Shows the last 50 tool invocations with timing, outcomes, and token estimates.",
|
|
1816
|
-
mimeType: "application/json"
|
|
1817
|
-
},
|
|
1818
|
-
async () => {
|
|
1819
|
-
const recent = await auditLogger.recent(50);
|
|
1820
|
-
const backupStats = backupManager ? await backupManager.getStats() : void 0;
|
|
1821
|
-
const payload = {
|
|
1822
|
-
entries: recent,
|
|
1823
|
-
stats: {
|
|
1824
|
-
totalEntries: recent.length,
|
|
1825
|
-
...backupStats && { backups: backupStats }
|
|
1826
|
-
}
|
|
1827
|
-
};
|
|
1828
|
-
const payloadStr = JSON.stringify(payload, null, 2);
|
|
1829
|
-
return {
|
|
1830
|
-
contents: [
|
|
1831
|
-
{
|
|
1832
|
-
uri: "sqlite://audit",
|
|
1833
|
-
mimeType: "application/json",
|
|
1834
|
-
text: redactSqlLiterals(payloadStr)
|
|
1835
|
-
}
|
|
1836
|
-
]
|
|
1837
|
-
};
|
|
1838
|
-
}
|
|
1839
|
-
);
|
|
1840
|
-
}
|
|
1841
|
-
function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
1842
|
-
if (!backupManager) return;
|
|
1843
|
-
server.registerTool(
|
|
1844
|
-
"sqlite_audit_list_backups",
|
|
1845
|
-
{
|
|
1846
|
-
title: "List Audit Backups",
|
|
1847
|
-
description: "List pre-mutation DDL snapshots captured before destructive operations. Returns metadata for each snapshot including timestamp, tool, target, and size.",
|
|
1848
|
-
inputSchema: AuditListBackupsSchema,
|
|
1849
|
-
outputSchema: AuditListBackupsOutputSchema,
|
|
1850
|
-
annotations: {
|
|
1851
|
-
readOnlyHint: true,
|
|
1852
|
-
destructiveHint: false,
|
|
1853
|
-
idempotentHint: true,
|
|
1854
|
-
openWorldHint: false
|
|
1855
|
-
}
|
|
1856
|
-
},
|
|
1857
|
-
async (args) => {
|
|
1858
|
-
const authCtx = getAuthContext();
|
|
1859
|
-
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "sqlite_audit_list_backups")) {
|
|
1860
|
-
throw new InsufficientScopeError(["admin", "full"], authCtx.scopes);
|
|
1861
|
-
}
|
|
1862
|
-
let parsed;
|
|
1863
|
-
try {
|
|
1864
|
-
parsed = AuditListBackupsSchema.parse(args ?? {});
|
|
1865
|
-
} catch (error) {
|
|
1866
|
-
const structured = formatHandlerError(error);
|
|
1867
|
-
return {
|
|
1868
|
-
content: [
|
|
1869
|
-
{
|
|
1870
|
-
type: "text",
|
|
1871
|
-
text: JSON.stringify(structured, null, 2)
|
|
1872
|
-
}
|
|
1873
|
-
],
|
|
1874
|
-
isError: true,
|
|
1875
|
-
structuredContent: structured
|
|
1810
|
+
const structured = formatHandlerError(error);
|
|
1811
|
+
return {
|
|
1812
|
+
content: [
|
|
1813
|
+
{
|
|
1814
|
+
type: "text",
|
|
1815
|
+
text: JSON.stringify(structured, null, 2)
|
|
1816
|
+
}
|
|
1817
|
+
],
|
|
1818
|
+
isError: true,
|
|
1819
|
+
structuredContent: structured
|
|
1876
1820
|
};
|
|
1877
1821
|
}
|
|
1878
1822
|
const limit = parsed.limit ?? 10;
|
|
@@ -1914,12 +1858,13 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
1914
1858
|
filename: z.string().default("").describe("Snapshot filename from sqlite_audit_list_backups results")
|
|
1915
1859
|
}),
|
|
1916
1860
|
outputSchema: AuditGetBackupOutputSchema,
|
|
1917
|
-
annotations: {
|
|
1861
|
+
annotations: toSDK({
|
|
1918
1862
|
readOnlyHint: true,
|
|
1919
1863
|
destructiveHint: false,
|
|
1920
1864
|
idempotentHint: true,
|
|
1921
|
-
openWorldHint: false
|
|
1922
|
-
|
|
1865
|
+
openWorldHint: false,
|
|
1866
|
+
sensitiveHint: true
|
|
1867
|
+
})
|
|
1923
1868
|
},
|
|
1924
1869
|
async (args) => {
|
|
1925
1870
|
const authCtx = getAuthContext();
|
|
@@ -1947,7 +1892,9 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
1947
1892
|
if (!snapshot) {
|
|
1948
1893
|
const errRes = {
|
|
1949
1894
|
success: false,
|
|
1950
|
-
error: `Snapshot not found: ${filename}
|
|
1895
|
+
error: `Snapshot not found: ${filename}`,
|
|
1896
|
+
code: "NOT_FOUND",
|
|
1897
|
+
category: "not_found"
|
|
1951
1898
|
};
|
|
1952
1899
|
return {
|
|
1953
1900
|
content: [
|
|
@@ -1979,12 +1926,13 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
1979
1926
|
title: "Cleanup Audit Backups",
|
|
1980
1927
|
description: "Apply retention policy to audit backup snapshots. Deletes snapshots exceeding age or count limits.",
|
|
1981
1928
|
outputSchema: AuditCleanupOutputSchema,
|
|
1982
|
-
annotations: {
|
|
1929
|
+
annotations: toSDK({
|
|
1983
1930
|
readOnlyHint: false,
|
|
1984
1931
|
destructiveHint: true,
|
|
1985
1932
|
idempotentHint: true,
|
|
1986
|
-
openWorldHint: false
|
|
1987
|
-
|
|
1933
|
+
openWorldHint: false,
|
|
1934
|
+
sensitiveHint: true
|
|
1935
|
+
})
|
|
1988
1936
|
},
|
|
1989
1937
|
async () => {
|
|
1990
1938
|
const authCtx = getAuthContext();
|
|
@@ -2019,12 +1967,13 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
2019
1967
|
)
|
|
2020
1968
|
}),
|
|
2021
1969
|
outputSchema: AuditDiffBackupOutputSchema,
|
|
2022
|
-
annotations: {
|
|
1970
|
+
annotations: toSDK({
|
|
2023
1971
|
readOnlyHint: true,
|
|
2024
1972
|
destructiveHint: false,
|
|
2025
1973
|
idempotentHint: true,
|
|
2026
|
-
openWorldHint: false
|
|
2027
|
-
|
|
1974
|
+
openWorldHint: false,
|
|
1975
|
+
sensitiveHint: true
|
|
1976
|
+
})
|
|
2028
1977
|
},
|
|
2029
1978
|
async (args) => {
|
|
2030
1979
|
const authCtx = getAuthContext();
|
|
@@ -2052,7 +2001,9 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
2052
2001
|
if (!snapshot) {
|
|
2053
2002
|
const errRes = {
|
|
2054
2003
|
success: false,
|
|
2055
|
-
error: `Snapshot not found: ${filename}
|
|
2004
|
+
error: `Snapshot not found: ${filename}`,
|
|
2005
|
+
code: "NOT_FOUND",
|
|
2006
|
+
category: "not_found"
|
|
2056
2007
|
};
|
|
2057
2008
|
return {
|
|
2058
2009
|
content: [
|
|
@@ -2069,7 +2020,9 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
2069
2020
|
if (!adapter) {
|
|
2070
2021
|
const errRes = {
|
|
2071
2022
|
success: false,
|
|
2072
|
-
error: "No connected adapter available"
|
|
2023
|
+
error: "No connected adapter available",
|
|
2024
|
+
code: "INTERNAL_ERROR",
|
|
2025
|
+
category: "internal"
|
|
2073
2026
|
};
|
|
2074
2027
|
return {
|
|
2075
2028
|
content: [
|
|
@@ -2158,19 +2111,16 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
2158
2111
|
structuredContent: result
|
|
2159
2112
|
};
|
|
2160
2113
|
} catch (error) {
|
|
2161
|
-
const
|
|
2162
|
-
success: false,
|
|
2163
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2164
|
-
};
|
|
2114
|
+
const structured = formatHandlerError(error);
|
|
2165
2115
|
return {
|
|
2166
2116
|
content: [
|
|
2167
2117
|
{
|
|
2168
2118
|
type: "text",
|
|
2169
|
-
text: JSON.stringify(
|
|
2119
|
+
text: JSON.stringify(structured, null, 2)
|
|
2170
2120
|
}
|
|
2171
2121
|
],
|
|
2172
2122
|
isError: true,
|
|
2173
|
-
structuredContent:
|
|
2123
|
+
structuredContent: structured
|
|
2174
2124
|
};
|
|
2175
2125
|
}
|
|
2176
2126
|
}
|
|
@@ -2185,12 +2135,13 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
2185
2135
|
dryRun: z.unknown().optional().describe("If true, returns the DDL without executing it (boolean)")
|
|
2186
2136
|
}),
|
|
2187
2137
|
outputSchema: AuditRestoreBackupOutputSchema,
|
|
2188
|
-
annotations: {
|
|
2138
|
+
annotations: toSDK({
|
|
2189
2139
|
readOnlyHint: false,
|
|
2190
2140
|
destructiveHint: true,
|
|
2191
2141
|
idempotentHint: false,
|
|
2192
|
-
openWorldHint: false
|
|
2193
|
-
|
|
2142
|
+
openWorldHint: false,
|
|
2143
|
+
sensitiveHint: true
|
|
2144
|
+
})
|
|
2194
2145
|
},
|
|
2195
2146
|
async (args) => {
|
|
2196
2147
|
const authCtx = getAuthContext();
|
|
@@ -2223,7 +2174,9 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
2223
2174
|
if (!snapshot) {
|
|
2224
2175
|
const errRes = {
|
|
2225
2176
|
success: false,
|
|
2226
|
-
error: `Snapshot not found: ${filename}
|
|
2177
|
+
error: `Snapshot not found: ${filename}`,
|
|
2178
|
+
code: "NOT_FOUND",
|
|
2179
|
+
category: "not_found"
|
|
2227
2180
|
};
|
|
2228
2181
|
return {
|
|
2229
2182
|
content: [
|
|
@@ -2240,7 +2193,9 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
2240
2193
|
if (!ddl.trim()) {
|
|
2241
2194
|
const errRes = {
|
|
2242
2195
|
success: false,
|
|
2243
|
-
error: "Snapshot contains no DDL statements"
|
|
2196
|
+
error: "Snapshot contains no DDL statements",
|
|
2197
|
+
code: "VALIDATION_ERROR",
|
|
2198
|
+
category: "validation"
|
|
2244
2199
|
};
|
|
2245
2200
|
return {
|
|
2246
2201
|
content: [
|
|
@@ -2257,7 +2212,9 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
2257
2212
|
if (!adapter) {
|
|
2258
2213
|
const errRes = {
|
|
2259
2214
|
success: false,
|
|
2260
|
-
error: "No connected adapter available"
|
|
2215
|
+
error: "No connected adapter available",
|
|
2216
|
+
code: "INTERNAL_ERROR",
|
|
2217
|
+
category: "internal"
|
|
2261
2218
|
};
|
|
2262
2219
|
return {
|
|
2263
2220
|
content: [
|
|
@@ -2334,19 +2291,16 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
2334
2291
|
structuredContent: result
|
|
2335
2292
|
};
|
|
2336
2293
|
} catch (error) {
|
|
2337
|
-
const
|
|
2338
|
-
success: false,
|
|
2339
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2340
|
-
};
|
|
2294
|
+
const structured = formatHandlerError(error);
|
|
2341
2295
|
return {
|
|
2342
2296
|
content: [
|
|
2343
2297
|
{
|
|
2344
2298
|
type: "text",
|
|
2345
|
-
text: JSON.stringify(
|
|
2299
|
+
text: JSON.stringify(structured, null, 2)
|
|
2346
2300
|
}
|
|
2347
2301
|
],
|
|
2348
2302
|
isError: true,
|
|
2349
|
-
structuredContent:
|
|
2303
|
+
structuredContent: structured
|
|
2350
2304
|
};
|
|
2351
2305
|
}
|
|
2352
2306
|
}
|
|
@@ -2366,46 +2320,626 @@ function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
|
2366
2320
|
);
|
|
2367
2321
|
}
|
|
2368
2322
|
|
|
2369
|
-
// src/server/
|
|
2370
|
-
|
|
2371
|
-
if (
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2323
|
+
// src/server/registration/audit-tools/search.ts
|
|
2324
|
+
function registerAuditSearchTool(server, auditLogger) {
|
|
2325
|
+
if (!auditLogger) return;
|
|
2326
|
+
server.registerTool(
|
|
2327
|
+
"sqlite_audit_search",
|
|
2328
|
+
{
|
|
2329
|
+
title: "Search Audit Log",
|
|
2330
|
+
description: "Search and filter structured audit logs from the System Database. Returns recent tool invocations, outcomes, token estimates, and parameters.",
|
|
2331
|
+
inputSchema: AuditSearchSchema,
|
|
2332
|
+
outputSchema: AuditSearchOutputSchema,
|
|
2333
|
+
annotations: toSDK({
|
|
2334
|
+
readOnlyHint: true,
|
|
2335
|
+
destructiveHint: false,
|
|
2336
|
+
idempotentHint: true,
|
|
2337
|
+
openWorldHint: false,
|
|
2338
|
+
sensitiveHint: true
|
|
2339
|
+
})
|
|
2340
|
+
},
|
|
2341
|
+
async (args) => {
|
|
2342
|
+
try {
|
|
2343
|
+
const authCtx = getAuthContext();
|
|
2344
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "sqlite_audit_search")) {
|
|
2345
|
+
throw new InsufficientScopeError(["admin", "full"], authCtx.scopes);
|
|
2346
|
+
}
|
|
2347
|
+
const parsed = AuditSearchSchema.parse(args ?? {});
|
|
2348
|
+
const { entries, totalCount } = await auditLogger.search(parsed);
|
|
2349
|
+
const result = {
|
|
2350
|
+
success: true,
|
|
2351
|
+
entries,
|
|
2352
|
+
count: entries.length,
|
|
2353
|
+
totalCount
|
|
2354
|
+
};
|
|
2355
|
+
const tokenEstimate = Math.ceil(
|
|
2356
|
+
Buffer.byteLength(JSON.stringify(result), "utf8") / 4
|
|
2384
2357
|
);
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2358
|
+
return {
|
|
2359
|
+
content: [
|
|
2360
|
+
{
|
|
2361
|
+
type: "text",
|
|
2362
|
+
text: JSON.stringify(
|
|
2363
|
+
{ ...result, _meta: { tokenEstimate } },
|
|
2364
|
+
null,
|
|
2365
|
+
2
|
|
2366
|
+
)
|
|
2367
|
+
}
|
|
2368
|
+
],
|
|
2369
|
+
structuredContent: result
|
|
2390
2370
|
};
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
const structured = formatHandlerError(error);
|
|
2373
|
+
return {
|
|
2374
|
+
content: [
|
|
2375
|
+
{
|
|
2376
|
+
type: "text",
|
|
2377
|
+
text: JSON.stringify(structured, null, 2)
|
|
2378
|
+
}
|
|
2379
|
+
],
|
|
2380
|
+
isError: true,
|
|
2381
|
+
structuredContent: structured
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
);
|
|
2386
|
+
registerToolScopes(/* @__PURE__ */ new Map([["sqlite_audit_search", ["admin", "full"]]]));
|
|
2387
|
+
logger.info("Registered audit search tool: sqlite_audit_search", {
|
|
2388
|
+
module: "AUDIT"
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// src/server/registration/built-in-tools.ts
|
|
2393
|
+
function registerBuiltInTools(server, adaptersMap, config, toolFilter) {
|
|
2394
|
+
const serverInfoOpts = {
|
|
2395
|
+
title: "Server Info",
|
|
2396
|
+
description: "Get information about the db-mcp server and registered adapters",
|
|
2397
|
+
icons: SERVER_ICONS,
|
|
2398
|
+
annotations: READ_ONLY
|
|
2399
|
+
};
|
|
2400
|
+
server.registerTool("server_info", serverInfoOpts, () => {
|
|
2401
|
+
const authCtx = getAuthContext();
|
|
2402
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "server_info")) {
|
|
2403
|
+
throw new InsufficientScopeError(["read"], authCtx.scopes);
|
|
2404
|
+
}
|
|
2405
|
+
const adapterInfo = [];
|
|
2406
|
+
for (const [id, adapter] of adaptersMap) {
|
|
2407
|
+
adapterInfo.push({
|
|
2408
|
+
id,
|
|
2409
|
+
...adapter.getInfo()
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
return {
|
|
2413
|
+
content: [
|
|
2414
|
+
{
|
|
2415
|
+
type: "text",
|
|
2416
|
+
text: JSON.stringify(
|
|
2417
|
+
{
|
|
2418
|
+
name: config.name,
|
|
2419
|
+
version: config.version,
|
|
2420
|
+
transport: config.transport,
|
|
2421
|
+
adapters: adapterInfo,
|
|
2422
|
+
toolFilter: {
|
|
2423
|
+
raw: toolFilter.raw,
|
|
2424
|
+
enabledGroups: [...toolFilter.enabledGroups]
|
|
2425
|
+
}
|
|
2426
|
+
},
|
|
2427
|
+
null,
|
|
2428
|
+
2
|
|
2429
|
+
)
|
|
2430
|
+
}
|
|
2431
|
+
]
|
|
2432
|
+
};
|
|
2433
|
+
});
|
|
2434
|
+
const healthOpts = {
|
|
2435
|
+
title: "Server Health",
|
|
2436
|
+
description: "Check health status of all database connections",
|
|
2437
|
+
icons: SERVER_ICONS,
|
|
2438
|
+
annotations: READ_ONLY
|
|
2439
|
+
};
|
|
2440
|
+
server.registerTool("server_health", healthOpts, async () => {
|
|
2441
|
+
const authCtx = getAuthContext();
|
|
2442
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "server_health")) {
|
|
2443
|
+
throw new InsufficientScopeError(["read"], authCtx.scopes);
|
|
2444
|
+
}
|
|
2445
|
+
const health = {
|
|
2446
|
+
server: "healthy",
|
|
2447
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2448
|
+
adapters: {}
|
|
2449
|
+
};
|
|
2450
|
+
for (const [id, adapter] of adaptersMap) {
|
|
2451
|
+
try {
|
|
2452
|
+
const adapterHealth = await adapter.getHealth();
|
|
2453
|
+
health["adapters"][id] = adapterHealth;
|
|
2454
|
+
} catch (error) {
|
|
2455
|
+
let errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
2456
|
+
errorMsg = errorMsg.replace(/(:[^:@/]+@)/g, ":***@");
|
|
2457
|
+
health["adapters"][id] = {
|
|
2458
|
+
connected: false,
|
|
2459
|
+
error: errorMsg
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
return {
|
|
2464
|
+
content: [
|
|
2465
|
+
{
|
|
2466
|
+
type: "text",
|
|
2467
|
+
text: JSON.stringify(health, null, 2)
|
|
2468
|
+
}
|
|
2469
|
+
]
|
|
2470
|
+
};
|
|
2471
|
+
});
|
|
2472
|
+
const listAdaptersOpts = {
|
|
2473
|
+
title: "List Adapters",
|
|
2474
|
+
description: "List all registered database adapters",
|
|
2475
|
+
icons: SERVER_ICONS,
|
|
2476
|
+
annotations: READ_ONLY
|
|
2477
|
+
};
|
|
2478
|
+
server.registerTool("list_adapters", listAdaptersOpts, () => {
|
|
2479
|
+
const authCtx = getAuthContext();
|
|
2480
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "list_adapters")) {
|
|
2481
|
+
throw new InsufficientScopeError(["read"], authCtx.scopes);
|
|
2482
|
+
}
|
|
2483
|
+
const adapters = [];
|
|
2484
|
+
for (const [, adapter] of adaptersMap) {
|
|
2485
|
+
adapters.push({
|
|
2486
|
+
id: `${adapter.type}:default`,
|
|
2487
|
+
type: adapter.type,
|
|
2488
|
+
name: adapter.name,
|
|
2489
|
+
version: adapter.version,
|
|
2490
|
+
connected: adapter.isConnected()
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
return {
|
|
2494
|
+
content: [
|
|
2495
|
+
{
|
|
2496
|
+
type: "text",
|
|
2497
|
+
text: JSON.stringify(adapters, null, 2)
|
|
2498
|
+
}
|
|
2499
|
+
]
|
|
2500
|
+
};
|
|
2501
|
+
});
|
|
2502
|
+
registerToolScopes(
|
|
2503
|
+
/* @__PURE__ */ new Map([
|
|
2504
|
+
["server_info", ["read"]],
|
|
2505
|
+
["server_health", ["read"]],
|
|
2506
|
+
["list_adapters", ["read"]]
|
|
2507
|
+
])
|
|
2508
|
+
);
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// src/server/registration/help-resources.ts
|
|
2512
|
+
function registerHelpResources(server, toolFilter) {
|
|
2513
|
+
const gotchasContent = HELP_CONTENT.get("gotchas");
|
|
2514
|
+
if (gotchasContent) {
|
|
2515
|
+
server.registerResource(
|
|
2516
|
+
"sqlite_help",
|
|
2517
|
+
"sqlite://help",
|
|
2518
|
+
{
|
|
2519
|
+
description: "Critical gotchas, WASM vs Native comparison, and Code Mode API reference",
|
|
2520
|
+
mimeType: "text/markdown",
|
|
2521
|
+
annotations: ASSISTANT_FOCUSED
|
|
2522
|
+
},
|
|
2523
|
+
() => {
|
|
2524
|
+
metrics.recordResourceRead("sqlite://help");
|
|
2525
|
+
return {
|
|
2526
|
+
contents: [
|
|
2527
|
+
{
|
|
2528
|
+
uri: "sqlite://help",
|
|
2529
|
+
mimeType: "text/markdown",
|
|
2530
|
+
text: gotchasContent
|
|
2531
|
+
}
|
|
2532
|
+
]
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
const groupHelpKeys = [
|
|
2538
|
+
{ group: "core", key: "core" },
|
|
2539
|
+
{ group: "json", key: "json" },
|
|
2540
|
+
{ group: "text", key: "text" },
|
|
2541
|
+
{ group: "stats", key: "stats" },
|
|
2542
|
+
{ group: "vector", key: "vector" },
|
|
2543
|
+
{ group: "geo", key: "geo" },
|
|
2544
|
+
{ group: "admin", key: "admin" },
|
|
2545
|
+
{ group: "transactions", key: "transactions" },
|
|
2546
|
+
{ group: "introspection", key: "introspection" },
|
|
2547
|
+
{ group: "migration", key: "migration" }
|
|
2548
|
+
];
|
|
2549
|
+
for (const { group, key } of groupHelpKeys) {
|
|
2550
|
+
const isCodemodeOnly = toolFilter.enabledGroups.size === 1 && toolFilter.enabledGroups.has("codemode");
|
|
2551
|
+
if (!toolFilter.enabledGroups.has(group) && !isCodemodeOnly) {
|
|
2552
|
+
continue;
|
|
2553
|
+
}
|
|
2554
|
+
const content = HELP_CONTENT.get(key);
|
|
2555
|
+
if (!content) continue;
|
|
2556
|
+
server.registerResource(
|
|
2557
|
+
`sqlite_help_${key}`,
|
|
2558
|
+
`sqlite://help/${key}`,
|
|
2559
|
+
{
|
|
2560
|
+
description: `Tool reference for the ${group} tool group`,
|
|
2561
|
+
mimeType: "text/markdown",
|
|
2562
|
+
annotations: ASSISTANT_FOCUSED
|
|
2563
|
+
},
|
|
2564
|
+
() => {
|
|
2565
|
+
metrics.recordResourceRead(`sqlite://help/${key}`);
|
|
2566
|
+
return {
|
|
2567
|
+
contents: [
|
|
2568
|
+
{
|
|
2569
|
+
uri: `sqlite://help/${key}`,
|
|
2570
|
+
mimeType: "text/markdown",
|
|
2571
|
+
text: content
|
|
2572
|
+
}
|
|
2573
|
+
]
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
);
|
|
2577
|
+
}
|
|
2578
|
+
const registeredHelp = ["sqlite://help"];
|
|
2579
|
+
for (const { group, key } of groupHelpKeys) {
|
|
2580
|
+
if (toolFilter.enabledGroups.has(group)) {
|
|
2581
|
+
registeredHelp.push(`sqlite://help/${key}`);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
logger.info(`Help resources: ${registeredHelp.join(", ")}`, {
|
|
2585
|
+
module: "SERVER"
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
// src/server/registration/observability-resources.ts
|
|
2590
|
+
function registerObservabilityResources(server) {
|
|
2591
|
+
server.registerResource(
|
|
2592
|
+
"sqlite://metrics",
|
|
2593
|
+
"sqlite://metrics",
|
|
2594
|
+
{
|
|
2595
|
+
description: "Returns in-memory streaming metrics including p50/p95/p99 latency percentiles and token usage.",
|
|
2596
|
+
mimeType: "application/json"
|
|
2597
|
+
},
|
|
2598
|
+
() => {
|
|
2599
|
+
const summary = metrics.getSummary();
|
|
2600
|
+
return {
|
|
2601
|
+
contents: [
|
|
2602
|
+
{
|
|
2603
|
+
uri: "sqlite://metrics",
|
|
2604
|
+
mimeType: "application/json",
|
|
2605
|
+
text: JSON.stringify(summary, null, 2)
|
|
2606
|
+
}
|
|
2607
|
+
]
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2612
|
+
function registerAdminTools(server) {
|
|
2613
|
+
server.registerTool(
|
|
2614
|
+
"sqlite_server_config",
|
|
2615
|
+
{
|
|
2616
|
+
title: "Server Configuration",
|
|
2617
|
+
description: "Get or update runtime configuration values for the server. Currently supports updating the log level.",
|
|
2618
|
+
inputSchema: z.object({
|
|
2619
|
+
action: z.enum(["get", "set"]).describe("Whether to get or set the configuration value"),
|
|
2620
|
+
setting: z.enum(["logLevel"]).optional().describe("The setting to modify"),
|
|
2621
|
+
value: z.string().optional().describe(
|
|
2622
|
+
"The new value for the setting (e.g., 'debug', 'info', 'warning')"
|
|
2623
|
+
)
|
|
2624
|
+
}),
|
|
2625
|
+
annotations: toSDK({
|
|
2626
|
+
readOnlyHint: false,
|
|
2627
|
+
destructiveHint: false,
|
|
2628
|
+
idempotentHint: false,
|
|
2629
|
+
openWorldHint: false,
|
|
2630
|
+
sensitiveHint: true
|
|
2631
|
+
})
|
|
2632
|
+
},
|
|
2633
|
+
(args) => {
|
|
2634
|
+
const authCtx = getAuthContext();
|
|
2635
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "sqlite_server_config")) {
|
|
2636
|
+
throw new InsufficientScopeError(["admin"], authCtx.scopes);
|
|
2637
|
+
}
|
|
2638
|
+
try {
|
|
2639
|
+
const parsed = z.object({
|
|
2640
|
+
action: z.enum(["get", "set"]),
|
|
2641
|
+
setting: z.enum(["logLevel"]).optional(),
|
|
2642
|
+
value: z.string().optional()
|
|
2643
|
+
}).parse(args ?? {});
|
|
2644
|
+
const { action, setting, value } = parsed;
|
|
2645
|
+
if (action === "get") {
|
|
2646
|
+
return {
|
|
2647
|
+
content: [
|
|
2648
|
+
{
|
|
2649
|
+
type: "text",
|
|
2650
|
+
text: JSON.stringify(
|
|
2651
|
+
{
|
|
2652
|
+
success: true,
|
|
2653
|
+
config: {
|
|
2654
|
+
logLevel: logger.getLevel()
|
|
2655
|
+
}
|
|
2656
|
+
},
|
|
2657
|
+
null,
|
|
2658
|
+
2
|
|
2659
|
+
)
|
|
2660
|
+
}
|
|
2661
|
+
]
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
if (action === "set") {
|
|
2665
|
+
if (setting === "logLevel" && value) {
|
|
2666
|
+
const validLevels = [
|
|
2667
|
+
"debug",
|
|
2668
|
+
"info",
|
|
2669
|
+
"notice",
|
|
2670
|
+
"warning",
|
|
2671
|
+
"error",
|
|
2672
|
+
"critical",
|
|
2673
|
+
"alert",
|
|
2674
|
+
"emergency"
|
|
2675
|
+
];
|
|
2676
|
+
if (!validLevels.includes(value.toLowerCase())) {
|
|
2677
|
+
return {
|
|
2678
|
+
content: [
|
|
2679
|
+
{
|
|
2680
|
+
type: "text",
|
|
2681
|
+
text: JSON.stringify(
|
|
2682
|
+
{
|
|
2683
|
+
success: false,
|
|
2684
|
+
error: `Invalid log level: ${value}. Must be one of: ${validLevels.join(", ")}`,
|
|
2685
|
+
code: "INVALID_CONFIG",
|
|
2686
|
+
category: "validation"
|
|
2687
|
+
},
|
|
2688
|
+
null,
|
|
2689
|
+
2
|
|
2690
|
+
)
|
|
2691
|
+
}
|
|
2692
|
+
],
|
|
2693
|
+
isError: true
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
logger.setLevel(value.toLowerCase());
|
|
2697
|
+
logger.info(
|
|
2698
|
+
`Log level dynamically changed to ${value} via sqlite_server_config tool`,
|
|
2699
|
+
{
|
|
2700
|
+
module: "SERVER"
|
|
2701
|
+
}
|
|
2702
|
+
);
|
|
2703
|
+
return {
|
|
2704
|
+
content: [
|
|
2705
|
+
{
|
|
2706
|
+
type: "text",
|
|
2707
|
+
text: JSON.stringify(
|
|
2708
|
+
{
|
|
2709
|
+
success: true,
|
|
2710
|
+
message: `Log level successfully updated to ${value}`
|
|
2711
|
+
},
|
|
2712
|
+
null,
|
|
2713
|
+
2
|
|
2714
|
+
)
|
|
2715
|
+
}
|
|
2716
|
+
]
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
return {
|
|
2720
|
+
content: [
|
|
2721
|
+
{
|
|
2722
|
+
type: "text",
|
|
2723
|
+
text: JSON.stringify(
|
|
2724
|
+
{
|
|
2725
|
+
success: false,
|
|
2726
|
+
error: "Missing setting or value for set action",
|
|
2727
|
+
code: "INVALID_CONFIG",
|
|
2728
|
+
category: "validation"
|
|
2729
|
+
},
|
|
2730
|
+
null,
|
|
2731
|
+
2
|
|
2732
|
+
)
|
|
2733
|
+
}
|
|
2734
|
+
],
|
|
2735
|
+
isError: true
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2738
|
+
return {
|
|
2739
|
+
content: [
|
|
2740
|
+
{
|
|
2741
|
+
type: "text",
|
|
2742
|
+
text: JSON.stringify({
|
|
2743
|
+
success: false,
|
|
2744
|
+
error: "Invalid action",
|
|
2745
|
+
code: "INVALID_CONFIG",
|
|
2746
|
+
category: "validation"
|
|
2747
|
+
})
|
|
2748
|
+
}
|
|
2749
|
+
],
|
|
2750
|
+
isError: true
|
|
2751
|
+
};
|
|
2752
|
+
} catch (error) {
|
|
2753
|
+
const structured = formatHandlerError(error);
|
|
2754
|
+
return {
|
|
2755
|
+
content: [
|
|
2756
|
+
{
|
|
2757
|
+
type: "text",
|
|
2758
|
+
text: JSON.stringify(structured, null, 2)
|
|
2759
|
+
}
|
|
2760
|
+
],
|
|
2761
|
+
isError: true,
|
|
2762
|
+
structuredContent: structured
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
);
|
|
2767
|
+
registerToolScopes(/* @__PURE__ */ new Map([["sqlite_server_config", ["admin"]]]));
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
// src/server/subscription-manager.ts
|
|
2771
|
+
var SubscriptionManager = class {
|
|
2772
|
+
server;
|
|
2773
|
+
// Map of URI string -> Set of Session IDs
|
|
2774
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
2775
|
+
constructor(server) {
|
|
2776
|
+
this.server = server;
|
|
2777
|
+
}
|
|
2778
|
+
/**
|
|
2779
|
+
* Subscribe a session to a resource URI.
|
|
2780
|
+
*/
|
|
2781
|
+
subscribe(uri, sessionId) {
|
|
2782
|
+
if (!sessionId) {
|
|
2783
|
+
logger.debug(
|
|
2784
|
+
`Subscription requested for ${uri} but no sessionId provided. Assuming stateless transport.`
|
|
2785
|
+
);
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
let sessions = this.subscriptions.get(uri);
|
|
2789
|
+
if (!sessions) {
|
|
2790
|
+
sessions = /* @__PURE__ */ new Set();
|
|
2791
|
+
this.subscriptions.set(uri, sessions);
|
|
2792
|
+
}
|
|
2793
|
+
if (!sessions.has(sessionId)) {
|
|
2794
|
+
sessions.add(sessionId);
|
|
2795
|
+
logger.debug(`Session ${sessionId} subscribed to resource ${uri}`, {
|
|
2796
|
+
module: "SERVER"
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* Unsubscribe a session from a resource URI.
|
|
2802
|
+
*/
|
|
2803
|
+
unsubscribe(uri, sessionId) {
|
|
2804
|
+
if (!sessionId) {
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
2807
|
+
const sessions = this.subscriptions.get(uri);
|
|
2808
|
+
if (sessions?.has(sessionId)) {
|
|
2809
|
+
sessions.delete(sessionId);
|
|
2810
|
+
logger.debug(`Session ${sessionId} unsubscribed from resource ${uri}`, {
|
|
2811
|
+
module: "SERVER"
|
|
2812
|
+
});
|
|
2813
|
+
if (sessions.size === 0) {
|
|
2814
|
+
this.subscriptions.delete(uri);
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* Unsubscribe a session from all resources (useful on transport disconnect).
|
|
2820
|
+
*/
|
|
2821
|
+
unsubscribeSession(sessionId) {
|
|
2822
|
+
let count = 0;
|
|
2823
|
+
for (const [uri, sessions] of this.subscriptions.entries()) {
|
|
2824
|
+
if (sessions.has(sessionId)) {
|
|
2825
|
+
sessions.delete(sessionId);
|
|
2826
|
+
count++;
|
|
2827
|
+
if (sessions.size === 0) {
|
|
2828
|
+
this.subscriptions.delete(uri);
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
if (count > 0) {
|
|
2833
|
+
logger.debug(
|
|
2834
|
+
`Session ${sessionId} disconnected, removed ${count} subscriptions`,
|
|
2835
|
+
{ module: "SERVER" }
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
/**
|
|
2840
|
+
* Notifies all subscribed clients that a resource has changed.
|
|
2841
|
+
*/
|
|
2842
|
+
async notifyResourceUpdated(uri) {
|
|
2843
|
+
if (!this.hasSubscribers(uri)) {
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
try {
|
|
2847
|
+
await this.server.server.sendResourceUpdated({ uri });
|
|
2848
|
+
logger.debug(`Notified subscribers of update to ${uri}`, {
|
|
2849
|
+
module: "SERVER"
|
|
2850
|
+
});
|
|
2851
|
+
} catch (error) {
|
|
2852
|
+
logger.error(`Failed to notify subscribers for ${uri}`, {
|
|
2853
|
+
module: "SERVER",
|
|
2854
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
2855
|
+
});
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Notifies subscribers of schema-related resources (schema, tables, and specific tables).
|
|
2860
|
+
*/
|
|
2861
|
+
async notifySchemaSubscribers() {
|
|
2862
|
+
const urisToNotify = /* @__PURE__ */ new Set([
|
|
2863
|
+
"sqlite://schema",
|
|
2864
|
+
"sqlite://tables"
|
|
2865
|
+
]);
|
|
2866
|
+
for (const uri of this.subscriptions.keys()) {
|
|
2867
|
+
if (uri.startsWith("sqlite://table/")) {
|
|
2868
|
+
urisToNotify.add(uri);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
const promises = Array.from(urisToNotify).map(
|
|
2872
|
+
(uri) => this.notifyResourceUpdated(uri)
|
|
2873
|
+
);
|
|
2874
|
+
await Promise.allSettled(promises);
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Check if there are any active subscribers for a specific URI.
|
|
2878
|
+
*/
|
|
2879
|
+
hasSubscribers(uri) {
|
|
2880
|
+
const sessions = this.subscriptions.get(uri);
|
|
2881
|
+
return sessions ? sessions.size > 0 : false;
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
|
|
2885
|
+
// src/server/mcp-server.ts
|
|
2886
|
+
var proto = McpServer.prototype;
|
|
2887
|
+
if (typeof proto["createToolError"] === "function") {
|
|
2888
|
+
const originalCreateToolError = proto["createToolError"];
|
|
2889
|
+
proto["createToolError"] = function(errorMessage) {
|
|
2890
|
+
const result = originalCreateToolError.call(this, errorMessage);
|
|
2891
|
+
if (result.content?.[0]?.type === "text") {
|
|
2892
|
+
const rawError = result.content[0].text;
|
|
2893
|
+
if (rawError.includes("Input validation error")) {
|
|
2894
|
+
const cleanError = rawError.replace(
|
|
2895
|
+
/^MCP error -32602: Input validation error: /,
|
|
2896
|
+
"Validation error: "
|
|
2897
|
+
);
|
|
2898
|
+
const structured = {
|
|
2899
|
+
success: false,
|
|
2900
|
+
error: cleanError,
|
|
2901
|
+
code: "VALIDATION_ERROR",
|
|
2902
|
+
category: ErrorCategory.VALIDATION
|
|
2903
|
+
};
|
|
2904
|
+
result.content[0].text = JSON.stringify(structured, null, 2);
|
|
2905
|
+
result.isError = true;
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
return result;
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
var DbMcpServer = class {
|
|
2912
|
+
server;
|
|
2913
|
+
adapters = /* @__PURE__ */ new Map();
|
|
2914
|
+
toolFilter;
|
|
2915
|
+
config;
|
|
2916
|
+
auditLogger = null;
|
|
2404
2917
|
backupManager = null;
|
|
2918
|
+
systemDb = null;
|
|
2405
2919
|
auditInterceptor = null;
|
|
2406
2920
|
auditInitPromise = null;
|
|
2921
|
+
subscriptionManager;
|
|
2407
2922
|
constructor(config) {
|
|
2408
2923
|
this.config = config;
|
|
2924
|
+
let allowedIoRoots = config.allowedIoRoots;
|
|
2925
|
+
if (!allowedIoRoots) {
|
|
2926
|
+
if (config.transport === "http") {
|
|
2927
|
+
const errorMsg = `FATAL: Refusing to bind HTTP transport without explicit ALLOWED_IO_ROOTS. You MUST specify --allowed-io-roots (or ALLOWED_IO_ROOTS env var) to prevent ambient filesystem authority.`;
|
|
2928
|
+
logger.error(errorMsg, { module: "SERVER" });
|
|
2929
|
+
process.exit(1);
|
|
2930
|
+
}
|
|
2931
|
+
allowedIoRoots = [];
|
|
2932
|
+
logger.warning(
|
|
2933
|
+
"\u26A0\uFE0F SECURITY WARNING: ALLOWED_IO_ROOTS not explicitly provided. Defaulting to empty array (NO filesystem access). You MUST specify --allowed-io-roots (or ALLOWED_IO_ROOTS env var) to enable filesystem tools.",
|
|
2934
|
+
{ module: "SERVER" }
|
|
2935
|
+
);
|
|
2936
|
+
} else {
|
|
2937
|
+
logger.info("IO sandbox configured", {
|
|
2938
|
+
module: "SERVER",
|
|
2939
|
+
allowedRoots: allowedIoRoots
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
this.config.allowedIoRoots = allowedIoRoots;
|
|
2409
2943
|
this.toolFilter = config.toolFilter ? parseToolFilter(config.toolFilter) : getToolFilterFromEnv();
|
|
2410
2944
|
this.server = new McpServer(
|
|
2411
2945
|
{
|
|
@@ -2419,6 +2953,48 @@ var DbMcpServer = class {
|
|
|
2419
2953
|
instructions: INSTRUCTIONS
|
|
2420
2954
|
}
|
|
2421
2955
|
);
|
|
2956
|
+
this.subscriptionManager = new SubscriptionManager(this.server);
|
|
2957
|
+
this.server.subscriptionManager = this.subscriptionManager;
|
|
2958
|
+
this.server.server.registerCapabilities({
|
|
2959
|
+
resources: {
|
|
2960
|
+
subscribe: true
|
|
2961
|
+
}
|
|
2962
|
+
});
|
|
2963
|
+
this.server.server.setRequestHandler(
|
|
2964
|
+
SubscribeRequestSchema,
|
|
2965
|
+
(request, extra) => {
|
|
2966
|
+
const uri = request.params.uri;
|
|
2967
|
+
let sessionId = extra.sessionId ?? extra.requestInfo?.headers["mcp-session-id"] ?? void 0;
|
|
2968
|
+
if (sessionId === void 0 && this.config.transport === "stdio") {
|
|
2969
|
+
sessionId = "stdio";
|
|
2970
|
+
}
|
|
2971
|
+
if (!["sqlite://schema", "sqlite://tables", "sqlite://health"].includes(
|
|
2972
|
+
uri
|
|
2973
|
+
) && !uri.startsWith("sqlite://table/")) {
|
|
2974
|
+
throw new ValidationError(`Resource ${uri} is not subscribable`);
|
|
2975
|
+
}
|
|
2976
|
+
this.subscriptionManager.subscribe(
|
|
2977
|
+
uri,
|
|
2978
|
+
sessionId
|
|
2979
|
+
);
|
|
2980
|
+
return {};
|
|
2981
|
+
}
|
|
2982
|
+
);
|
|
2983
|
+
this.server.server.setRequestHandler(
|
|
2984
|
+
UnsubscribeRequestSchema,
|
|
2985
|
+
(request, extra) => {
|
|
2986
|
+
const uri = request.params.uri;
|
|
2987
|
+
let sessionId = extra.sessionId ?? extra.requestInfo?.headers["mcp-session-id"] ?? void 0;
|
|
2988
|
+
if (sessionId === void 0 && this.config.transport === "stdio") {
|
|
2989
|
+
sessionId = "stdio";
|
|
2990
|
+
}
|
|
2991
|
+
this.subscriptionManager.unsubscribe(
|
|
2992
|
+
uri,
|
|
2993
|
+
sessionId
|
|
2994
|
+
);
|
|
2995
|
+
return {};
|
|
2996
|
+
}
|
|
2997
|
+
);
|
|
2422
2998
|
logger.info(getFilterSummary(this.toolFilter), { module: "FILTER" });
|
|
2423
2999
|
logger.info("MCP Server initialized", {
|
|
2424
3000
|
module: "SERVER",
|
|
@@ -2434,13 +3010,17 @@ var DbMcpServer = class {
|
|
|
2434
3010
|
this.toolFilter
|
|
2435
3011
|
);
|
|
2436
3012
|
registerHelpResources(this.server, this.toolFilter);
|
|
3013
|
+
registerObservabilityResources(this.server);
|
|
3014
|
+
if (this.toolFilter.enabledGroups.has("admin")) {
|
|
3015
|
+
registerAdminTools(this.server);
|
|
3016
|
+
}
|
|
2437
3017
|
const internalMcp = this.server;
|
|
2438
3018
|
const handlers = internalMcp.server._requestHandlers;
|
|
2439
3019
|
if (!handlers?.has("tools/list")) {
|
|
2440
3020
|
throw new DbMcpError(
|
|
2441
3021
|
"Security: SDK _requestHandlers monkey-patch failed. Scope filtering is disabled.",
|
|
2442
3022
|
"SERVER_START_FAILED",
|
|
2443
|
-
|
|
3023
|
+
ErrorCategory.INTERNAL
|
|
2444
3024
|
);
|
|
2445
3025
|
}
|
|
2446
3026
|
const originalListToolsHandler = handlers.get("tools/list");
|
|
@@ -2459,6 +3039,11 @@ var DbMcpServer = class {
|
|
|
2459
3039
|
if (config.audit?.enabled) {
|
|
2460
3040
|
this.auditInitPromise = this.initializeAudit(config);
|
|
2461
3041
|
}
|
|
3042
|
+
setInterval(() => {
|
|
3043
|
+
if (this.subscriptionManager.hasSubscribers("sqlite://health")) {
|
|
3044
|
+
void this.subscriptionManager.notifyResourceUpdated("sqlite://health");
|
|
3045
|
+
}
|
|
3046
|
+
}, 6e4).unref();
|
|
2462
3047
|
}
|
|
2463
3048
|
/**
|
|
2464
3049
|
* Register a database adapter
|
|
@@ -2470,6 +3055,9 @@ var DbMcpServer = class {
|
|
|
2470
3055
|
}
|
|
2471
3056
|
await adapter.connect(config);
|
|
2472
3057
|
this.adapters.set(adapterId, adapter);
|
|
3058
|
+
if ("setAllowedIoRoots" in adapter) {
|
|
3059
|
+
adapter.setAllowedIoRoots(this.config.allowedIoRoots);
|
|
3060
|
+
}
|
|
2473
3061
|
if (this.auditInterceptor && "setAuditInterceptor" in adapter) {
|
|
2474
3062
|
adapter.setAuditInterceptor(this.auditInterceptor);
|
|
2475
3063
|
this.auditInterceptor.setQueryAdapter({
|
|
@@ -2482,6 +3070,9 @@ var DbMcpServer = class {
|
|
|
2482
3070
|
adapter.registerTools(this.server, this.toolFilter);
|
|
2483
3071
|
adapter.registerResources(this.server);
|
|
2484
3072
|
adapter.registerPrompts(this.server);
|
|
3073
|
+
adapter.on("schemaChanged", () => {
|
|
3074
|
+
void this.subscriptionManager.notifySchemaSubscribers();
|
|
3075
|
+
});
|
|
2485
3076
|
const toolDefs = adapter.getToolDefinitions();
|
|
2486
3077
|
const scopesMap = /* @__PURE__ */ new Map();
|
|
2487
3078
|
for (const tool of toolDefs) {
|
|
@@ -2522,7 +3113,7 @@ var DbMcpServer = class {
|
|
|
2522
3113
|
throw new DbMcpError(
|
|
2523
3114
|
`Unsupported transport: ${this.config.transport}`,
|
|
2524
3115
|
"SERVER_START_FAILED",
|
|
2525
|
-
|
|
3116
|
+
ErrorCategory.CONFIGURATION
|
|
2526
3117
|
);
|
|
2527
3118
|
}
|
|
2528
3119
|
}
|
|
@@ -2544,7 +3135,7 @@ var DbMcpServer = class {
|
|
|
2544
3135
|
* Start server with HTTP transport (Streamable HTTP with SSE support)
|
|
2545
3136
|
*/
|
|
2546
3137
|
async startHttp() {
|
|
2547
|
-
const { HttpTransport } = await import('./http-
|
|
3138
|
+
const { HttpTransport } = await import('./http-ZDMK46QU.js');
|
|
2548
3139
|
const oauthConfig = {
|
|
2549
3140
|
enabled: this.config.oauth?.enabled ?? false,
|
|
2550
3141
|
authorizationServerUrl: this.config.oauth?.authorizationServerUrl ?? "",
|
|
@@ -2573,7 +3164,10 @@ var DbMcpServer = class {
|
|
|
2573
3164
|
},
|
|
2574
3165
|
oauth: oauthConfig,
|
|
2575
3166
|
stateless: this.config.statelessHttp ?? false,
|
|
2576
|
-
noAuthEnforcement: this.config.noAuthEnforcement ?? false
|
|
3167
|
+
noAuthEnforcement: this.config.noAuthEnforcement ?? false,
|
|
3168
|
+
...this.config.metricsExport !== void 0 && {
|
|
3169
|
+
metricsExport: this.config.metricsExport
|
|
3170
|
+
}
|
|
2577
3171
|
});
|
|
2578
3172
|
await transport.initialize(this.server);
|
|
2579
3173
|
await transport.start();
|
|
@@ -2605,6 +3199,11 @@ var DbMcpServer = class {
|
|
|
2605
3199
|
await this.auditLogger.close();
|
|
2606
3200
|
logger.info("Audit logger closed", { module: "AUDIT" });
|
|
2607
3201
|
}
|
|
3202
|
+
metrics.close();
|
|
3203
|
+
if (this.systemDb) {
|
|
3204
|
+
this.systemDb.close();
|
|
3205
|
+
logger.info("System database closed", { module: "SYSTEM_DB" });
|
|
3206
|
+
}
|
|
2608
3207
|
if (this.backupManager) {
|
|
2609
3208
|
await this.backupManager.flush();
|
|
2610
3209
|
logger.info("Backup manager flushed", { module: "AUDIT" });
|
|
@@ -2619,7 +3218,12 @@ var DbMcpServer = class {
|
|
|
2619
3218
|
async initializeAudit(config) {
|
|
2620
3219
|
const auditConfig = config.audit;
|
|
2621
3220
|
if (!auditConfig?.enabled) return;
|
|
2622
|
-
|
|
3221
|
+
if (auditConfig.logPath !== "stderr") {
|
|
3222
|
+
this.systemDb = new SystemDb({ dbPath: auditConfig.logPath });
|
|
3223
|
+
await this.systemDb.init();
|
|
3224
|
+
metrics.setSystemDb(this.systemDb);
|
|
3225
|
+
}
|
|
3226
|
+
this.auditLogger = new AuditLogger(auditConfig, this.systemDb);
|
|
2623
3227
|
await this.auditLogger.init();
|
|
2624
3228
|
if (auditConfig.backup?.enabled) {
|
|
2625
3229
|
this.backupManager = new BackupManager(
|
|
@@ -2635,6 +3239,9 @@ var DbMcpServer = class {
|
|
|
2635
3239
|
if (this.backupManager && this.toolFilter.enabledGroups.has("admin")) {
|
|
2636
3240
|
registerAuditBackupTools(this.server, this.backupManager, this.adapters);
|
|
2637
3241
|
}
|
|
3242
|
+
if (this.toolFilter.enabledGroups.has("admin")) {
|
|
3243
|
+
registerAuditSearchTool(this.server, this.auditLogger);
|
|
3244
|
+
}
|
|
2638
3245
|
logger.info(
|
|
2639
3246
|
`Audit logging enabled (${auditConfig.logPath}, redact=${String(auditConfig.redact)}, reads=${String(auditConfig.auditReads)}, backup=${String(!!auditConfig.backup?.enabled)})`,
|
|
2640
3247
|
{ module: "AUDIT" }
|