db-mcp 1.1.1 → 3.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 +247 -169
- package/dist/.tsbuildinfo +1 -0
- package/dist/adapters/database-adapter.d.ts +173 -0
- package/dist/adapters/database-adapter.d.ts.map +1 -0
- package/dist/adapters/query-validation.d.ts +20 -0
- package/dist/adapters/query-validation.d.ts.map +1 -0
- package/dist/adapters/registration/index.d.ts +4 -0
- package/dist/adapters/registration/index.d.ts.map +1 -0
- package/dist/adapters/registration/prompts.d.ts +7 -0
- package/dist/adapters/registration/prompts.d.ts.map +1 -0
- package/dist/adapters/registration/resources.d.ts +7 -0
- package/dist/adapters/registration/resources.d.ts.map +1 -0
- package/dist/adapters/registration/tools.d.ts +9 -0
- package/dist/adapters/registration/tools.d.ts.map +1 -0
- package/dist/adapters/sqlite/index.d.ts +9 -0
- package/dist/adapters/sqlite/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/json-utils.d.ts +100 -0
- package/dist/adapters/sqlite/json-utils.d.ts.map +1 -0
- package/dist/adapters/sqlite/prompts/analysis.d.ts +23 -0
- package/dist/adapters/sqlite/prompts/analysis.d.ts.map +1 -0
- package/dist/adapters/sqlite/prompts/index.d.ts +16 -0
- package/dist/adapters/sqlite/prompts/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/prompts/query.d.ts +19 -0
- package/dist/adapters/sqlite/prompts/query.d.ts.map +1 -0
- package/dist/adapters/sqlite/prompts/schema.d.ts +20 -0
- package/dist/adapters/sqlite/prompts/schema.d.ts.map +1 -0
- package/dist/adapters/sqlite/query-executor.d.ts +38 -0
- package/dist/adapters/sqlite/query-executor.d.ts.map +1 -0
- package/dist/adapters/sqlite/resources.d.ts +13 -0
- package/dist/adapters/sqlite/resources.d.ts.map +1 -0
- package/dist/adapters/sqlite/schema-manager.d.ts +69 -0
- package/dist/adapters/sqlite/schema-manager.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/admin.d.ts +459 -0
- package/dist/adapters/sqlite/schemas/admin.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/codemode.d.ts +30 -0
- package/dist/adapters/sqlite/schemas/codemode.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/common.d.ts +9 -0
- package/dist/adapters/sqlite/schemas/common.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/core.d.ts +530 -0
- package/dist/adapters/sqlite/schemas/core.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/error-mixin.d.ts +10 -0
- package/dist/adapters/sqlite/schemas/error-mixin.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/fts.d.ts +106 -0
- package/dist/adapters/sqlite/schemas/fts.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/geo.d.ts +135 -0
- package/dist/adapters/sqlite/schemas/geo.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/index.d.ts +23 -0
- package/dist/adapters/sqlite/schemas/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/introspection.d.ts +583 -0
- package/dist/adapters/sqlite/schemas/introspection.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/json.d.ts +849 -0
- package/dist/adapters/sqlite/schemas/json.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/migration.d.ts +220 -0
- package/dist/adapters/sqlite/schemas/migration.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/native.d.ts +235 -0
- package/dist/adapters/sqlite/schemas/native.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/server.d.ts +13 -0
- package/dist/adapters/sqlite/schemas/server.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/spatialite.d.ts +114 -0
- package/dist/adapters/sqlite/schemas/spatialite.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/stats.d.ts +780 -0
- package/dist/adapters/sqlite/schemas/stats.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/text.d.ts +570 -0
- package/dist/adapters/sqlite/schemas/text.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/vector.d.ts +252 -0
- package/dist/adapters/sqlite/schemas/vector.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/virtual.d.ts +257 -0
- package/dist/adapters/sqlite/schemas/virtual.d.ts.map +1 -0
- package/dist/adapters/sqlite/schemas/where.d.ts +20 -0
- package/dist/adapters/sqlite/schemas/where.d.ts.map +1 -0
- package/dist/adapters/sqlite/sqlite-adapter/lifecycle.d.ts +13 -0
- package/dist/adapters/sqlite/sqlite-adapter/lifecycle.d.ts.map +1 -0
- package/dist/adapters/sqlite/sqlite-adapter/schema.d.ts +7 -0
- package/dist/adapters/sqlite/sqlite-adapter/schema.d.ts.map +1 -0
- package/dist/adapters/sqlite/sqlite-adapter.d.ts +138 -0
- package/dist/adapters/sqlite/sqlite-adapter.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/backup/analyze.d.ts +7 -0
- package/dist/adapters/sqlite/tools/admin/backup/analyze.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/backup/create.d.ts +11 -0
- package/dist/adapters/sqlite/tools/admin/backup/create.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/backup/dump.d.ts +7 -0
- package/dist/adapters/sqlite/tools/admin/backup/dump.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/backup/index.d.ts +7 -0
- package/dist/adapters/sqlite/tools/admin/backup/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/backup/integrity.d.ts +7 -0
- package/dist/adapters/sqlite/tools/admin/backup/integrity.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/backup/optimize.d.ts +7 -0
- package/dist/adapters/sqlite/tools/admin/backup/optimize.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/backup/restore.d.ts +7 -0
- package/dist/adapters/sqlite/tools/admin/backup/restore.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/helpers.d.ts +18 -0
- package/dist/adapters/sqlite/tools/admin/helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/index.d.ts +10 -0
- package/dist/adapters/sqlite/tools/admin/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/pragma.d.ts +37 -0
- package/dist/adapters/sqlite/tools/admin/pragma.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/reindex.d.ts +10 -0
- package/dist/adapters/sqlite/tools/admin/reindex.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/verify.d.ts +13 -0
- package/dist/adapters/sqlite/tools/admin/verify.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/admin/wal.d.ts +10 -0
- package/dist/adapters/sqlite/tools/admin/wal.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/codemode.d.ts +22 -0
- package/dist/adapters/sqlite/tools/codemode.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/column-validation.d.ts +26 -0
- package/dist/adapters/sqlite/tools/column-validation.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/alter-table.d.ts +11 -0
- package/dist/adapters/sqlite/tools/core/alter-table.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/constraints.d.ts +10 -0
- package/dist/adapters/sqlite/tools/core/constraints.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/convenience-helpers.d.ts +20 -0
- package/dist/adapters/sqlite/tools/core/convenience-helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/convenience-schemas.d.ts +20 -0
- package/dist/adapters/sqlite/tools/core/convenience-schemas.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/convenience.d.ts +29 -0
- package/dist/adapters/sqlite/tools/core/convenience.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/datetime.d.ts +11 -0
- package/dist/adapters/sqlite/tools/core/datetime.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/index.d.ts +14 -0
- package/dist/adapters/sqlite/tools/core/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/indexes.d.ts +20 -0
- package/dist/adapters/sqlite/tools/core/indexes.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/queries.d.ts +16 -0
- package/dist/adapters/sqlite/tools/core/queries.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/tables.d.ts +37 -0
- package/dist/adapters/sqlite/tools/core/tables.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/core/triggers.d.ts +18 -0
- package/dist/adapters/sqlite/tools/core/triggers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/fts.d.ts +13 -0
- package/dist/adapters/sqlite/tools/fts.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/geo.d.ts +14 -0
- package/dist/adapters/sqlite/tools/geo.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/index.d.ts +33 -0
- package/dist/adapters/sqlite/tools/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/analysis/constraints.d.ts +10 -0
- package/dist/adapters/sqlite/tools/introspection/analysis/constraints.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/analysis/diff.d.ts +11 -0
- package/dist/adapters/sqlite/tools/introspection/analysis/diff.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/analysis/index.d.ts +10 -0
- package/dist/adapters/sqlite/tools/introspection/analysis/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/analysis/risks.d.ts +9 -0
- package/dist/adapters/sqlite/tools/introspection/analysis/risks.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/analysis/snapshot.d.ts +40 -0
- package/dist/adapters/sqlite/tools/introspection/analysis/snapshot.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/diagnostics/index.d.ts +9 -0
- package/dist/adapters/sqlite/tools/introspection/diagnostics/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/diagnostics/indexes.d.ts +10 -0
- package/dist/adapters/sqlite/tools/introspection/diagnostics/indexes.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/diagnostics/query-plan.d.ts +10 -0
- package/dist/adapters/sqlite/tools/introspection/diagnostics/query-plan.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/diagnostics/storage.d.ts +10 -0
- package/dist/adapters/sqlite/tools/introspection/diagnostics/storage.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/graph/helpers.d.ts +44 -0
- package/dist/adapters/sqlite/tools/introspection/graph/helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/graph/index.d.ts +9 -0
- package/dist/adapters/sqlite/tools/introspection/graph/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/graph/tools.d.ts +12 -0
- package/dist/adapters/sqlite/tools/introspection/graph/tools.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/introspection/index.d.ts +17 -0
- package/dist/adapters/sqlite/tools/introspection/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-helpers/helpers.d.ts +21 -0
- package/dist/adapters/sqlite/tools/json-helpers/helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-helpers/index.d.ts +17 -0
- package/dist/adapters/sqlite/tools/json-helpers/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-helpers/read.d.ts +24 -0
- package/dist/adapters/sqlite/tools/json-helpers/read.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-helpers/write.d.ts +24 -0
- package/dist/adapters/sqlite/tools/json-helpers/write.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-operations/crud.d.ts +39 -0
- package/dist/adapters/sqlite/tools/json-operations/crud.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-operations/diff.d.ts +11 -0
- package/dist/adapters/sqlite/tools/json-operations/diff.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-operations/helpers.d.ts +18 -0
- package/dist/adapters/sqlite/tools/json-operations/helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-operations/index.d.ts +10 -0
- package/dist/adapters/sqlite/tools/json-operations/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-operations/query.d.ts +24 -0
- package/dist/adapters/sqlite/tools/json-operations/query.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-operations/security.d.ts +19 -0
- package/dist/adapters/sqlite/tools/json-operations/security.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/json-operations/transform.d.ts +24 -0
- package/dist/adapters/sqlite/tools/json-operations/transform.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/migration/helpers.d.ts +34 -0
- package/dist/adapters/sqlite/tools/migration/helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/migration/index.d.ts +15 -0
- package/dist/adapters/sqlite/tools/migration/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/migration/tracking/apply.d.ts +4 -0
- package/dist/adapters/sqlite/tools/migration/tracking/apply.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/migration/tracking/history.d.ts +4 -0
- package/dist/adapters/sqlite/tools/migration/tracking/history.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/migration/tracking/index.d.ts +7 -0
- package/dist/adapters/sqlite/tools/migration/tracking/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/migration/tracking/init.d.ts +4 -0
- package/dist/adapters/sqlite/tools/migration/tracking/init.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/migration/tracking/record.d.ts +4 -0
- package/dist/adapters/sqlite/tools/migration/tracking/record.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/migration/tracking/rollback.d.ts +4 -0
- package/dist/adapters/sqlite/tools/migration/tracking/rollback.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/migration/tracking/status.d.ts +4 -0
- package/dist/adapters/sqlite/tools/migration/tracking/status.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/advanced.d.ts +28 -0
- package/dist/adapters/sqlite/tools/stats/advanced.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/anomaly-detection.d.ts +22 -0
- package/dist/adapters/sqlite/tools/stats/anomaly-detection.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/basic.d.ts +32 -0
- package/dist/adapters/sqlite/tools/stats/basic.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/helpers.d.ts +27 -0
- package/dist/adapters/sqlite/tools/stats/helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/index.d.ts +12 -0
- package/dist/adapters/sqlite/tools/stats/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/inference/hypothesis.d.ts +7 -0
- package/dist/adapters/sqlite/tools/stats/inference/hypothesis.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/inference/index.d.ts +4 -0
- package/dist/adapters/sqlite/tools/stats/inference/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/inference/outlier.d.ts +7 -0
- package/dist/adapters/sqlite/tools/stats/inference/outlier.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/inference/regression.d.ts +7 -0
- package/dist/adapters/sqlite/tools/stats/inference/regression.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/math-helpers.d.ts +17 -0
- package/dist/adapters/sqlite/tools/stats/math-helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/stats/schema-risks.d.ts +15 -0
- package/dist/adapters/sqlite/tools/stats/schema-risks.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/text/formatting.d.ts +48 -0
- package/dist/adapters/sqlite/tools/text/formatting.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/text/helpers.d.ts +22 -0
- package/dist/adapters/sqlite/tools/text/helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/text/index.d.ts +10 -0
- package/dist/adapters/sqlite/tools/text/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/text/regex.d.ts +21 -0
- package/dist/adapters/sqlite/tools/text/regex.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/text/search.d.ts +17 -0
- package/dist/adapters/sqlite/tools/text/search.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/text/sentiment.d.ts +13 -0
- package/dist/adapters/sqlite/tools/text/sentiment.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/text/validate.d.ts +11 -0
- package/dist/adapters/sqlite/tools/text/validate.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/vector/helpers.d.ts +11 -0
- package/dist/adapters/sqlite/tools/vector/helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/vector/index.d.ts +14 -0
- package/dist/adapters/sqlite/tools/vector/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/vector/metadata.d.ts +28 -0
- package/dist/adapters/sqlite/tools/vector/metadata.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/vector/search.d.ts +16 -0
- package/dist/adapters/sqlite/tools/vector/search.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/vector/storage.d.ts +24 -0
- package/dist/adapters/sqlite/tools/vector/storage.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/vector/tools.d.ts +9 -0
- package/dist/adapters/sqlite/tools/vector/tools.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/analysis.d.ts +24 -0
- package/dist/adapters/sqlite/tools/virtual/analysis.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/extensions.d.ts +13 -0
- package/dist/adapters/sqlite/tools/virtual/extensions.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/helpers.d.ts +18 -0
- package/dist/adapters/sqlite/tools/virtual/helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/index.d.ts +10 -0
- package/dist/adapters/sqlite/tools/virtual/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/views.d.ts +21 -0
- package/dist/adapters/sqlite/tools/virtual/views.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/analyze-csv.d.ts +4 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/analyze-csv.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/csv.d.ts +4 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/csv.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/drop.d.ts +4 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/drop.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/index.d.ts +6 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/info.d.ts +4 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/info.d.ts.map +1 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/list.d.ts +4 -0
- package/dist/adapters/sqlite/tools/virtual/vtable/list.d.ts.map +1 -0
- package/dist/adapters/sqlite/types.d.ts +89 -0
- package/dist/adapters/sqlite/types.d.ts.map +1 -0
- package/dist/adapters/sqlite-helpers.d.ts +51 -0
- package/dist/adapters/sqlite-helpers.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/extensions.d.ts +17 -0
- package/dist/adapters/sqlite-native/extensions.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/index.d.ts +11 -0
- package/dist/adapters/sqlite-native/index.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/native-query-executor.d.ts +24 -0
- package/dist/adapters/sqlite-native/native-query-executor.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/native-sqlite-adapter.d.ts +160 -0
- package/dist/adapters/sqlite-native/native-sqlite-adapter.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/registration/index.d.ts +12 -0
- package/dist/adapters/sqlite-native/registration/index.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/spatialite/analysis.d.ts +23 -0
- package/dist/adapters/sqlite-native/tools/spatialite/analysis.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/spatialite/index.d.ts +15 -0
- package/dist/adapters/sqlite-native/tools/spatialite/index.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/spatialite/loader.d.ts +22 -0
- package/dist/adapters/sqlite-native/tools/spatialite/loader.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/spatialite/schemas.d.ts +78 -0
- package/dist/adapters/sqlite-native/tools/spatialite/schemas.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/spatialite/tools.d.ts +30 -0
- package/dist/adapters/sqlite-native/tools/spatialite/tools.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/transactions.d.ts +12 -0
- package/dist/adapters/sqlite-native/tools/transactions.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/tools/window.d.ts +7 -0
- package/dist/adapters/sqlite-native/tools/window.d.ts.map +1 -0
- package/dist/adapters/sqlite-native/transaction-methods.d.ts +36 -0
- package/dist/adapters/sqlite-native/transaction-methods.d.ts.map +1 -0
- package/dist/audit/backup-manager.d.ts +90 -0
- package/dist/audit/backup-manager.d.ts.map +1 -0
- package/dist/audit/index.d.ts +11 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/interceptor.d.ts +54 -0
- package/dist/audit/interceptor.d.ts.map +1 -0
- package/dist/audit/logger.d.ts +62 -0
- package/dist/audit/logger.d.ts.map +1 -0
- package/dist/audit/types.d.ts +112 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/auth/auth-context.d.ts +28 -0
- package/dist/auth/auth-context.d.ts.map +1 -0
- package/dist/auth/authorization-server-discovery.d.ts +90 -0
- package/dist/auth/authorization-server-discovery.d.ts.map +1 -0
- package/dist/auth/errors.d.ts +74 -0
- package/dist/auth/errors.d.ts.map +1 -0
- package/dist/auth/middleware/core.d.ts +22 -0
- package/dist/auth/middleware/core.d.ts.map +1 -0
- package/dist/auth/middleware/express-auth.d.ts +13 -0
- package/dist/auth/middleware/express-auth.d.ts.map +1 -0
- package/dist/auth/middleware/express-scopes.d.ts +6 -0
- package/dist/auth/middleware/express-scopes.d.ts.map +1 -0
- package/dist/auth/middleware/extraction.d.ts +2 -0
- package/dist/auth/middleware/extraction.d.ts.map +1 -0
- package/dist/auth/middleware/index.d.ts +5 -0
- package/dist/auth/middleware/index.d.ts.map +1 -0
- package/dist/auth/oauth-resource-server.d.ts +74 -0
- package/dist/auth/oauth-resource-server.d.ts.map +1 -0
- package/dist/auth/scope-map.d.ts +24 -0
- package/dist/auth/scope-map.d.ts.map +1 -0
- package/dist/auth/scopes/constants.d.ts +40 -0
- package/dist/auth/scopes/constants.d.ts.map +1 -0
- package/dist/auth/scopes/display.d.ts +5 -0
- package/dist/auth/scopes/display.d.ts.map +1 -0
- package/dist/auth/scopes/enforcement.d.ts +27 -0
- package/dist/auth/scopes/enforcement.d.ts.map +1 -0
- package/dist/auth/scopes/index.d.ts +6 -0
- package/dist/auth/scopes/index.d.ts.map +1 -0
- package/dist/auth/scopes/mapping.d.ts +39 -0
- package/dist/auth/scopes/mapping.d.ts.map +1 -0
- package/dist/auth/scopes/validation.d.ts +27 -0
- package/dist/auth/scopes/validation.d.ts.map +1 -0
- package/dist/auth/token-validator.d.ts +63 -0
- package/dist/auth/token-validator.d.ts.map +1 -0
- package/dist/auth/transport-agnostic.d.ts +11 -0
- package/dist/auth/transport-agnostic.d.ts.map +1 -0
- package/dist/auth/types.d.ts +257 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/chunk-E5IESRTK.js +489 -0
- package/dist/{chunk-TVIZ3XJH.js → chunk-FR65YPAH.js} +8509 -3925
- package/dist/{chunk-AOUL5SHS.js → chunk-L552U3QS.js} +880 -252
- package/dist/chunk-THATOQRT.js +2624 -0
- package/dist/chunk-W5WQVNVX.js +568 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +114 -19
- package/dist/codemode/api-constants.d.ts +40 -0
- package/dist/codemode/api-constants.d.ts.map +1 -0
- package/dist/codemode/api.d.ts +70 -0
- package/dist/codemode/api.d.ts.map +1 -0
- package/dist/codemode/auto-return.d.ts +25 -0
- package/dist/codemode/auto-return.d.ts.map +1 -0
- package/dist/codemode/index.d.ts +12 -0
- package/dist/codemode/index.d.ts.map +1 -0
- package/dist/codemode/sandbox-factory.d.ts +72 -0
- package/dist/codemode/sandbox-factory.d.ts.map +1 -0
- package/dist/codemode/sandbox.d.ts +54 -0
- package/dist/codemode/sandbox.d.ts.map +1 -0
- package/dist/codemode/security.d.ts +45 -0
- package/dist/codemode/security.d.ts.map +1 -0
- package/dist/codemode/types.d.ts +167 -0
- package/dist/codemode/types.d.ts.map +1 -0
- package/dist/constants/server-instructions.d.ts +22 -0
- package/dist/constants/server-instructions.d.ts.map +1 -0
- package/dist/filtering/tool-constants.d.ts +45 -0
- package/dist/filtering/tool-constants.d.ts.map +1 -0
- package/dist/filtering/tool-filter.d.ts +82 -0
- package/dist/filtering/tool-filter.d.ts.map +1 -0
- package/dist/{http-VSB7DBJR.js → http-HWTUVFIA.js} +587 -358
- package/dist/index.d.ts +9 -842
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -5
- package/dist/server/mcp-server.d.ts +66 -0
- package/dist/server/mcp-server.d.ts.map +1 -0
- package/dist/server/registration/audit-tools.d.ts +13 -0
- package/dist/server/registration/audit-tools.d.ts.map +1 -0
- package/dist/server/registration/built-in-tools.d.ts +8 -0
- package/dist/server/registration/built-in-tools.d.ts.map +1 -0
- package/dist/server/registration/help-resources.d.ts +7 -0
- package/dist/server/registration/help-resources.d.ts.map +1 -0
- package/dist/server/registration/index.d.ts +4 -0
- package/dist/server/registration/index.d.ts.map +1 -0
- package/dist/{sqlite-26V3Y4MK.js → sqlite-6C3AJI4I.js} +115 -147
- package/dist/{sqlite-native-5O7FZJGB.js → sqlite-native-ZSSWCTYC.js} +1016 -349
- package/dist/transports/http/index.d.ts +8 -0
- package/dist/transports/http/index.d.ts.map +1 -0
- package/dist/transports/http/middleware.d.ts +25 -0
- package/dist/transports/http/middleware.d.ts.map +1 -0
- package/dist/transports/http/oauth.d.ts +24 -0
- package/dist/transports/http/oauth.d.ts.map +1 -0
- package/dist/transports/http/session.d.ts +2 -0
- package/dist/transports/http/session.d.ts.map +1 -0
- package/dist/transports/http/sessions/index.d.ts +4 -0
- package/dist/transports/http/sessions/index.d.ts.map +1 -0
- package/dist/transports/http/sessions/legacy-sse.d.ts +6 -0
- package/dist/transports/http/sessions/legacy-sse.d.ts.map +1 -0
- package/dist/transports/http/sessions/mutex.d.ts +6 -0
- package/dist/transports/http/sessions/mutex.d.ts.map +1 -0
- package/dist/transports/http/sessions/stateful.d.ts +12 -0
- package/dist/transports/http/sessions/stateful.d.ts.map +1 -0
- package/dist/transports/http/sessions/stateless.d.ts +6 -0
- package/dist/transports/http/sessions/stateless.d.ts.map +1 -0
- package/dist/transports/http/transport.d.ts +65 -0
- package/dist/transports/http/transport.d.ts.map +1 -0
- package/dist/transports/http/type-adapters.d.ts +20 -0
- package/dist/transports/http/type-adapters.d.ts.map +1 -0
- package/dist/transports/http/types.d.ts +120 -0
- package/dist/transports/http/types.d.ts.map +1 -0
- package/dist/types/adapter.d.ts +138 -0
- package/dist/types/adapter.d.ts.map +1 -0
- package/dist/types/auth.d.ts +79 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/database.d.ts +100 -0
- package/dist/types/database.d.ts.map +1 -0
- package/dist/types/filtering.d.ts +41 -0
- package/dist/types/filtering.d.ts.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/server.d.ts +48 -0
- package/dist/types/server.d.ts.map +1 -0
- package/dist/utils/annotations.d.ts +62 -0
- package/dist/utils/annotations.d.ts.map +1 -0
- package/dist/utils/errors/base.d.ts +33 -0
- package/dist/utils/errors/base.d.ts.map +1 -0
- package/dist/utils/errors/categories.d.ts +41 -0
- package/dist/utils/errors/categories.d.ts.map +1 -0
- package/dist/utils/errors/classes.d.ts +124 -0
- package/dist/utils/errors/classes.d.ts.map +1 -0
- package/dist/utils/errors/error-response-fields.d.ts +24 -0
- package/dist/utils/errors/error-response-fields.d.ts.map +1 -0
- package/dist/utils/errors/format.d.ts +74 -0
- package/dist/utils/errors/format.d.ts.map +1 -0
- package/dist/utils/errors/index.d.ts +11 -0
- package/dist/utils/errors/index.d.ts.map +1 -0
- package/dist/utils/errors/suggestions.d.ts +16 -0
- package/dist/utils/errors/suggestions.d.ts.map +1 -0
- package/dist/utils/icons.d.ts +19 -0
- package/dist/utils/icons.d.ts.map +1 -0
- package/dist/utils/identifiers.d.ts +121 -0
- package/dist/utils/identifiers.d.ts.map +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/insights-manager.d.ts +39 -0
- package/dist/utils/insights-manager.d.ts.map +1 -0
- package/dist/utils/logger/error-codes.d.ts +48 -0
- package/dist/utils/logger/error-codes.d.ts.map +1 -0
- package/dist/utils/logger/index.d.ts +22 -0
- package/dist/utils/logger/index.d.ts.map +1 -0
- package/dist/utils/logger/logger.d.ts +94 -0
- package/dist/utils/logger/logger.d.ts.map +1 -0
- package/dist/utils/logger/module-logger.d.ts +28 -0
- package/dist/utils/logger/module-logger.d.ts.map +1 -0
- package/dist/utils/logger/types.d.ts +36 -0
- package/dist/utils/logger/types.d.ts.map +1 -0
- package/dist/utils/progress-utils.d.ts +54 -0
- package/dist/utils/progress-utils.d.ts.map +1 -0
- package/dist/utils/redaction.d.ts +4 -0
- package/dist/utils/redaction.d.ts.map +1 -0
- package/dist/utils/resource-annotations.d.ts +36 -0
- package/dist/utils/resource-annotations.d.ts.map +1 -0
- package/dist/utils/validate-json-path.d.ts +43 -0
- package/dist/utils/validate-json-path.d.ts.map +1 -0
- package/dist/utils/validate-path.d.ts +37 -0
- package/dist/utils/validate-path.d.ts.map +1 -0
- package/dist/utils/where-clause.d.ts +42 -0
- package/dist/utils/where-clause.d.ts.map +1 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +33 -20
- package/.gitattributes +0 -2
- package/dist/chunk-4IA3DB5C.js +0 -135
- package/dist/chunk-FW7UCRLN.js +0 -82
- package/dist/chunk-RHVEZ42P.js +0 -873
- package/dist/chunk-Z2GFQU3G.js +0 -363
- package/dist/worker-script.d.ts +0 -2
- package/dist/worker-script.js +0 -126
- package/playwright.config.ts +0 -101
- package/scripts/generate-server-instructions.ts +0 -111
- package/server.json +0 -52
- package/test-server/README.md +0 -118
- package/test-server/code-map.md +0 -409
- package/test-server/fixtures/sample.csv +0 -6
- package/test-server/reset-database.ps1 +0 -373
- package/test-server/sample.csv +0 -11
- package/test-server/test-agent-experience.md +0 -243
- package/test-server/test-database.sql +0 -388
- package/test-server/test-group-tools.md +0 -861
- package/test-server/test-help-resources.mjs +0 -238
- package/test-server/test-preflight.md +0 -53
- package/test-server/test-prompts.md +0 -354
- package/test-server/test-resources.md +0 -245
- package/test-server/test-tool-annotations.mjs +0 -157
- package/test-server/test-tools-advanced-1.md +0 -517
- package/test-server/test-tools-advanced-2.md +0 -487
- package/test-server/test-tools-codemode.md +0 -629
- package/test-server/test-tools.md +0 -176
- package/test-server/tool-reference.md +0 -236
- package/tsconfig.test.json +0 -9
|
@@ -0,0 +1,2624 @@
|
|
|
1
|
+
import { VERSION, NAME, READ_ONLY, SERVER_ICONS, ASSISTANT_FOCUSED, redactObject, getRequiredScope, AuditListBackupsOutputSchema, AuditListBackupsSchema, AuditGetBackupOutputSchema, AuditCleanupOutputSchema, AuditDiffBackupOutputSchema, AuditRestoreBackupOutputSchema } from './chunk-E5IESRTK.js';
|
|
2
|
+
import { parseToolFilter, getToolFilterFromEnv, logger, getFilterSummary, DbMcpError, getAuthContext, scopesGrantToolAccess, registerToolScopes, InsufficientScopeError, sanitizeErrorMessage, formatHandlerError } from './chunk-L552U3QS.js';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
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
|
+
import { dirname, join, basename, sep } from 'path';
|
|
7
|
+
import { gzip, gunzipSync } from 'zlib';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
import { performance } from 'perf_hooks';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
|
|
12
|
+
// src/constants/server-instructions.ts
|
|
13
|
+
var INSTRUCTIONS = `# db-mcp (SQLite MCP Server)
|
|
14
|
+
|
|
15
|
+
## Quick Access
|
|
16
|
+
|
|
17
|
+
| Purpose | Action |
|
|
18
|
+
| --------------- | -------------------------- |
|
|
19
|
+
| Health check | \`server_health\` tool |
|
|
20
|
+
| Server info | \`server_info\` tool |
|
|
21
|
+
| Database schema | \`sqlite://schema\` resource |
|
|
22
|
+
| Tool help | \`sqlite://help\` resource |
|
|
23
|
+
|
|
24
|
+
## Built-in Tools
|
|
25
|
+
|
|
26
|
+
\`server_info\`, \`server_health\`, \`list_adapters\`, \`sqlite_execute_code\` \u2014 injected into all configurations.
|
|
27
|
+
|
|
28
|
+
## Help Resources
|
|
29
|
+
|
|
30
|
+
Read \`sqlite://help\` for gotchas and critical usage patterns.
|
|
31
|
+
Read \`sqlite://help/{group}\` for group-specific tool reference (core, json, text, stats, vector, geo, admin, transactions, introspection, migration).
|
|
32
|
+
Only help resources for your enabled tool groups are registered.`;
|
|
33
|
+
var HELP_CONTENT = /* @__PURE__ */ new Map([
|
|
34
|
+
["admin", `# db-mcp Help \u2014 Database Administration (32N/31W tools) + Server Audit (5 tools)
|
|
35
|
+
|
|
36
|
+
## Maintenance
|
|
37
|
+
|
|
38
|
+
\`\`\`javascript
|
|
39
|
+
sqlite_integrity_check({ maxErrors: 10 }); // check for corruption
|
|
40
|
+
sqlite_optimize({ analyze: true, reindex: true }); // optimize performance
|
|
41
|
+
sqlite_vacuum(); // reclaim space
|
|
42
|
+
sqlite_analyze({ table: "orders" }); // update statistics for query planner
|
|
43
|
+
sqlite_dbstat({ summarize: true }); // storage stats (\u26A0\uFE0F summarize native-only; WASM returns counts only)
|
|
44
|
+
sqlite_reindex(); // rebuild all indexes
|
|
45
|
+
sqlite_reindex({ target: "idx_users_email" }); // rebuild specific index
|
|
46
|
+
sqlite_reindex({ target: "orders" }); // rebuild all indexes for a table
|
|
47
|
+
\`\`\`
|
|
48
|
+
|
|
49
|
+
## Backup/Restore (Native only)
|
|
50
|
+
|
|
51
|
+
\`\`\`javascript
|
|
52
|
+
sqlite_backup({ targetPath: "/path/to/backup.db" });
|
|
53
|
+
sqlite_vacuum_into({ outputPath: "/path/to/compact.db" }); // create defragmented copy
|
|
54
|
+
sqlite_dump({ outputPath: "/path/to/dump.sql" }); // export SQL text dump
|
|
55
|
+
sqlite_verify_backup({ backupPath: "/path/to/backup.db" }); // check integrity without restoring
|
|
56
|
+
sqlite_restore({ sourcePath: "/path/to/backup.db" }); // \u26A0\uFE0F WARNING: Replaces current database
|
|
57
|
+
\`\`\`
|
|
58
|
+
|
|
59
|
+
## Audit Backups
|
|
60
|
+
|
|
61
|
+
\`\`\`javascript
|
|
62
|
+
sqlite_audit_list_backups({ limit: 10, offset: 0 }); // list pre-mutation DDL snapshots
|
|
63
|
+
sqlite_audit_get_backup({ filename: "snapshot_123.json" }); // retrieve specific snapshot
|
|
64
|
+
sqlite_audit_diff_backup({ filename: "snapshot_123.json" }); // compare snapshot against live schema
|
|
65
|
+
sqlite_audit_restore_backup({ filename: "snapshot_123.json", dryRun: true }); // restore schema from snapshot
|
|
66
|
+
sqlite_audit_cleanup(); // apply retention policy and delete old snapshots
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
## PRAGMA
|
|
70
|
+
|
|
71
|
+
\`\`\`javascript
|
|
72
|
+
sqlite_pragma_settings({ pragma: "journal_mode" }); // get value
|
|
73
|
+
sqlite_pragma_settings({ pragma: "cache_size", value: 10000 }); // set value
|
|
74
|
+
sqlite_pragma_table_info({ table: "users" }); // column details
|
|
75
|
+
sqlite_pragma_compile_options({ filter: "FTS" }); // \u26A0\uFE0F WASM may show FTS3, not FTS5
|
|
76
|
+
sqlite_pragma_database_list(); // list attached databases
|
|
77
|
+
sqlite_attach_database({ filepath: "/path/to/other.db", alias: "archive" }); // attach external DB
|
|
78
|
+
sqlite_detach_database({ alias: "archive" }); // detach DB
|
|
79
|
+
sqlite_pragma_optimize(); // run PRAGMA optimize
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
## WAL Management
|
|
83
|
+
|
|
84
|
+
\`\`\`javascript
|
|
85
|
+
sqlite_wal({ action: "status" }); // check current journal mode
|
|
86
|
+
sqlite_wal({ action: "enable" }); // switch to WAL mode
|
|
87
|
+
sqlite_wal({ action: "disable" }); // switch back to DELETE journal mode
|
|
88
|
+
sqlite_wal({ action: "checkpoint" }); // run default checkpoint
|
|
89
|
+
sqlite_wal({ action: "checkpoint", checkpointMode: "TRUNCATE" }); // checkpoint and truncate WAL file
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
## Index & Stats
|
|
93
|
+
|
|
94
|
+
- \`sqlite_index_stats({ table: "orders" })\` \u2014 stats for explicit indexes
|
|
95
|
+
|
|
96
|
+
## Views
|
|
97
|
+
|
|
98
|
+
\`\`\`javascript
|
|
99
|
+
sqlite_create_view({
|
|
100
|
+
viewName: "active_orders",
|
|
101
|
+
selectQuery: "SELECT * FROM orders WHERE status = 'active'",
|
|
102
|
+
});
|
|
103
|
+
sqlite_create_view({ viewName: "v", selectQuery: "...", replace: true }); // CREATE OR REPLACE
|
|
104
|
+
sqlite_list_views(); // list all views
|
|
105
|
+
sqlite_drop_view({ viewName: "active_orders" });
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
## Virtual Tables
|
|
109
|
+
|
|
110
|
+
\`\`\`javascript
|
|
111
|
+
sqlite_list_virtual_tables(); // list FTS5, R-Tree, CSV tables
|
|
112
|
+
sqlite_virtual_table_info({ tableName: "articles_fts" }); // module and column info
|
|
113
|
+
sqlite_drop_virtual_table({ tableName: "old_fts", ifExists: true });
|
|
114
|
+
\`\`\`
|
|
115
|
+
|
|
116
|
+
## Generate Series (pure JS)
|
|
117
|
+
|
|
118
|
+
\`\`\`javascript
|
|
119
|
+
sqlite_generate_series({ start: 1, stop: 100, step: 5 }); // returns array of values
|
|
120
|
+
// \u26A0\uFE0F Creates a REGULAR table (not virtual) \u2014 use sqlite_drop_table to remove
|
|
121
|
+
sqlite_create_series_table({ tableName: "numbers", start: 1, stop: 1000 });
|
|
122
|
+
\`\`\`
|
|
123
|
+
|
|
124
|
+
## R-Tree (Native only)
|
|
125
|
+
|
|
126
|
+
\`\`\`javascript
|
|
127
|
+
sqlite_create_rtree_table({ tableName: "locations_idx", dimensions: 2 }); // 2D: minX, maxX, minY, maxY
|
|
128
|
+
// Returns graceful error with wasmLimitation: true in WASM
|
|
129
|
+
\`\`\`
|
|
130
|
+
|
|
131
|
+
## CSV Virtual Tables (Native only)
|
|
132
|
+
|
|
133
|
+
\u26A0\uFE0F Requires ABSOLUTE file paths
|
|
134
|
+
|
|
135
|
+
\`\`\`javascript
|
|
136
|
+
sqlite_analyze_csv_schema({ filePath: "/absolute/path/to/data.csv" }); // analyze CSV structure
|
|
137
|
+
sqlite_create_csv_table({
|
|
138
|
+
tableName: "csv_data",
|
|
139
|
+
filePath: "/absolute/path/to/data.csv",
|
|
140
|
+
});
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
## Business Insights
|
|
144
|
+
|
|
145
|
+
\`\`\`javascript
|
|
146
|
+
sqlite_append_insight({ insight: "Q4 revenue increased 23% YoY" }); // add to memo://insights
|
|
147
|
+
\`\`\``],
|
|
148
|
+
["core", `# db-mcp Help \u2014 Core Operations (21 tools)
|
|
149
|
+
|
|
150
|
+
## Basic Queries
|
|
151
|
+
|
|
152
|
+
- \`sqlite_read_query({ query: "SELECT * FROM users LIMIT 10" })\` \u2014 execute SELECT, PRAGMA, EXPLAIN, or WITH statements
|
|
153
|
+
- \`sqlite_write_query({ query: "INSERT INTO users (name) VALUES ('Alice')" })\` \u2014 execute INSERT, UPDATE, DELETE, REPLACE, or trigger DDL (CREATE/DROP TRIGGER)
|
|
154
|
+
|
|
155
|
+
## Tables & Schema
|
|
156
|
+
|
|
157
|
+
- \`sqlite_list_tables({ excludeSystemTables?: boolean })\` \u2014 list all tables in the database (system tables excluded by default)
|
|
158
|
+
- \`sqlite_describe_table({ table: "users" })\` \u2014 get detailed schema, columns, and foreign keys for a specific table. Detects generated columns (VIRTUAL/STORED) with expression.
|
|
159
|
+
- \`sqlite_create_table({ table: "users", columns: [{ name: "id", type: "INTEGER PRIMARY KEY" }, { name: "email", type: "TEXT UNIQUE" }], foreignKeys: [{ column: "role_id", targetTable: "roles" }], checkConstraints: ["price > 0"] })\` \u2014 create a new table with optional table-level constraints. Use \`strict: true\` for STRICT mode (SQLite 3.37+) to enforce column type checking.
|
|
160
|
+
- \`sqlite_drop_table({ table: "users", ifExists?: true })\` \u2014 drop an existing table
|
|
161
|
+
- \`sqlite_alter_table({ table: "users", operation: "add_column", column: "age", type: "INTEGER", nullable: true })\` \u2014 add, rename, or drop columns, or rename a table. Operations: \`add_column\`, \`rename_column\`, \`drop_column\`, \`rename_table\`.
|
|
162
|
+
- \`sqlite_list_triggers({ table?: "users" })\` \u2014 list database triggers, optionally filtered by table
|
|
163
|
+
- \`sqlite_create_trigger({ name: "trg_updated", table: "users", timing: "AFTER", event: "UPDATE", body: "UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id" })\` \u2014 create a trigger with BEFORE/AFTER/INSTEAD OF timing, optional column-specific UPDATE triggers and WHEN conditions
|
|
164
|
+
- \`sqlite_drop_trigger({ name: "trg_updated" })\` \u2014 drop a database trigger
|
|
165
|
+
- \`sqlite_list_constraints({ table: "users" })\` \u2014 list primary key, foreign key, unique, and check constraints for a table
|
|
166
|
+
|
|
167
|
+
## Indexes
|
|
168
|
+
|
|
169
|
+
- \`sqlite_get_indexes({ table?: "users", excludeSystemIndexes?: true })\` \u2014 list indexes (optionally filtered by table)
|
|
170
|
+
- \`sqlite_create_index({ indexName: "idx_users_email", table: "users", columns: ["email"], unique?: true })\` \u2014 create a new index
|
|
171
|
+
- \`sqlite_drop_index({ indexName: "idx_users_email", ifExists?: true })\` \u2014 drop an existing index
|
|
172
|
+
|
|
173
|
+
## Convenience Tools (High-Level Data Operations)
|
|
174
|
+
|
|
175
|
+
- \`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.
|
|
176
|
+
- \`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.
|
|
177
|
+
- \`sqlite_count({ table: "users", where?: "status = 'active'" })\` \u2014 count rows in a table (faster than a full query)
|
|
178
|
+
- \`sqlite_exists({ table: "users", where: "email = 'test@example.com'" })\` \u2014 check if a row exists (stops at first match)
|
|
179
|
+
- \`sqlite_truncate({ table: "users" })\` \u2014 quickly delete all rows from a table (executes \`DELETE FROM table\`)
|
|
180
|
+
- \`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.
|
|
181
|
+
- \`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.`],
|
|
182
|
+
["geo", `# db-mcp Help \u2014 Geospatial Operations (11N/4W: 4 basic + 7 SpatiaLite [NATIVE ONLY])
|
|
183
|
+
|
|
184
|
+
## Basic Geo (always available \u2014 Haversine formula)
|
|
185
|
+
|
|
186
|
+
\`\`\`javascript
|
|
187
|
+
sqlite_geo_distance({
|
|
188
|
+
lat1: 40.7128,
|
|
189
|
+
lon1: -74.006,
|
|
190
|
+
lat2: 34.0522,
|
|
191
|
+
lon2: -118.2437,
|
|
192
|
+
}); // returns km
|
|
193
|
+
sqlite_geo_bounding_box({
|
|
194
|
+
table: "stores",
|
|
195
|
+
latColumn: "lat",
|
|
196
|
+
lonColumn: "lon",
|
|
197
|
+
minLat: 40,
|
|
198
|
+
maxLat: 41,
|
|
199
|
+
minLon: -75,
|
|
200
|
+
maxLon: -73,
|
|
201
|
+
});
|
|
202
|
+
sqlite_geo_nearby({
|
|
203
|
+
table: "stores",
|
|
204
|
+
latColumn: "lat",
|
|
205
|
+
lonColumn: "lon",
|
|
206
|
+
centerLat: 40.7,
|
|
207
|
+
centerLon: -74,
|
|
208
|
+
radius: 10,
|
|
209
|
+
unit: "km",
|
|
210
|
+
});
|
|
211
|
+
sqlite_geo_cluster({
|
|
212
|
+
table: "customers",
|
|
213
|
+
latColumn: "lat",
|
|
214
|
+
lonColumn: "lon",
|
|
215
|
+
gridSize: 0.1,
|
|
216
|
+
});
|
|
217
|
+
\`\`\`
|
|
218
|
+
|
|
219
|
+
## SpatiaLite (7 tools, Native only)
|
|
220
|
+
|
|
221
|
+
\`\`\`javascript
|
|
222
|
+
// Load extension first
|
|
223
|
+
sqlite_spatialite_load(); // \u26A0\uFE0F Required before using other spatial tools
|
|
224
|
+
|
|
225
|
+
// Create spatial table with geometry column
|
|
226
|
+
sqlite_spatialite_create_table({
|
|
227
|
+
tableName: "places",
|
|
228
|
+
geometryColumn: "geom",
|
|
229
|
+
geometryType: "POINT",
|
|
230
|
+
srid: 4326,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Import data (WKT or GeoJSON)
|
|
234
|
+
sqlite_spatialite_import({
|
|
235
|
+
tableName: "places",
|
|
236
|
+
format: "wkt",
|
|
237
|
+
data: "POINT(-73.99 40.75)",
|
|
238
|
+
additionalData: { name: "NYC" },
|
|
239
|
+
});
|
|
240
|
+
sqlite_spatialite_import({
|
|
241
|
+
tableName: "places",
|
|
242
|
+
format: "geojson",
|
|
243
|
+
data: '{"type":"Point","coordinates":[-73.99,40.75]}',
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Spatial queries (SELECT only)
|
|
247
|
+
sqlite_spatialite_query({
|
|
248
|
+
query: "SELECT name, AsText(geom) FROM places WHERE ST_Within(geom, ...)",
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Spatial analysis
|
|
252
|
+
// analysisType: "spatial_extent" | "point_in_polygon" | "nearest_neighbor" | "distance_matrix"
|
|
253
|
+
// \u26A0\uFE0F nearest_neighbor/distance_matrix return CARTESIAN distance (degrees), not geodetic (km/miles)
|
|
254
|
+
// For same source/target table, use excludeSelf: true to avoid self-matches
|
|
255
|
+
sqlite_spatialite_analyze({
|
|
256
|
+
analysisType: "spatial_extent",
|
|
257
|
+
sourceTable: "places",
|
|
258
|
+
geometryColumn: "geom",
|
|
259
|
+
});
|
|
260
|
+
sqlite_spatialite_analyze({
|
|
261
|
+
analysisType: "nearest_neighbor",
|
|
262
|
+
sourceTable: "pts",
|
|
263
|
+
targetTable: "pts",
|
|
264
|
+
excludeSelf: true,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Geometry transforms
|
|
268
|
+
// buffer: 'distance' = radius; simplify: 'distance' = tolerance (0.0001 for lat/lon)
|
|
269
|
+
// \u26A0\uFE0F Buffer auto-simplifies output by default (tolerance=0.0001). Use simplifyTolerance: 0 to disable
|
|
270
|
+
sqlite_spatialite_transform({
|
|
271
|
+
operation: "buffer",
|
|
272
|
+
geometry1: "POINT(-73.99 40.75)",
|
|
273
|
+
distance: 0.01,
|
|
274
|
+
});
|
|
275
|
+
sqlite_spatialite_transform({
|
|
276
|
+
operation: "simplify",
|
|
277
|
+
geometry1: "...",
|
|
278
|
+
distance: 0.001,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Spatial index (R-Tree)
|
|
282
|
+
sqlite_spatialite_index({
|
|
283
|
+
tableName: "places",
|
|
284
|
+
geometryColumn: "geom",
|
|
285
|
+
action: "create",
|
|
286
|
+
}); // create, drop, or check
|
|
287
|
+
\`\`\``],
|
|
288
|
+
["gotchas", `# db-mcp Help \u2014 Gotchas & Code Mode
|
|
289
|
+
|
|
290
|
+
## \u26A0\uFE0F Critical Gotchas
|
|
291
|
+
|
|
292
|
+
1. **sqlite_write_query**: DML only (INSERT/UPDATE/DELETE/REPLACE) \u2014 use \`sqlite_read_query\` for SELECT, and dedicated DDL tools like \`sqlite_create_table\`, \`sqlite_create_trigger\`, and \`sqlite_drop_trigger\` for schema modifications.
|
|
293
|
+
2. **Regex patterns**: Double-escape backslashes (\`\\\\\\\\\`) when passing through JSON/MCP
|
|
294
|
+
3. **FTS5 virtual tables**: \`*_fts\` and shadow tables \`*_fts_*\` are hidden from \`sqlite_list_tables\` for cleaner output
|
|
295
|
+
4. **FTS5 boolean logic**: Uses AND by default \u2014 \`"machine learning"\` = rows with BOTH words. Use OR explicitly: \`"machine OR learning"\`
|
|
296
|
+
5. **json_each row multiplication**: Expands arrays to rows \u2014 use \`limit\` param for large arrays
|
|
297
|
+
6. **json_group_object without groupByColumn**: Each row creates a key-value pair; duplicate keys result if key values aren't unique
|
|
298
|
+
7. **allowExpressions**: For column extraction ONLY (e.g., \`json_extract\`), NOT aggregate functions \u2014 use \`aggregateFunction\` param instead
|
|
299
|
+
8. **sqlite_json_normalize_column**: Defaults to \`preserve\` (maintains original format); use \`outputFormat: 'text'\` to force text
|
|
300
|
+
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
|
|
301
|
+
10. **SpatiaLite distances**: \`nearest_neighbor\`/\`distance_matrix\` return CARTESIAN distance (degrees), not geodetic (km/miles)
|
|
302
|
+
11. **SpatiaLite buffer**: Auto-simplifies output by default (tolerance=0.0001). Use \`simplifyTolerance: 0\` to disable
|
|
303
|
+
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
|
|
304
|
+
13. **CSV virtual tables**: Require ABSOLUTE file paths
|
|
305
|
+
14. **sqlite_create_series_table**: Creates a REGULAR table (not virtual) \u2014 use \`sqlite_drop_table\` to remove
|
|
306
|
+
15. **sqlite_dbstat**: \`summarize\` only works in native; WASM returns counts only
|
|
307
|
+
16. **PRAGMA compile options**: WASM may show FTS3, not FTS5
|
|
308
|
+
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\`).
|
|
309
|
+
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.
|
|
310
|
+
19. **sqlite_batch_insert**: All rows must have the same keys \u2014 inconsistent column sets across rows will cause errors or unexpected NULLs
|
|
311
|
+
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
|
|
312
|
+
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\`
|
|
313
|
+
|
|
314
|
+
## WASM vs Native
|
|
315
|
+
|
|
316
|
+
| Feature | Native | WASM | Fallback |
|
|
317
|
+
| ------------------------------------------------- | --------------------- | ----------- | ---------------- |
|
|
318
|
+
| FTS5 full-text search | \u2705 | \u274C | None |
|
|
319
|
+
| Transactions (8 tools) | \u2705 | \u274C | None |
|
|
320
|
+
| Window functions (6 tools in stats group) | \u2705 | \u274C | None |
|
|
321
|
+
| SpatiaLite GIS (7 tools; 4 basic geo always work) | \u2705 | \u274C | None |
|
|
322
|
+
| Backup/Restore/Dump/VacuumInto/Verify (5 tools) | \u2705 | \u274C | Graceful error |
|
|
323
|
+
| R-Tree spatial indexing | \u2705 | \u274C | Graceful error |
|
|
324
|
+
| CSV virtual tables | \u2705 | \u274C | Graceful error |
|
|
325
|
+
| generate_series | JS fallback | JS fallback | \u2014 |
|
|
326
|
+
| dbstat | \u2705 native (per-table) | \u274C | JS (counts only) |
|
|
327
|
+
| soundex() | \u2705 native | \u274C | JS |
|
|
328
|
+
|
|
329
|
+
## Code Mode (Built-in)
|
|
330
|
+
|
|
331
|
+
| Tool | Description |
|
|
332
|
+
| --------------------- | ----------------------------------------------------------------- |
|
|
333
|
+
| \`sqlite_execute_code\` | Execute JavaScript in a sandboxed environment with \`sqlite.*\` API |
|
|
334
|
+
|
|
335
|
+
**Usage**: \`sqlite_execute_code({ code: "const tables = await sqlite.core.listTables(); return tables;" })\`
|
|
336
|
+
**Discover**: \`sqlite.help()\` for all groups, \`sqlite.<group>.help()\` for methods.
|
|
337
|
+
**Progress**: Use \`await sqlite.reportProgress(current, total, "Message")\` for custom long-running tasks.
|
|
338
|
+
**Groups**: \`sqlite.core\`, \`sqlite.json\`, \`sqlite.text\`, \`sqlite.stats\`, \`sqlite.vector\`, \`sqlite.admin\`, \`sqlite.transactions\` (Native-only), \`sqlite.geo\`, \`sqlite.introspection\`, \`sqlite.migration\`
|
|
339
|
+
|
|
340
|
+
> **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.
|
|
341
|
+
|
|
342
|
+
## Code Mode API Mapping
|
|
343
|
+
|
|
344
|
+
\`sqlite_group_action\` \u2192 \`sqlite.group.action()\` (group prefixes dropped: \`sqlite_json_insert\` \u2192 \`sqlite.json.insert()\`)
|
|
345
|
+
**Exception**: \`stats\` and \`migration\` keep their prefix: \`sqlite_stats_basic\` \u2192 \`sqlite.stats.statsBasic()\`, \`sqlite_migration_apply\` \u2192 \`sqlite.migration.migrationApply()\`
|
|
346
|
+
|
|
347
|
+
**Positional args work**: \`sqlite.core.readQuery("SELECT...")\`, \`sqlite.json.insert("docs", "data", {...})\`
|
|
348
|
+
|
|
349
|
+
**Discovery**: \`sqlite.help()\` returns all groups and methods. \`sqlite.core.help()\`, \`sqlite.json.help()\` for group-specific methods.`],
|
|
350
|
+
["introspection", `# db-mcp Help \u2014 Schema Introspection (10 tools)
|
|
351
|
+
|
|
352
|
+
All introspection tools are **read-only** \u2014 they query PRAGMAs and sqlite_master, never modify data.
|
|
353
|
+
|
|
354
|
+
## Graph Analysis (3 tools)
|
|
355
|
+
|
|
356
|
+
\`\`\`javascript
|
|
357
|
+
// Build FK dependency graph \u2014 nodesOnly: true for lightweight response (nodes without edges)
|
|
358
|
+
sqlite_dependency_graph({ includeRowCounts: true, excludeSystemTables: true });
|
|
359
|
+
sqlite_dependency_graph({ nodesOnly: true }); // lighter payload
|
|
360
|
+
|
|
361
|
+
// Safe DDL execution order \u2014 "create" = parents first (default), "drop" = children first
|
|
362
|
+
sqlite_topological_sort({ direction: "create", excludeSystemTables: true });
|
|
363
|
+
sqlite_topological_sort({ direction: "drop" }); // safe DROP order
|
|
364
|
+
|
|
365
|
+
// Simulate DELETE/DROP/TRUNCATE impact \u2014 shows affected tables, cascade paths, severity scoring
|
|
366
|
+
sqlite_cascade_simulator({ table: "users", operation: "DELETE" });
|
|
367
|
+
sqlite_cascade_simulator({ table: "users", operation: "DROP", compact: true }); // compact omits path arrays
|
|
368
|
+
\`\`\`
|
|
369
|
+
|
|
370
|
+
## Schema Analysis (4 tools)
|
|
371
|
+
|
|
372
|
+
\`\`\`javascript
|
|
373
|
+
// Full schema in one call \u2014 compact: true omits column details, sections limits scope
|
|
374
|
+
sqlite_schema_snapshot({
|
|
375
|
+
sections: ["tables", "indexes"],
|
|
376
|
+
excludeSystemTables: true,
|
|
377
|
+
});
|
|
378
|
+
sqlite_schema_snapshot({ compact: true }); // lighter payload
|
|
379
|
+
|
|
380
|
+
// Compare a saved snapshot against the current live schema \u2014 structured drift analysis
|
|
381
|
+
sqlite_schema_diff({
|
|
382
|
+
baseline: previousSnapshot, // from an earlier sqlite_schema_snapshot call
|
|
383
|
+
target: "current",
|
|
384
|
+
});
|
|
385
|
+
// Compare specific sections only
|
|
386
|
+
sqlite_schema_diff({
|
|
387
|
+
baseline: "current",
|
|
388
|
+
target: modifiedSnapshot,
|
|
389
|
+
sections: ["tables", "indexes"],
|
|
390
|
+
});
|
|
391
|
+
// Both sides inline (offline diff, no DB queries)
|
|
392
|
+
sqlite_schema_diff({ baseline: snapshotA, target: snapshotB });
|
|
393
|
+
|
|
394
|
+
// Find constraint health issues \u2014 missing PKs, nullable FKs, unindexed FKs
|
|
395
|
+
sqlite_constraint_analysis({ excludeSystemTables: true });
|
|
396
|
+
sqlite_constraint_analysis({
|
|
397
|
+
table: "orders",
|
|
398
|
+
checks: ["missing_pk", "unindexed_fk"],
|
|
399
|
+
}); // limit scope
|
|
400
|
+
|
|
401
|
+
// Analyze DDL for SQLite-specific risks \u2014 does NOT execute the statements
|
|
402
|
+
sqlite_migration_risks({
|
|
403
|
+
statements: [
|
|
404
|
+
"ALTER TABLE users ADD COLUMN email TEXT",
|
|
405
|
+
"DROP TABLE old_logs",
|
|
406
|
+
],
|
|
407
|
+
});
|
|
408
|
+
\`\`\`
|
|
409
|
+
|
|
410
|
+
## Diagnostics (3 tools)
|
|
411
|
+
|
|
412
|
+
\`\`\`javascript
|
|
413
|
+
// Fragmentation, per-table size breakdown, optimization recommendations
|
|
414
|
+
sqlite_storage_analysis({
|
|
415
|
+
includeTableDetails: true,
|
|
416
|
+
excludeSystemTables: true,
|
|
417
|
+
});
|
|
418
|
+
sqlite_storage_analysis({ limit: 10 }); // top 10 tables only
|
|
419
|
+
|
|
420
|
+
// Audit index effectiveness \u2014 find redundant, missing FK, unindexed large tables
|
|
421
|
+
sqlite_index_audit({ excludeSystemTables: true });
|
|
422
|
+
sqlite_index_audit({ table: "orders", minSeverity: "warning" }); // reduce payload
|
|
423
|
+
|
|
424
|
+
// EXPLAIN QUERY PLAN with scan-type classification and optimization suggestions (SELECT/WITH only)
|
|
425
|
+
sqlite_query_plan({ sql: "SELECT * FROM orders WHERE status = 'active'" });
|
|
426
|
+
\`\`\`
|
|
427
|
+
|
|
428
|
+
## \u26A0\uFE0F Gotchas
|
|
429
|
+
|
|
430
|
+
- \`excludeSystemTables\` defaults to \`true\` \u2014 SpatiaLite system tables are hidden for cleaner output. Pass \`false\` to include them
|
|
431
|
+
- \`sqlite_migration_risks\` analyzes DDL text statically \u2014 it does NOT execute the statements`],
|
|
432
|
+
["json", `# db-mcp Help \u2014 JSON Operations (25 tools)
|
|
433
|
+
|
|
434
|
+
## Collection & CRUD
|
|
435
|
+
|
|
436
|
+
- \`sqlite_create_json_collection({ tableName, indexes: [{ path: "$.type" }, { path: "$.author" }] })\` \u2014 creates table with JSON indexes
|
|
437
|
+
- \`sqlite_json_insert({ table, column, data: { type: "article", title: "Hello", tags: ["news"] } })\` \u2014 insert JSON document
|
|
438
|
+
- \`sqlite_json_select({ table, column, paths? })\` \u2014 select rows, optionally extract specific JSON paths
|
|
439
|
+
- \`sqlite_json_update({ table, column, path, value, whereClause })\` \u2014 update value at JSON path
|
|
440
|
+
- \`sqlite_json_query({ table, column, filterPaths: { "$.type": "article" }, selectPaths: ["$.title"] })\` \u2014 query with path-based filters and projections
|
|
441
|
+
|
|
442
|
+
## Path Operations
|
|
443
|
+
|
|
444
|
+
- \`sqlite_json_extract({ table, column, path: "$.title" })\` \u2014 extract value at path. Returns null if path doesn't exist
|
|
445
|
+
- \`sqlite_json_set({ table, column, path: "$.views", value: 100, whereClause: "id = 1" })\` \u2014 set value at path (creates if missing)
|
|
446
|
+
- \`sqlite_json_merge({ table, column, mergeData: { featured: true }, whereClause: "id = 1" })\` \u2014 merge object into existing JSON
|
|
447
|
+
- \`sqlite_json_remove({ table, column, path, whereClause })\` \u2014 remove key at path
|
|
448
|
+
- \`sqlite_json_validate_path({ path: "$.store.books[0].title" })\` \u2014 validate JSON path syntax without executing a query
|
|
449
|
+
- \`sqlite_json_type({ table, column, path })\` \u2014 get JSON type (null, true, false, integer, real, text, array, object)
|
|
450
|
+
|
|
451
|
+
## Array Operations
|
|
452
|
+
|
|
453
|
+
\u26A0\uFE0F \`json_each\` multiplies output rows \u2014 use \`limit\` param for large arrays
|
|
454
|
+
|
|
455
|
+
- \`sqlite_json_array_append({ table, column, path: "$.tags", value: "featured", whereClause: "id = 1" })\` \u2014 append to array
|
|
456
|
+
- \`sqlite_json_array_length({ table, column, path })\` \u2014 get array length
|
|
457
|
+
- \`sqlite_json_each({ table, column, path: "$.tags", limit: 50 })\` \u2014 expand array to rows
|
|
458
|
+
|
|
459
|
+
## Aggregation & Analysis
|
|
460
|
+
|
|
461
|
+
\`\`\`javascript
|
|
462
|
+
// Regular tables: use column names directly
|
|
463
|
+
sqlite_json_group_array({
|
|
464
|
+
table: "events",
|
|
465
|
+
valueColumn: "user_id",
|
|
466
|
+
groupByColumn: "event_type",
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// JSON collections: use allowExpressions with json_extract for both columns
|
|
470
|
+
// \u26A0\uFE0F allowExpressions is for column extraction ONLY, NOT aggregate functions
|
|
471
|
+
// \u26A0\uFE0F Without groupByColumn, each row creates a key-value pair; duplicate keys if values aren't unique
|
|
472
|
+
sqlite_json_group_array({
|
|
473
|
+
table: "docs",
|
|
474
|
+
valueColumn: "json_extract(data, '$.author')",
|
|
475
|
+
groupByColumn: "json_extract(data, '$.type')",
|
|
476
|
+
allowExpressions: true,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// For aggregate values (COUNT, SUM, AVG), use aggregateFunction parameter instead
|
|
480
|
+
sqlite_json_group_object({
|
|
481
|
+
table: "events",
|
|
482
|
+
keyColumn: "event_type",
|
|
483
|
+
aggregateFunction: "COUNT(*)",
|
|
484
|
+
});
|
|
485
|
+
sqlite_json_group_object({
|
|
486
|
+
table: "orders",
|
|
487
|
+
keyColumn: "status",
|
|
488
|
+
aggregateFunction: "SUM(total)",
|
|
489
|
+
});
|
|
490
|
+
\`\`\`
|
|
491
|
+
|
|
492
|
+
- \`sqlite_json_keys({ table, column, path? })\` \u2014 get distinct keys of JSON objects
|
|
493
|
+
- \`sqlite_json_pretty({ json: "{\\"name\\": \\"Alice\\"}" })\` \u2014 format JSON string with indentation for readability
|
|
494
|
+
- \`sqlite_json_valid({ json: "{\\"name\\": \\"Alice\\"}" })\` \u2014 check if a string is valid JSON
|
|
495
|
+
- \`sqlite_json_analyze_schema({ table, column })\` \u2014 infer schema types
|
|
496
|
+
- \`sqlite_json_diff({ table, column, path1, path2 })\` \u2014 compare two JSON paths within the same row
|
|
497
|
+
|
|
498
|
+
## Security
|
|
499
|
+
|
|
500
|
+
- \`sqlite_json_security_scan({ table, column, sampleSize?, whereClause? })\` \u2014 scan JSON column for sensitive keys (password, token, ssn, etc.), SQL injection patterns, and XSS patterns. Returns \`riskLevel\` (low/medium/high) and issue details. Use larger \`sampleSize\` for thorough scans
|
|
501
|
+
|
|
502
|
+
## JSONB Optimization (SQLite 3.45+)
|
|
503
|
+
|
|
504
|
+
- \`sqlite_json_storage_info({ table, column })\` \u2014 check text vs JSONB format
|
|
505
|
+
- \`sqlite_jsonb_convert({ table, column })\` \u2014 convert to JSONB for faster queries
|
|
506
|
+
- \`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`],
|
|
507
|
+
["migration", `# db-mcp Help \u2014 Migration Tracking (6 tools)
|
|
508
|
+
|
|
509
|
+
\u26A0\uFE0F Must call \`sqlite_migration_init()\` before using any other migration tool \u2014 it creates the tracking table.
|
|
510
|
+
|
|
511
|
+
\`\`\`javascript
|
|
512
|
+
// Initialize tracking table (idempotent \u2014 safe to call multiple times)
|
|
513
|
+
sqlite_migration_init();
|
|
514
|
+
|
|
515
|
+
// Apply migration: executes SQL and records atomically. If SQL fails, no record is created
|
|
516
|
+
// SHA-256 dedup \u2014 submitting the same SQL twice is rejected as a duplicate
|
|
517
|
+
sqlite_migration_apply({
|
|
518
|
+
version: "2024-01-15-add-users",
|
|
519
|
+
description: "Create users table",
|
|
520
|
+
sql: "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE)",
|
|
521
|
+
rollbackSql: "DROP TABLE users",
|
|
522
|
+
sourceSystem: "agent",
|
|
523
|
+
appliedBy: "claude",
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Record externally-applied migration (does NOT execute the SQL)
|
|
527
|
+
sqlite_migration_record({
|
|
528
|
+
version: "2024-01-10-initial",
|
|
529
|
+
description: "Initial schema (pre-existing)",
|
|
530
|
+
sql: "CREATE TABLE orders (...)",
|
|
531
|
+
sourceSystem: "manual",
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// Rollback by ID or version \u2014 requires rollbackSql to have been recorded
|
|
535
|
+
sqlite_migration_rollback({ version: "2024-01-15-add-users" });
|
|
536
|
+
sqlite_migration_rollback({ id: 3, dryRun: true }); // preview SQL without executing
|
|
537
|
+
|
|
538
|
+
// Query history with filters and pagination
|
|
539
|
+
sqlite_migration_history({ status: "applied", limit: 10 });
|
|
540
|
+
sqlite_migration_history({ sourceSystem: "agent", compact: true }); // compact omits hash and source system
|
|
541
|
+
|
|
542
|
+
// Summary: latest version, counts by status, unique source systems
|
|
543
|
+
sqlite_migration_status();
|
|
544
|
+
\`\`\`
|
|
545
|
+
|
|
546
|
+
## \u26A0\uFE0F Gotchas
|
|
547
|
+
|
|
548
|
+
- Rollback requires \`rollbackSql\` to have been provided when the migration was recorded/applied
|
|
549
|
+
- 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)`],
|
|
550
|
+
["stats", `# db-mcp Help \u2014 Statistical Analysis (23N/17W: 17 core + 6 window [NATIVE ONLY])
|
|
551
|
+
|
|
552
|
+
## Core Statistics (always available)
|
|
553
|
+
|
|
554
|
+
\`\`\`javascript
|
|
555
|
+
sqlite_stats_basic({ table: "employees", column: "salary" }); // count, sum, avg, min, max
|
|
556
|
+
sqlite_stats_count({ table: "orders", column: "status", distinct: true }); // count rows, optionally distinct
|
|
557
|
+
sqlite_stats_group_by({ table: "orders", column: "total", groupBy: "status" }); // aggregate by group
|
|
558
|
+
sqlite_stats_percentile({
|
|
559
|
+
table: "sales",
|
|
560
|
+
column: "revenue",
|
|
561
|
+
percentiles: [25, 50, 75, 90],
|
|
562
|
+
});
|
|
563
|
+
sqlite_stats_histogram({ table: "products", column: "price", buckets: 10 });
|
|
564
|
+
sqlite_stats_correlation({ table: "data", column1: "x", column2: "y" }); // Pearson coefficient
|
|
565
|
+
sqlite_stats_distinct({ table: "orders", column: "status" }); // distinct values
|
|
566
|
+
sqlite_stats_sample({ table: "orders", sampleSize: 100 }); // random sample
|
|
567
|
+
sqlite_stats_summary({
|
|
568
|
+
table: "data",
|
|
569
|
+
columns: ["price", "quantity", "discount"],
|
|
570
|
+
}); // multi-column summary
|
|
571
|
+
sqlite_stats_frequency({ table: "orders", column: "status" }); // frequency distribution
|
|
572
|
+
sqlite_stats_regression({ table: "data", xColumn: "year", yColumn: "revenue" }); // linear
|
|
573
|
+
sqlite_stats_regression({
|
|
574
|
+
table: "data",
|
|
575
|
+
xColumn: "year",
|
|
576
|
+
yColumn: "revenue",
|
|
577
|
+
degree: 2,
|
|
578
|
+
}); // quadratic
|
|
579
|
+
sqlite_stats_outliers({ table: "sales", column: "amount", method: "iqr" }); // or "zscore". Use maxOutliers to limit payload
|
|
580
|
+
sqlite_stats_hypothesis({
|
|
581
|
+
table: "samples",
|
|
582
|
+
column: "value",
|
|
583
|
+
testType: "ttest_one",
|
|
584
|
+
expectedMean: 100,
|
|
585
|
+
});
|
|
586
|
+
\`\`\`
|
|
587
|
+
|
|
588
|
+
\u26A0\uFE0F \`sqlite_stats_top_n\` \u2014 always use \`selectColumns\` to avoid returning all columns (large payloads with text fields):
|
|
589
|
+
|
|
590
|
+
\`\`\`javascript
|
|
591
|
+
sqlite_stats_top_n({
|
|
592
|
+
table: "products",
|
|
593
|
+
column: "price",
|
|
594
|
+
n: 10,
|
|
595
|
+
selectColumns: ["id", "name", "price"],
|
|
596
|
+
});
|
|
597
|
+
\`\`\`
|
|
598
|
+
|
|
599
|
+
## Anomaly Detection (3 tools)
|
|
600
|
+
|
|
601
|
+
\`\`\`javascript
|
|
602
|
+
// Data distribution anomaly detection \u2014 z-score analysis on numeric columns
|
|
603
|
+
sqlite_stats_detect_anomalies({ table: "sales", threshold: 2.0 }); // auto-detects numeric columns
|
|
604
|
+
sqlite_stats_detect_anomalies({
|
|
605
|
+
table: "metrics",
|
|
606
|
+
columns: ["latency_ms", "error_rate"],
|
|
607
|
+
threshold: 3.0,
|
|
608
|
+
limit: 20,
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// Fragmentation/bloat risk scoring \u2014 PRAGMA + dbstat analysis
|
|
612
|
+
sqlite_stats_detect_bloat(); // riskiest tables (>0 score), default limit 50
|
|
613
|
+
sqlite_stats_detect_bloat({ includeZeroRisk: true, limit: 10 }); // include zero risk, top 10
|
|
614
|
+
|
|
615
|
+
// Schema health risk scoring \u2014 FK indexes, PKs, wide tables
|
|
616
|
+
sqlite_stats_detect_schema_risks(); // riskiest tables (>0 score)
|
|
617
|
+
sqlite_stats_detect_schema_risks({ excludeSystemTables: false }); // include SpatiaLite tables
|
|
618
|
+
\`\`\`
|
|
619
|
+
|
|
620
|
+
## Window Functions (6 tools, Native only)
|
|
621
|
+
|
|
622
|
+
\`\`\`javascript
|
|
623
|
+
sqlite_window_row_number({
|
|
624
|
+
table: "employees",
|
|
625
|
+
orderBy: "hire_date",
|
|
626
|
+
partitionBy: "department",
|
|
627
|
+
});
|
|
628
|
+
sqlite_window_rank({
|
|
629
|
+
table: "sales",
|
|
630
|
+
orderBy: "revenue DESC",
|
|
631
|
+
partitionBy: "region",
|
|
632
|
+
rankType: "dense_rank",
|
|
633
|
+
});
|
|
634
|
+
sqlite_window_lag_lead({
|
|
635
|
+
table: "sales",
|
|
636
|
+
orderBy: "date",
|
|
637
|
+
column: "revenue",
|
|
638
|
+
partitionBy: "region",
|
|
639
|
+
}); // access previous/next row values
|
|
640
|
+
sqlite_window_running_total({
|
|
641
|
+
table: "transactions",
|
|
642
|
+
column: "amount",
|
|
643
|
+
orderBy: "date",
|
|
644
|
+
});
|
|
645
|
+
sqlite_window_moving_avg({
|
|
646
|
+
table: "stock_prices",
|
|
647
|
+
column: "close_price",
|
|
648
|
+
orderBy: "date",
|
|
649
|
+
windowSize: 7,
|
|
650
|
+
});
|
|
651
|
+
sqlite_window_ntile({
|
|
652
|
+
table: "employees",
|
|
653
|
+
orderBy: "salary DESC",
|
|
654
|
+
buckets: 4,
|
|
655
|
+
partitionBy: "department",
|
|
656
|
+
}); // quartiles
|
|
657
|
+
\`\`\`
|
|
658
|
+
|
|
659
|
+
\u26A0\uFE0F **Payload Sizes**: Window functions retrieve all standard columns by default (up to the \`limit\`). When querying wide tables, this can result in large payloads. It is highly recommended to pass \`selectColumns\` to reduce token usage:
|
|
660
|
+
|
|
661
|
+
\`\`\`javascript
|
|
662
|
+
sqlite_window_moving_avg({
|
|
663
|
+
table: "stock_prices",
|
|
664
|
+
column: "close_price",
|
|
665
|
+
orderBy: "date",
|
|
666
|
+
windowSize: 7,
|
|
667
|
+
selectColumns: ["date"],
|
|
668
|
+
});
|
|
669
|
+
\`\`\``],
|
|
670
|
+
["text", `# db-mcp Help \u2014 Text Processing & FTS5 (19N/14W: 14 text + 5 FTS5 [NATIVE ONLY])
|
|
671
|
+
|
|
672
|
+
## Full-Text Search / FTS5 (5 tools, Native only)
|
|
673
|
+
|
|
674
|
+
\`\`\`javascript
|
|
675
|
+
// Create FTS5 table with triggers for auto-sync on future changes
|
|
676
|
+
// (Dropping this FTS table via sqlite_drop_table will automatically clean up these sync triggers)
|
|
677
|
+
sqlite_fts_create({
|
|
678
|
+
tableName: "articles_fts",
|
|
679
|
+
sourceTable: "articles",
|
|
680
|
+
columns: ["title", "content"],
|
|
681
|
+
});
|
|
682
|
+
sqlite_fts_rebuild({ table: "articles_fts" }); // \u26A0\uFE0F Required: populate index with existing data
|
|
683
|
+
|
|
684
|
+
// FTS5 uses AND by default: "machine learning" = rows containing BOTH words
|
|
685
|
+
// Use OR explicitly: "machine OR learning" for rows containing EITHER word
|
|
686
|
+
sqlite_fts_search({
|
|
687
|
+
table: "articles_fts",
|
|
688
|
+
query: "machine learning",
|
|
689
|
+
limit: 10,
|
|
690
|
+
});
|
|
691
|
+
sqlite_fts_match_info({ table: "articles_fts", query: "machine learning" }); // bm25 ranking info
|
|
692
|
+
|
|
693
|
+
// Highlighted snippets from FTS5 search matches (headline/snippet)
|
|
694
|
+
sqlite_fts_headline({ table: "articles_fts", query: "machine learning" });
|
|
695
|
+
// \u2192 results with <b>machine</b> <b>learning</b> highlighted in context
|
|
696
|
+
|
|
697
|
+
// Custom markers and snippet window
|
|
698
|
+
sqlite_fts_headline({
|
|
699
|
+
table: "articles_fts",
|
|
700
|
+
query: "database",
|
|
701
|
+
startSel: "**",
|
|
702
|
+
stopSel: "**",
|
|
703
|
+
snippetWords: 5,
|
|
704
|
+
});
|
|
705
|
+
\`\`\`
|
|
706
|
+
|
|
707
|
+
\u26A0\uFE0F FTS5 virtual tables (\`*_fts\`) and shadow tables (\`*_fts_*\`) are hidden from \`sqlite_list_tables\` for cleaner output
|
|
708
|
+
|
|
709
|
+
## Text Processing (14 tools)
|
|
710
|
+
|
|
711
|
+
\`\`\`javascript
|
|
712
|
+
// Regex patterns: \u26A0\uFE0F Double-escape backslashes (\\\\) when passing through JSON/MCP
|
|
713
|
+
sqlite_regex_match({
|
|
714
|
+
table: "logs",
|
|
715
|
+
column: "message",
|
|
716
|
+
pattern: "ERROR:\\\\\\\\s+(\\\\\\\\w+)",
|
|
717
|
+
});
|
|
718
|
+
sqlite_regex_extract({
|
|
719
|
+
table: "users",
|
|
720
|
+
column: "email",
|
|
721
|
+
pattern: "@([a-zA-Z0-9.-]+)",
|
|
722
|
+
groupIndex: 1,
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
// Text manipulation
|
|
726
|
+
sqlite_text_split({ table: "users", column: "email", delimiter: "@" }); // split into parts array
|
|
727
|
+
sqlite_text_concat({
|
|
728
|
+
table: "users",
|
|
729
|
+
columns: ["first_name", "last_name"],
|
|
730
|
+
separator: " ",
|
|
731
|
+
});
|
|
732
|
+
sqlite_text_replace({
|
|
733
|
+
table: "docs",
|
|
734
|
+
column: "content",
|
|
735
|
+
searchPattern: "old",
|
|
736
|
+
replaceWith: "new",
|
|
737
|
+
});
|
|
738
|
+
sqlite_text_trim({ table: "users", column: "name" }); // trim whitespace
|
|
739
|
+
sqlite_text_case({ table: "products", column: "name", mode: "upper" }); // or "lower"
|
|
740
|
+
sqlite_text_substring({ table: "codes", column: "value", start: 1, length: 3 }); // extract substring
|
|
741
|
+
sqlite_text_normalize({
|
|
742
|
+
table: "docs",
|
|
743
|
+
column: "content",
|
|
744
|
+
mode: "strip_accents",
|
|
745
|
+
}); // or nfc, nfd, nfkc, nfkd
|
|
746
|
+
|
|
747
|
+
// Validation patterns: email, phone, url, uuid, ipv4, custom
|
|
748
|
+
sqlite_text_validate({ table: "users", column: "email", pattern: "email" });
|
|
749
|
+
sqlite_text_validate({
|
|
750
|
+
table: "data",
|
|
751
|
+
column: "field",
|
|
752
|
+
pattern: "custom",
|
|
753
|
+
customPattern: "^[A-Z]{2}[0-9]{4}$",
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
// Fuzzy matching \u2014 matches WORD TOKENS by default (not entire value)
|
|
757
|
+
// "laptop" matches "Laptop Pro 15" (distance 0 on first token). Use tokenize: false for full-string matching.
|
|
758
|
+
sqlite_fuzzy_match({
|
|
759
|
+
table: "products",
|
|
760
|
+
column: "name",
|
|
761
|
+
search: "laptop",
|
|
762
|
+
maxDistance: 2,
|
|
763
|
+
});
|
|
764
|
+
sqlite_fuzzy_match({
|
|
765
|
+
table: "products",
|
|
766
|
+
column: "name",
|
|
767
|
+
search: "laptob",
|
|
768
|
+
maxDistance: 2,
|
|
769
|
+
tokenize: false,
|
|
770
|
+
}); // full value
|
|
771
|
+
|
|
772
|
+
// Phonetic matching \u2014 finds words that sound alike (matches against any word in value)
|
|
773
|
+
// Use includeRowData: false for lighter payloads when only matching values are needed
|
|
774
|
+
sqlite_phonetic_match({
|
|
775
|
+
table: "products",
|
|
776
|
+
column: "name",
|
|
777
|
+
search: "laptop",
|
|
778
|
+
algorithm: "soundex",
|
|
779
|
+
});
|
|
780
|
+
sqlite_phonetic_match({
|
|
781
|
+
table: "products",
|
|
782
|
+
column: "name",
|
|
783
|
+
search: "laptop",
|
|
784
|
+
includeRowData: false,
|
|
785
|
+
}); // lighter
|
|
786
|
+
|
|
787
|
+
// Advanced search \u2014 combines exact, fuzzy, and phonetic techniques
|
|
788
|
+
// fuzzyThreshold: 0.3-0.4 = loose matching (more results), 0.6-0.8 = strict matching (fewer results)
|
|
789
|
+
sqlite_advanced_search({
|
|
790
|
+
table: "products",
|
|
791
|
+
column: "name",
|
|
792
|
+
searchTerm: "laptop",
|
|
793
|
+
techniques: ["exact", "fuzzy", "phonetic"],
|
|
794
|
+
fuzzyThreshold: 0.4,
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
// Sentiment analysis \u2014 text analysis (can analyze raw text or database columns)
|
|
798
|
+
sqlite_text_sentiment({ text: "This product is amazing and wonderful!" });
|
|
799
|
+
// \u2192 { sentiment: "very_positive", score: 2, confidence: "medium" }
|
|
800
|
+
|
|
801
|
+
sqlite_text_sentiment({ table: "articles", column: "body" }); // analyzes all rows in a column
|
|
802
|
+
|
|
803
|
+
sqlite_text_sentiment({
|
|
804
|
+
text: "Great service but slow delivery",
|
|
805
|
+
returnWords: true,
|
|
806
|
+
});
|
|
807
|
+
// \u2192 { sentiment: "neutral", score: 0, matchedPositive: ["great"], matchedNegative: ["slow"] }
|
|
808
|
+
\`\`\``],
|
|
809
|
+
["transactions", `# db-mcp Help \u2014 Transactions (8 tools, Native only)
|
|
810
|
+
|
|
811
|
+
## Atomic Execution (preferred for simple cases)
|
|
812
|
+
|
|
813
|
+
\`\`\`javascript
|
|
814
|
+
sqlite_transaction_execute({
|
|
815
|
+
statements: ["UPDATE a SET x=1", "UPDATE b SET y=2"],
|
|
816
|
+
});
|
|
817
|
+
\`\`\`
|
|
818
|
+
|
|
819
|
+
## Manual Transaction Control
|
|
820
|
+
|
|
821
|
+
\`\`\`javascript
|
|
822
|
+
sqlite_transaction_begin({ mode: "immediate" }); // or "deferred", "exclusive"
|
|
823
|
+
sqlite_transaction_savepoint({ name: "checkpoint" });
|
|
824
|
+
sqlite_transaction_rollback_to({ name: "checkpoint" });
|
|
825
|
+
sqlite_transaction_release({ name: "checkpoint" });
|
|
826
|
+
sqlite_transaction_commit();
|
|
827
|
+
sqlite_transaction_rollback();
|
|
828
|
+
\`\`\`
|
|
829
|
+
|
|
830
|
+
## Transaction State (read-only)
|
|
831
|
+
|
|
832
|
+
\`\`\`javascript
|
|
833
|
+
sqlite_transaction_status(); // \u2192 { status: "active" | "none", active: true/false }
|
|
834
|
+
\`\`\`
|
|
835
|
+
|
|
836
|
+
## \u26A0\uFE0F Gotchas
|
|
837
|
+
|
|
838
|
+
- Transaction tools are **Native only** \u2014 WASM adapter does not support transactions
|
|
839
|
+
- Use \`sqlite_transaction_execute\` for simple multi-statement operations; manual \`begin\`/\`commit\` for complex flows with savepoints
|
|
840
|
+
- \`sqlite_transaction_status\` is read-only and requires only \`read\` scope; all other transaction tools require \`write\` scope`],
|
|
841
|
+
["vector", `# db-mcp Help \u2014 Vector/Semantic Search (11 tools)
|
|
842
|
+
|
|
843
|
+
\`\`\`javascript
|
|
844
|
+
// Create vector table with metadata columns
|
|
845
|
+
sqlite_vector_create_table({ tableName: "docs", dimensions: 384, additionalColumns: [{ name: "content", type: "TEXT" }] });
|
|
846
|
+
|
|
847
|
+
// Store vectors (single and batch)
|
|
848
|
+
sqlite_vector_store({ table: "docs", idColumn: "id", vectorColumn: "emb", id: 1, vector: [...] });
|
|
849
|
+
sqlite_vector_batch_store({ table: "docs", idColumn: "id", vectorColumn: "emb", items: [{ id: 1, vector: [...] }, { id: 2, vector: [...] }] });
|
|
850
|
+
|
|
851
|
+
// Search vectors (returnColumns omits vector data from results for smaller payloads)
|
|
852
|
+
sqlite_vector_search({ table: "docs", vectorColumn: "emb", queryVector: [...], limit: 10, returnColumns: ["id", "title"] }); // returns { results: [...] } instead of rows
|
|
853
|
+
|
|
854
|
+
// Retrieve and delete vectors
|
|
855
|
+
// Note: sqlite_vector_get returns parsed 'vector' array + additional columns in a 'metadata' object for flexibility
|
|
856
|
+
sqlite_vector_get({ table: "docs", idColumn: "id", vectorColumn: "emb", id: 1 });
|
|
857
|
+
sqlite_vector_delete({ table: "docs", idColumn: "id", ids: [1, 2, 3] });
|
|
858
|
+
|
|
859
|
+
// Vector metadata
|
|
860
|
+
sqlite_vector_count({ table: "docs" }); // or with dimensions filter: { table: "docs", dimensions: 384 }
|
|
861
|
+
sqlite_vector_dimensions({ table: "docs", vectorColumn: "emb" });
|
|
862
|
+
sqlite_vector_stats({ table: "docs", vectorColumn: "emb" }); // returns sampleSize, magnitudeStats (min/max/avg)
|
|
863
|
+
|
|
864
|
+
// Utility tools for preprocessing
|
|
865
|
+
sqlite_vector_normalize({ vector: [3, 4, 0, 0] }); // returns unit vector [0.6, 0.8, 0, 0]
|
|
866
|
+
sqlite_vector_distance({ vector1: [...], vector2: [...], metric: "cosine" }); // returns { value: <number> }
|
|
867
|
+
\`\`\``]
|
|
868
|
+
]);
|
|
869
|
+
var BUFFER_HIGH_WATER = 50;
|
|
870
|
+
var FLUSH_INTERVAL_MS = 100;
|
|
871
|
+
var DEFAULT_RECENT_COUNT = 50;
|
|
872
|
+
var STDERR_SENTINEL = "stderr";
|
|
873
|
+
var TAIL_READ_BYTES = 65536;
|
|
874
|
+
var AuditLogger = class {
|
|
875
|
+
config;
|
|
876
|
+
buffer = [];
|
|
877
|
+
flushTimer = null;
|
|
878
|
+
activeFlush = null;
|
|
879
|
+
closed = false;
|
|
880
|
+
dirEnsured = false;
|
|
881
|
+
stderrMode;
|
|
882
|
+
constructor(config) {
|
|
883
|
+
this.config = config;
|
|
884
|
+
this.stderrMode = config.logPath.toLowerCase() === STDERR_SENTINEL;
|
|
885
|
+
if (config.enabled) {
|
|
886
|
+
this.flushTimer = setInterval(() => {
|
|
887
|
+
void this.flush();
|
|
888
|
+
}, FLUSH_INTERVAL_MS);
|
|
889
|
+
this.flushTimer.unref();
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Eagerly create the log directory and touch the log file on startup.
|
|
894
|
+
* Provides immediate confirmation that audit is configured correctly.
|
|
895
|
+
* Non-throwing — falls back silently if the path is inaccessible.
|
|
896
|
+
*/
|
|
897
|
+
async init() {
|
|
898
|
+
if (this.stderrMode || !this.config.enabled) return;
|
|
899
|
+
try {
|
|
900
|
+
await this.ensureDirectory();
|
|
901
|
+
const fh = await open(this.config.logPath, "a");
|
|
902
|
+
await fh.close();
|
|
903
|
+
} catch (err) {
|
|
904
|
+
process.stderr.write(
|
|
905
|
+
`[AUDIT] Failed to initialise log file: ${this.config.logPath}
|
|
906
|
+
`
|
|
907
|
+
);
|
|
908
|
+
throw err;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Append an audit entry to the buffer.
|
|
913
|
+
* Non-blocking — the entry is serialised and queued; the
|
|
914
|
+
* actual file write happens on the next flush cycle.
|
|
915
|
+
*/
|
|
916
|
+
log(entry) {
|
|
917
|
+
if (this.closed || !this.config.enabled) return;
|
|
918
|
+
const safeEntry = redactObject(entry, 0, 5);
|
|
919
|
+
this.buffer.push(JSON.stringify(safeEntry));
|
|
920
|
+
if (this.buffer.length >= BUFFER_HIGH_WATER) {
|
|
921
|
+
void this.flush();
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Flush the buffer to disk.
|
|
926
|
+
* Safe to call concurrently — serialises via `this.activeFlush` Promise.
|
|
927
|
+
*/
|
|
928
|
+
async flush() {
|
|
929
|
+
if (this.activeFlush) {
|
|
930
|
+
await this.activeFlush;
|
|
931
|
+
if (this.buffer.length === 0) return;
|
|
932
|
+
}
|
|
933
|
+
if (this.buffer.length === 0) return;
|
|
934
|
+
const doFlush = async () => {
|
|
935
|
+
await this.rotateIfNeeded();
|
|
936
|
+
const lines = this.buffer;
|
|
937
|
+
this.buffer = [];
|
|
938
|
+
try {
|
|
939
|
+
if (this.stderrMode) {
|
|
940
|
+
process.stderr.write(lines.join("\n") + "\n");
|
|
941
|
+
} else {
|
|
942
|
+
await this.ensureDirectory();
|
|
943
|
+
await appendFile(
|
|
944
|
+
this.config.logPath,
|
|
945
|
+
lines.join("\n") + "\n",
|
|
946
|
+
"utf-8"
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
} catch (err) {
|
|
950
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
951
|
+
process.stderr.write(`[AUDIT] Write failed: ${message}
|
|
952
|
+
`);
|
|
953
|
+
this.buffer.unshift(...lines);
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
this.activeFlush = doFlush();
|
|
957
|
+
try {
|
|
958
|
+
await this.activeFlush;
|
|
959
|
+
} finally {
|
|
960
|
+
this.activeFlush = null;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Gracefully close the logger — flush remaining entries and stop the timer.
|
|
965
|
+
*/
|
|
966
|
+
async close() {
|
|
967
|
+
this.closed = true;
|
|
968
|
+
if (this.flushTimer) {
|
|
969
|
+
clearInterval(this.flushTimer);
|
|
970
|
+
this.flushTimer = null;
|
|
971
|
+
}
|
|
972
|
+
await this.flush();
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Read the most recent audit entries from the log file.
|
|
976
|
+
* Uses a streaming tail-read: only the last TAIL_READ_BYTES (64 KB) are
|
|
977
|
+
* read from disk, preventing O(n) memory spikes for large audit logs.
|
|
978
|
+
* Used by the `sqlite://audit` resource.
|
|
979
|
+
*
|
|
980
|
+
* @param count Maximum number of entries to return (default 50)
|
|
981
|
+
*/
|
|
982
|
+
async recent(count = DEFAULT_RECENT_COUNT) {
|
|
983
|
+
if (this.stderrMode) return [];
|
|
984
|
+
await this.flush();
|
|
985
|
+
try {
|
|
986
|
+
let fh;
|
|
987
|
+
try {
|
|
988
|
+
fh = await open(this.config.logPath, "r");
|
|
989
|
+
} catch {
|
|
990
|
+
return [];
|
|
991
|
+
}
|
|
992
|
+
try {
|
|
993
|
+
const info = await stat(this.config.logPath);
|
|
994
|
+
const fileSize = info.size;
|
|
995
|
+
if (fileSize === 0) return [];
|
|
996
|
+
const readSize = Math.min(fileSize, TAIL_READ_BYTES);
|
|
997
|
+
const startOffset = fileSize - readSize;
|
|
998
|
+
const buf = Buffer.alloc(readSize);
|
|
999
|
+
await fh.read(buf, 0, readSize, startOffset);
|
|
1000
|
+
const chunk = buf.toString("utf-8");
|
|
1001
|
+
const rawLines = chunk.split("\n").filter(Boolean);
|
|
1002
|
+
const lines = startOffset > 0 ? rawLines.slice(1) : rawLines;
|
|
1003
|
+
const tail = lines.slice(-count);
|
|
1004
|
+
return tail.reduce((acc, line) => {
|
|
1005
|
+
try {
|
|
1006
|
+
acc.push(JSON.parse(line));
|
|
1007
|
+
} catch {
|
|
1008
|
+
}
|
|
1009
|
+
return acc;
|
|
1010
|
+
}, []);
|
|
1011
|
+
} finally {
|
|
1012
|
+
await fh.close();
|
|
1013
|
+
}
|
|
1014
|
+
} catch {
|
|
1015
|
+
return [];
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Ensure the parent directory of the log file exists.
|
|
1020
|
+
*/
|
|
1021
|
+
async ensureDirectory() {
|
|
1022
|
+
if (this.dirEnsured) return;
|
|
1023
|
+
try {
|
|
1024
|
+
await mkdir(dirname(this.config.logPath), { recursive: true });
|
|
1025
|
+
this.dirEnsured = true;
|
|
1026
|
+
} catch {
|
|
1027
|
+
this.dirEnsured = true;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Rotate the log file if it exceeds the configured size limit.
|
|
1032
|
+
* Keeps up to 5 rotated files (`.1` through `.5`); older data is discarded.
|
|
1033
|
+
* Rotation failure is non-fatal — audit must not block tool execution.
|
|
1034
|
+
*/
|
|
1035
|
+
async rotateIfNeeded() {
|
|
1036
|
+
if (this.stderrMode || !this.config.maxSizeBytes) return;
|
|
1037
|
+
try {
|
|
1038
|
+
const info = await stat(this.config.logPath).catch(() => null);
|
|
1039
|
+
if (!info || info.size < this.config.maxSizeBytes) return;
|
|
1040
|
+
for (let i = 4; i >= 1; i--) {
|
|
1041
|
+
const oldFile = `${this.config.logPath}.${String(i)}`;
|
|
1042
|
+
const newFile = `${this.config.logPath}.${String(i + 1)}`;
|
|
1043
|
+
await rename(oldFile, newFile).catch(() => null);
|
|
1044
|
+
}
|
|
1045
|
+
const rotatedPath = `${this.config.logPath}.1`;
|
|
1046
|
+
await rename(this.config.logPath, rotatedPath);
|
|
1047
|
+
} catch {
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
var gzipAsync = promisify(gzip);
|
|
1052
|
+
var SNAPSHOT_TOOL_ARGS = {
|
|
1053
|
+
sqlite_drop_table: { targetKey: "table" },
|
|
1054
|
+
sqlite_drop_index: { targetKey: "index" },
|
|
1055
|
+
sqlite_drop_view: { targetKey: "viewName" },
|
|
1056
|
+
sqlite_import_csv: { targetKey: "table" },
|
|
1057
|
+
sqlite_backup: { targetKey: "targetPath" }
|
|
1058
|
+
};
|
|
1059
|
+
var SNAPSHOT_EXT = ".snapshot.json.gz";
|
|
1060
|
+
var SNAPSHOT_EXT_LEGACY = ".snapshot.json";
|
|
1061
|
+
var MAX_SAMPLE_ROWS = 100;
|
|
1062
|
+
var DEFAULT_MAX_DATA_SIZE_BYTES = 50 * 1024 * 1024;
|
|
1063
|
+
var BackupManager = class {
|
|
1064
|
+
config;
|
|
1065
|
+
snapshotDir;
|
|
1066
|
+
dirEnsured = false;
|
|
1067
|
+
pendingWrites = /* @__PURE__ */ new Set();
|
|
1068
|
+
constructor(config, auditLogPath) {
|
|
1069
|
+
this.config = config;
|
|
1070
|
+
const logDir = dirname(auditLogPath);
|
|
1071
|
+
this.snapshotDir = join(logDir, "snapshots");
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Check if a tool should receive a pre-mutation snapshot.
|
|
1075
|
+
*/
|
|
1076
|
+
shouldSnapshot(toolName) {
|
|
1077
|
+
return this.config.enabled && toolName in SNAPSHOT_TOOL_ARGS;
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Create a pre-mutation snapshot of the target object.
|
|
1081
|
+
*
|
|
1082
|
+
* @returns Relative path to the snapshot file, or undefined if skipped/failed
|
|
1083
|
+
*/
|
|
1084
|
+
async createSnapshot(toolName, args, requestId, adapter, logAs) {
|
|
1085
|
+
if (!this.shouldSnapshot(toolName)) return void 0;
|
|
1086
|
+
try {
|
|
1087
|
+
const mapping = SNAPSHOT_TOOL_ARGS[toolName];
|
|
1088
|
+
if (!mapping) return void 0;
|
|
1089
|
+
const rawTarget = args[mapping.targetKey];
|
|
1090
|
+
const target = typeof rawTarget === "string" ? rawTarget : "unknown";
|
|
1091
|
+
return await this.captureObjectSnapshot(
|
|
1092
|
+
logAs ?? toolName,
|
|
1093
|
+
target,
|
|
1094
|
+
requestId,
|
|
1095
|
+
adapter
|
|
1096
|
+
);
|
|
1097
|
+
} catch (err) {
|
|
1098
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1099
|
+
process.stderr.write(
|
|
1100
|
+
`[AUDIT-BACKUP] Snapshot failed for ${toolName}: ${message}
|
|
1101
|
+
`
|
|
1102
|
+
);
|
|
1103
|
+
return void 0;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* List available snapshots with metadata.
|
|
1108
|
+
*/
|
|
1109
|
+
async listSnapshots(limit = 10, offset = 0) {
|
|
1110
|
+
try {
|
|
1111
|
+
await this.ensureDirectory();
|
|
1112
|
+
const files = await readdir(this.snapshotDir);
|
|
1113
|
+
const snapshotFiles = files.filter(
|
|
1114
|
+
(f) => f.endsWith(SNAPSHOT_EXT) || f.endsWith(SNAPSHOT_EXT_LEGACY)
|
|
1115
|
+
);
|
|
1116
|
+
snapshotFiles.sort((a, b) => b.localeCompare(a));
|
|
1117
|
+
const total = snapshotFiles.length;
|
|
1118
|
+
const targetFiles = snapshotFiles.slice(offset, offset + limit);
|
|
1119
|
+
const snapshots = [];
|
|
1120
|
+
for (const file of targetFiles) {
|
|
1121
|
+
try {
|
|
1122
|
+
const parsed = await this.readSnapshotFile(file);
|
|
1123
|
+
if (parsed) {
|
|
1124
|
+
snapshots.push({ ...parsed.metadata, filename: file });
|
|
1125
|
+
}
|
|
1126
|
+
} catch {
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return { snapshots, total };
|
|
1130
|
+
} catch {
|
|
1131
|
+
return { snapshots: [], total: 0 };
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Read a specific snapshot by filename.
|
|
1136
|
+
*/
|
|
1137
|
+
async getSnapshot(filename) {
|
|
1138
|
+
try {
|
|
1139
|
+
const safe = basename(filename);
|
|
1140
|
+
return await this.readSnapshotFile(safe);
|
|
1141
|
+
} catch {
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Apply retention policy — delete oldest snapshots that exceed limits.
|
|
1147
|
+
*/
|
|
1148
|
+
async cleanup() {
|
|
1149
|
+
if (!this.config.enabled) return 0;
|
|
1150
|
+
try {
|
|
1151
|
+
const files = await readdir(this.snapshotDir);
|
|
1152
|
+
const snapshotFiles = files.filter(
|
|
1153
|
+
(f) => f.endsWith(SNAPSHOT_EXT) || f.endsWith(SNAPSHOT_EXT_LEGACY)
|
|
1154
|
+
);
|
|
1155
|
+
if (snapshotFiles.length === 0) return 0;
|
|
1156
|
+
const fileInfos = [];
|
|
1157
|
+
for (const file of snapshotFiles) {
|
|
1158
|
+
const filePath = join(this.snapshotDir, file);
|
|
1159
|
+
try {
|
|
1160
|
+
const stats = await stat(filePath);
|
|
1161
|
+
fileInfos.push({ name: file, mtime: stats.mtime, path: filePath });
|
|
1162
|
+
} catch {
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
fileInfos.sort((a, b) => a.mtime.getTime() - b.mtime.getTime());
|
|
1166
|
+
let deleted = 0;
|
|
1167
|
+
const now = Date.now();
|
|
1168
|
+
const maxAgeMs = this.config.maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
1169
|
+
for (const info of fileInfos) {
|
|
1170
|
+
const age = now - info.mtime.getTime();
|
|
1171
|
+
const overAge = age > maxAgeMs;
|
|
1172
|
+
const overCount = fileInfos.length - deleted > this.config.maxCount;
|
|
1173
|
+
if (overAge || overCount) {
|
|
1174
|
+
try {
|
|
1175
|
+
await unlink(info.path);
|
|
1176
|
+
deleted++;
|
|
1177
|
+
} catch {
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
if (deleted > 0) {
|
|
1182
|
+
process.stderr.write(
|
|
1183
|
+
`[AUDIT-BACKUP] Cleaned up ${String(deleted)} snapshot(s)
|
|
1184
|
+
`
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
return deleted;
|
|
1188
|
+
} catch {
|
|
1189
|
+
return 0;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Flush all pending async snapshot writes.
|
|
1194
|
+
* Call during graceful shutdown to ensure all snapshots are persisted.
|
|
1195
|
+
*/
|
|
1196
|
+
async flush() {
|
|
1197
|
+
if (this.pendingWrites.size > 0) {
|
|
1198
|
+
await Promise.allSettled(this.pendingWrites);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
async getStats() {
|
|
1202
|
+
try {
|
|
1203
|
+
const files = await readdir(this.snapshotDir);
|
|
1204
|
+
const snapshotFiles = files.filter(
|
|
1205
|
+
(f) => f.endsWith(SNAPSHOT_EXT) || f.endsWith(SNAPSHOT_EXT_LEGACY)
|
|
1206
|
+
);
|
|
1207
|
+
let totalSize = 0;
|
|
1208
|
+
let oldestMtime;
|
|
1209
|
+
for (const file of snapshotFiles) {
|
|
1210
|
+
try {
|
|
1211
|
+
const stats = await stat(join(this.snapshotDir, file));
|
|
1212
|
+
totalSize += stats.size;
|
|
1213
|
+
if (!oldestMtime || stats.mtime < oldestMtime) {
|
|
1214
|
+
oldestMtime = stats.mtime;
|
|
1215
|
+
}
|
|
1216
|
+
} catch {
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return {
|
|
1220
|
+
count: snapshotFiles.length,
|
|
1221
|
+
...oldestMtime && { oldestAge: oldestMtime.toISOString() },
|
|
1222
|
+
totalSizeKB: Math.round(totalSize / 1024)
|
|
1223
|
+
};
|
|
1224
|
+
} catch {
|
|
1225
|
+
return { count: 0, totalSizeKB: 0 };
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
// =========================================================================
|
|
1229
|
+
// Private snapshot capture methods
|
|
1230
|
+
// =========================================================================
|
|
1231
|
+
async captureObjectSnapshot(toolName, target, requestId, adapter) {
|
|
1232
|
+
const ddl = await this.buildDdl(target, adapter);
|
|
1233
|
+
const { rowCount } = await this.captureVolumeMetadata(target, adapter);
|
|
1234
|
+
const { data, dataSkipped, dataSkippedReason } = await this.captureTableData(target, adapter);
|
|
1235
|
+
return this.writeSnapshot(toolName, target, requestId, ddl, data, {
|
|
1236
|
+
...rowCount !== void 0 && { rowCount },
|
|
1237
|
+
...dataSkipped && { dataSkipped },
|
|
1238
|
+
...dataSkippedReason !== void 0 && { dataSkippedReason }
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Build a DDL string from sqlite_master for the given object.
|
|
1243
|
+
* Works for tables, views, indexes, and triggers.
|
|
1244
|
+
*/
|
|
1245
|
+
async buildDdl(objectName, adapter) {
|
|
1246
|
+
try {
|
|
1247
|
+
const result = await adapter.executeQuery(
|
|
1248
|
+
"SELECT sql FROM sqlite_master WHERE name = ?",
|
|
1249
|
+
[objectName]
|
|
1250
|
+
);
|
|
1251
|
+
if (result.rows && result.rows.length > 0) {
|
|
1252
|
+
const row = result.rows[0];
|
|
1253
|
+
if (row?.["sql"] != null) {
|
|
1254
|
+
const val = row["sql"];
|
|
1255
|
+
return typeof val === "string" ? val : "";
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
} catch (e) {
|
|
1259
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1260
|
+
process.stderr.write(
|
|
1261
|
+
`[AUDIT-BACKUP] DDL capture failed for ${objectName}: ${msg}
|
|
1262
|
+
`
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
return `-- Object "${objectName}" does not exist or cannot be described`;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Capture row count using COUNT(*).
|
|
1269
|
+
* Near-zero cost for small tables; failures are silently ignored (best-effort).
|
|
1270
|
+
*/
|
|
1271
|
+
async captureVolumeMetadata(tableName, adapter) {
|
|
1272
|
+
try {
|
|
1273
|
+
const typeResult = await adapter.executeQuery(
|
|
1274
|
+
"SELECT type FROM sqlite_master WHERE name = ?",
|
|
1275
|
+
[tableName]
|
|
1276
|
+
);
|
|
1277
|
+
const objType = typeResult.rows?.[0]?.["type"];
|
|
1278
|
+
if (objType !== "table") return {};
|
|
1279
|
+
const countResult = await adapter.executeQuery(
|
|
1280
|
+
`SELECT COUNT(*) as row_count FROM "${tableName}"`
|
|
1281
|
+
);
|
|
1282
|
+
const rawCount = countResult.rows?.[0]?.["row_count"];
|
|
1283
|
+
if (typeof rawCount === "number") {
|
|
1284
|
+
return { rowCount: rawCount };
|
|
1285
|
+
}
|
|
1286
|
+
} catch {
|
|
1287
|
+
}
|
|
1288
|
+
return {};
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Capture row data as INSERT statements, subject to config limits.
|
|
1292
|
+
* Returns empty output when `includeData` is disabled.
|
|
1293
|
+
*/
|
|
1294
|
+
async captureTableData(tableName, adapter) {
|
|
1295
|
+
if (!this.config.includeData) {
|
|
1296
|
+
return { dataSkipped: false };
|
|
1297
|
+
}
|
|
1298
|
+
const maxDataSize = this.config.maxDataSizeBytes || DEFAULT_MAX_DATA_SIZE_BYTES;
|
|
1299
|
+
try {
|
|
1300
|
+
const typeResult = await adapter.executeQuery(
|
|
1301
|
+
"SELECT type FROM sqlite_master WHERE name = ?",
|
|
1302
|
+
[tableName]
|
|
1303
|
+
);
|
|
1304
|
+
const objType = typeResult.rows?.[0]?.["type"];
|
|
1305
|
+
if (objType !== "table") return { dataSkipped: false };
|
|
1306
|
+
} catch {
|
|
1307
|
+
return { dataSkipped: false };
|
|
1308
|
+
}
|
|
1309
|
+
try {
|
|
1310
|
+
const pageResult = await adapter.executeQuery(
|
|
1311
|
+
"SELECT (SELECT page_count FROM pragma_page_count()) * (SELECT page_size FROM pragma_page_size()) as total_size"
|
|
1312
|
+
);
|
|
1313
|
+
const rawSize = pageResult.rows?.[0]?.["total_size"];
|
|
1314
|
+
const totalSizeBytes = typeof rawSize === "number" ? rawSize : 0;
|
|
1315
|
+
if (totalSizeBytes > maxDataSize) {
|
|
1316
|
+
const sizeMB = Math.round(totalSizeBytes / (1024 * 1024));
|
|
1317
|
+
const thresholdMB = Math.round(maxDataSize / (1024 * 1024));
|
|
1318
|
+
return {
|
|
1319
|
+
dataSkipped: true,
|
|
1320
|
+
dataSkippedReason: `Database size ~${String(sizeMB)}MB exceeds ${String(thresholdMB)}MB threshold`
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
} catch {
|
|
1324
|
+
}
|
|
1325
|
+
try {
|
|
1326
|
+
const result = await adapter.executeQuery(
|
|
1327
|
+
`SELECT * FROM "${tableName}" LIMIT ${String(MAX_SAMPLE_ROWS)}`
|
|
1328
|
+
);
|
|
1329
|
+
if (result.rows && result.rows.length > 0) {
|
|
1330
|
+
const firstRow = result.rows[0];
|
|
1331
|
+
if (firstRow) {
|
|
1332
|
+
const cols = Object.keys(firstRow).map((c) => `"${c}"`).join(", ");
|
|
1333
|
+
const data = result.rows.map((row) => {
|
|
1334
|
+
const vals = Object.values(row).map((v) => {
|
|
1335
|
+
if (v === null) return "NULL";
|
|
1336
|
+
if (typeof v === "string")
|
|
1337
|
+
return `'${v.replace(/'/g, "''")}'`;
|
|
1338
|
+
if (typeof v === "number" || typeof v === "boolean")
|
|
1339
|
+
return String(v);
|
|
1340
|
+
return `'${JSON.stringify(v).replace(/'/g, "''")}'`;
|
|
1341
|
+
}).join(", ");
|
|
1342
|
+
return `INSERT INTO "${tableName}" (${cols}) VALUES (${vals});`;
|
|
1343
|
+
}).join("\n");
|
|
1344
|
+
return { data, dataSkipped: false };
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
} catch {
|
|
1348
|
+
}
|
|
1349
|
+
return { dataSkipped: false };
|
|
1350
|
+
}
|
|
1351
|
+
async writeSnapshot(tool, target, requestId, ddl, data, volumeMeta) {
|
|
1352
|
+
await this.ensureDirectory();
|
|
1353
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1354
|
+
const safeTarget = target.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1355
|
+
const safeTool = tool.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1356
|
+
const filename = `${timestamp.replace(/[:.]/g, "-")}_${safeTool}_${safeTarget}${SNAPSHOT_EXT}`;
|
|
1357
|
+
const content = {
|
|
1358
|
+
metadata: {
|
|
1359
|
+
timestamp,
|
|
1360
|
+
tool,
|
|
1361
|
+
target,
|
|
1362
|
+
type: data ? "ddl+data" : "ddl",
|
|
1363
|
+
requestId,
|
|
1364
|
+
sizeBytes: 0,
|
|
1365
|
+
// Updated after serialization
|
|
1366
|
+
...volumeMeta?.rowCount !== void 0 && {
|
|
1367
|
+
rowCount: volumeMeta.rowCount
|
|
1368
|
+
},
|
|
1369
|
+
...volumeMeta?.dataSkipped && { dataSkipped: true },
|
|
1370
|
+
...volumeMeta?.dataSkippedReason && {
|
|
1371
|
+
dataSkippedReason: volumeMeta.dataSkippedReason
|
|
1372
|
+
}
|
|
1373
|
+
},
|
|
1374
|
+
ddl,
|
|
1375
|
+
data
|
|
1376
|
+
};
|
|
1377
|
+
const json = JSON.stringify(content, null, 2);
|
|
1378
|
+
const sizeBytes = Buffer.byteLength(json, "utf-8");
|
|
1379
|
+
const finalJson = json.replace(
|
|
1380
|
+
'"sizeBytes": 0',
|
|
1381
|
+
`"sizeBytes": ${String(sizeBytes)}`
|
|
1382
|
+
);
|
|
1383
|
+
const compressed = await gzipAsync(Buffer.from(finalJson, "utf-8"));
|
|
1384
|
+
const filePath = join(this.snapshotDir, filename);
|
|
1385
|
+
const writePromise = mkdtemp(`${this.snapshotDir}${sep}.tmp-`).then(async (tempDir) => {
|
|
1386
|
+
const tmpPath = join(tempDir, filename);
|
|
1387
|
+
await writeFile(tmpPath, compressed, { flag: "wx" });
|
|
1388
|
+
await rename(tmpPath, filePath);
|
|
1389
|
+
await rmdir(tempDir).catch(() => null);
|
|
1390
|
+
}).catch((err) => {
|
|
1391
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1392
|
+
process.stderr.write(
|
|
1393
|
+
`[AUDIT-BACKUP] Async write failed for ${filename}: ${msg}
|
|
1394
|
+
`
|
|
1395
|
+
);
|
|
1396
|
+
});
|
|
1397
|
+
this.pendingWrites.add(writePromise);
|
|
1398
|
+
void writePromise.finally(() => {
|
|
1399
|
+
this.pendingWrites.delete(writePromise);
|
|
1400
|
+
});
|
|
1401
|
+
return filename;
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Read and decompress a snapshot file (supports both gzip and legacy JSON).
|
|
1405
|
+
*/
|
|
1406
|
+
async readSnapshotFile(filename) {
|
|
1407
|
+
const filePath = join(this.snapshotDir, filename);
|
|
1408
|
+
const raw = await readFile(filePath);
|
|
1409
|
+
if (raw[0] === 31 && raw[1] === 139) {
|
|
1410
|
+
const decompressed = gunzipSync(raw);
|
|
1411
|
+
return JSON.parse(decompressed.toString("utf-8"));
|
|
1412
|
+
}
|
|
1413
|
+
return JSON.parse(raw.toString("utf-8"));
|
|
1414
|
+
}
|
|
1415
|
+
async ensureDirectory() {
|
|
1416
|
+
if (this.dirEnsured) return;
|
|
1417
|
+
try {
|
|
1418
|
+
await mkdir(this.snapshotDir, { recursive: true });
|
|
1419
|
+
this.dirEnsured = true;
|
|
1420
|
+
} catch {
|
|
1421
|
+
this.dirEnsured = true;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
var SENSITIVE_KEY_PATTERN = /^(password|passwd|token|secret|authorization|api_?key|credential|private_?key|access_?token|refresh_?token)$/i;
|
|
1426
|
+
var SENSITIVE_VALUE_PATTERN = /(?:sk-|Bearer |token\s*[:=]\s*|password\s*[:=]\s*|secret\s*[:=]\s*|apikey\s*[:=]\s*|api_key\s*[:=]\s*|AWS_SECRET_ACCESS_KEY\s*[:=]\s*|GITHUB_TOKEN\s*[:=]\s*|ghp_|gho_|ghu_|ghs_|xoxb-|xoxp-|xoxs-|AZURE_[A-Z_]*\s*[:=]\s*|DATABASE_URL\s*[:=]\s*|AKIA|sk-ant-api[a-zA-Z0-9_-]+|sk_live_[a-zA-Z0-9_]+|rk_live_[a-zA-Z0-9_]+|SG\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+|npm_[a-zA-Z0-9]{30,}|dpl_[a-zA-Z0-9]{30,}|hvs\.[a-zA-Z0-9_-]+)[^\s'",;)}\]]{4,}/gi;
|
|
1427
|
+
function redactSensitiveKeys(value, depth = 0) {
|
|
1428
|
+
const MAX_DEPTH = 5;
|
|
1429
|
+
if (depth > MAX_DEPTH || value === null || value === void 0) {
|
|
1430
|
+
return value;
|
|
1431
|
+
}
|
|
1432
|
+
if (Array.isArray(value)) {
|
|
1433
|
+
return value.map((item) => redactSensitiveKeys(item, depth + 1));
|
|
1434
|
+
}
|
|
1435
|
+
if (typeof value === "string") {
|
|
1436
|
+
const isSql = /^\s*(?:SELECT|INSERT|UPDATE|DELETE|WITH|CREATE|ALTER|DROP|PRAGMA)\b/i.test(
|
|
1437
|
+
value
|
|
1438
|
+
);
|
|
1439
|
+
const scrubbed = isSql ? value.replace(/'(?:''|[^'])*'/g, "'***'") : value;
|
|
1440
|
+
return scrubbed.replace(SENSITIVE_VALUE_PATTERN, "[REDACTED]");
|
|
1441
|
+
}
|
|
1442
|
+
if (typeof value === "object") {
|
|
1443
|
+
const result = {};
|
|
1444
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1445
|
+
if (SENSITIVE_KEY_PATTERN.test(key)) {
|
|
1446
|
+
result[key] = "[REDACTED]";
|
|
1447
|
+
} else if (typeof val === "object" && val !== null) {
|
|
1448
|
+
result[key] = redactSensitiveKeys(val, depth + 1);
|
|
1449
|
+
} else if (typeof val === "string") {
|
|
1450
|
+
const isSql = key.toLowerCase() === "sql" || key.toLowerCase() === "query" || /^\s*(?:SELECT|INSERT|UPDATE|DELETE|WITH|CREATE|ALTER|DROP|PRAGMA)\b/i.test(
|
|
1451
|
+
val
|
|
1452
|
+
);
|
|
1453
|
+
const scrubbed = isSql ? val.replace(/'(?:''|[^'])*'/g, "'***'") : val;
|
|
1454
|
+
result[key] = scrubbed.replace(SENSITIVE_VALUE_PATTERN, "[REDACTED]");
|
|
1455
|
+
} else {
|
|
1456
|
+
result[key] = val;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
return result;
|
|
1460
|
+
}
|
|
1461
|
+
return value;
|
|
1462
|
+
}
|
|
1463
|
+
var ALWAYS_AUDITED_SCOPES = /* @__PURE__ */ new Set(["write", "admin"]);
|
|
1464
|
+
function scopeToCategory(scope) {
|
|
1465
|
+
if (scope === "admin") return "admin";
|
|
1466
|
+
if (scope === "read") return "read";
|
|
1467
|
+
return "write";
|
|
1468
|
+
}
|
|
1469
|
+
function createAuditInterceptor(auditLogger, backupManager) {
|
|
1470
|
+
const auditReads = auditLogger.config.auditReads;
|
|
1471
|
+
let currentQueryAdapter;
|
|
1472
|
+
return {
|
|
1473
|
+
setQueryAdapter(adapter) {
|
|
1474
|
+
currentQueryAdapter = adapter;
|
|
1475
|
+
},
|
|
1476
|
+
async around(toolName, args, requestId, fn, options) {
|
|
1477
|
+
const scope = getRequiredScope(toolName);
|
|
1478
|
+
if (!ALWAYS_AUDITED_SCOPES.has(scope) && !auditReads) {
|
|
1479
|
+
return fn();
|
|
1480
|
+
}
|
|
1481
|
+
const isReadScope = scope === "read";
|
|
1482
|
+
const authCtx = getAuthContext();
|
|
1483
|
+
const start = performance.now();
|
|
1484
|
+
let success = true;
|
|
1485
|
+
let error;
|
|
1486
|
+
let backupRef;
|
|
1487
|
+
let tokenEstimate;
|
|
1488
|
+
if (backupManager !== void 0 && currentQueryAdapter !== void 0 && backupManager.shouldSnapshot(toolName)) {
|
|
1489
|
+
try {
|
|
1490
|
+
backupRef = await backupManager.createSnapshot(
|
|
1491
|
+
toolName,
|
|
1492
|
+
args ?? {},
|
|
1493
|
+
requestId,
|
|
1494
|
+
currentQueryAdapter,
|
|
1495
|
+
options?.logAs
|
|
1496
|
+
);
|
|
1497
|
+
} catch {
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
try {
|
|
1501
|
+
const result = await fn();
|
|
1502
|
+
if (typeof result === "object" && result !== null) {
|
|
1503
|
+
try {
|
|
1504
|
+
const json = JSON.stringify({
|
|
1505
|
+
...result,
|
|
1506
|
+
_meta: { tokenEstimate: 0 }
|
|
1507
|
+
});
|
|
1508
|
+
tokenEstimate = Math.ceil(Buffer.byteLength(json, "utf8") / 4);
|
|
1509
|
+
} catch {
|
|
1510
|
+
}
|
|
1511
|
+
} else if (typeof result === "string") {
|
|
1512
|
+
tokenEstimate = Math.ceil(Buffer.byteLength(result, "utf8") / 4);
|
|
1513
|
+
}
|
|
1514
|
+
return result;
|
|
1515
|
+
} catch (err) {
|
|
1516
|
+
success = false;
|
|
1517
|
+
error = sanitizeErrorMessage(
|
|
1518
|
+
err instanceof Error ? err.message : String(err)
|
|
1519
|
+
);
|
|
1520
|
+
const errorResult = {
|
|
1521
|
+
success: false,
|
|
1522
|
+
error,
|
|
1523
|
+
code: "INTERNAL_ERROR",
|
|
1524
|
+
category: "internal",
|
|
1525
|
+
recoverable: false
|
|
1526
|
+
};
|
|
1527
|
+
const enriched = JSON.stringify({
|
|
1528
|
+
...errorResult,
|
|
1529
|
+
_meta: { tokenEstimate: 0 }
|
|
1530
|
+
});
|
|
1531
|
+
tokenEstimate = Math.ceil(Buffer.byteLength(enriched, "utf8") / 4);
|
|
1532
|
+
throw err;
|
|
1533
|
+
} finally {
|
|
1534
|
+
const durationMs = Math.round(performance.now() - start);
|
|
1535
|
+
if (isReadScope) {
|
|
1536
|
+
auditLogger.log({
|
|
1537
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1538
|
+
requestId,
|
|
1539
|
+
tool: options?.logAs ?? toolName,
|
|
1540
|
+
category: "read",
|
|
1541
|
+
scope,
|
|
1542
|
+
durationMs,
|
|
1543
|
+
success,
|
|
1544
|
+
error,
|
|
1545
|
+
tokenEstimate
|
|
1546
|
+
});
|
|
1547
|
+
} else {
|
|
1548
|
+
auditLogger.log({
|
|
1549
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1550
|
+
requestId,
|
|
1551
|
+
tool: options?.logAs ?? toolName,
|
|
1552
|
+
category: scopeToCategory(scope),
|
|
1553
|
+
scope,
|
|
1554
|
+
user: authCtx?.claims?.sub ?? null,
|
|
1555
|
+
scopes: authCtx?.scopes ?? [],
|
|
1556
|
+
durationMs,
|
|
1557
|
+
success,
|
|
1558
|
+
error,
|
|
1559
|
+
args: auditLogger.config.redact ? void 0 : redactSensitiveKeys(args),
|
|
1560
|
+
backup: backupRef,
|
|
1561
|
+
tokenEstimate
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// src/server/registration/built-in-tools.ts
|
|
1570
|
+
function registerBuiltInTools(server, adaptersMap, config, toolFilter) {
|
|
1571
|
+
const serverInfoOpts = {
|
|
1572
|
+
title: "Server Info",
|
|
1573
|
+
description: "Get information about the db-mcp server and registered adapters",
|
|
1574
|
+
icons: SERVER_ICONS,
|
|
1575
|
+
annotations: READ_ONLY
|
|
1576
|
+
};
|
|
1577
|
+
server.registerTool("server_info", serverInfoOpts, () => {
|
|
1578
|
+
const authCtx = getAuthContext();
|
|
1579
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "server_info")) {
|
|
1580
|
+
throw new InsufficientScopeError(["read"], authCtx.scopes);
|
|
1581
|
+
}
|
|
1582
|
+
const adapterInfo = [];
|
|
1583
|
+
for (const [id, adapter] of adaptersMap) {
|
|
1584
|
+
adapterInfo.push({
|
|
1585
|
+
id,
|
|
1586
|
+
...adapter.getInfo()
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
return {
|
|
1590
|
+
content: [
|
|
1591
|
+
{
|
|
1592
|
+
type: "text",
|
|
1593
|
+
text: JSON.stringify(
|
|
1594
|
+
{
|
|
1595
|
+
name: config.name,
|
|
1596
|
+
version: config.version,
|
|
1597
|
+
transport: config.transport,
|
|
1598
|
+
adapters: adapterInfo,
|
|
1599
|
+
toolFilter: {
|
|
1600
|
+
raw: toolFilter.raw,
|
|
1601
|
+
enabledGroups: [...toolFilter.enabledGroups]
|
|
1602
|
+
}
|
|
1603
|
+
},
|
|
1604
|
+
null,
|
|
1605
|
+
2
|
|
1606
|
+
)
|
|
1607
|
+
}
|
|
1608
|
+
]
|
|
1609
|
+
};
|
|
1610
|
+
});
|
|
1611
|
+
const healthOpts = {
|
|
1612
|
+
title: "Server Health",
|
|
1613
|
+
description: "Check health status of all database connections",
|
|
1614
|
+
icons: SERVER_ICONS,
|
|
1615
|
+
annotations: READ_ONLY
|
|
1616
|
+
};
|
|
1617
|
+
server.registerTool("server_health", healthOpts, async () => {
|
|
1618
|
+
const authCtx = getAuthContext();
|
|
1619
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "server_health")) {
|
|
1620
|
+
throw new InsufficientScopeError(["read"], authCtx.scopes);
|
|
1621
|
+
}
|
|
1622
|
+
const health = {
|
|
1623
|
+
server: "healthy",
|
|
1624
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1625
|
+
adapters: {}
|
|
1626
|
+
};
|
|
1627
|
+
for (const [id, adapter] of adaptersMap) {
|
|
1628
|
+
try {
|
|
1629
|
+
const adapterHealth = await adapter.getHealth();
|
|
1630
|
+
health["adapters"][id] = adapterHealth;
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
let errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
1633
|
+
errorMsg = errorMsg.replace(/(:[^:@/]+@)/g, ":***@");
|
|
1634
|
+
health["adapters"][id] = {
|
|
1635
|
+
connected: false,
|
|
1636
|
+
error: errorMsg
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return {
|
|
1641
|
+
content: [
|
|
1642
|
+
{
|
|
1643
|
+
type: "text",
|
|
1644
|
+
text: JSON.stringify(health, null, 2)
|
|
1645
|
+
}
|
|
1646
|
+
]
|
|
1647
|
+
};
|
|
1648
|
+
});
|
|
1649
|
+
const listAdaptersOpts = {
|
|
1650
|
+
title: "List Adapters",
|
|
1651
|
+
description: "List all registered database adapters",
|
|
1652
|
+
icons: SERVER_ICONS,
|
|
1653
|
+
annotations: READ_ONLY
|
|
1654
|
+
};
|
|
1655
|
+
server.registerTool("list_adapters", listAdaptersOpts, () => {
|
|
1656
|
+
const authCtx = getAuthContext();
|
|
1657
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "list_adapters")) {
|
|
1658
|
+
throw new InsufficientScopeError(["read"], authCtx.scopes);
|
|
1659
|
+
}
|
|
1660
|
+
const adapters = [];
|
|
1661
|
+
for (const [, adapter] of adaptersMap) {
|
|
1662
|
+
adapters.push({
|
|
1663
|
+
id: `${adapter.type}:default`,
|
|
1664
|
+
type: adapter.type,
|
|
1665
|
+
name: adapter.name,
|
|
1666
|
+
version: adapter.version,
|
|
1667
|
+
connected: adapter.isConnected()
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
return {
|
|
1671
|
+
content: [
|
|
1672
|
+
{
|
|
1673
|
+
type: "text",
|
|
1674
|
+
text: JSON.stringify(adapters, null, 2)
|
|
1675
|
+
}
|
|
1676
|
+
]
|
|
1677
|
+
};
|
|
1678
|
+
});
|
|
1679
|
+
registerToolScopes(
|
|
1680
|
+
/* @__PURE__ */ new Map([
|
|
1681
|
+
["server_info", ["read"]],
|
|
1682
|
+
["server_health", ["read"]],
|
|
1683
|
+
["list_adapters", ["read"]]
|
|
1684
|
+
])
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
// src/server/registration/help-resources.ts
|
|
1689
|
+
function registerHelpResources(server, toolFilter) {
|
|
1690
|
+
const gotchasContent = HELP_CONTENT.get("gotchas");
|
|
1691
|
+
if (gotchasContent) {
|
|
1692
|
+
server.registerResource(
|
|
1693
|
+
"sqlite_help",
|
|
1694
|
+
"sqlite://help",
|
|
1695
|
+
{
|
|
1696
|
+
description: "Critical gotchas, WASM vs Native comparison, and Code Mode API reference",
|
|
1697
|
+
mimeType: "text/markdown",
|
|
1698
|
+
annotations: ASSISTANT_FOCUSED
|
|
1699
|
+
},
|
|
1700
|
+
() => ({
|
|
1701
|
+
contents: [
|
|
1702
|
+
{
|
|
1703
|
+
uri: "sqlite://help",
|
|
1704
|
+
mimeType: "text/markdown",
|
|
1705
|
+
text: gotchasContent
|
|
1706
|
+
}
|
|
1707
|
+
]
|
|
1708
|
+
})
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
const groupHelpKeys = [
|
|
1712
|
+
{ group: "core", key: "core" },
|
|
1713
|
+
{ group: "json", key: "json" },
|
|
1714
|
+
{ group: "text", key: "text" },
|
|
1715
|
+
{ group: "stats", key: "stats" },
|
|
1716
|
+
{ group: "vector", key: "vector" },
|
|
1717
|
+
{ group: "geo", key: "geo" },
|
|
1718
|
+
{ group: "admin", key: "admin" },
|
|
1719
|
+
{ group: "transactions", key: "transactions" },
|
|
1720
|
+
{ group: "introspection", key: "introspection" },
|
|
1721
|
+
{ group: "migration", key: "migration" }
|
|
1722
|
+
];
|
|
1723
|
+
for (const { group, key } of groupHelpKeys) {
|
|
1724
|
+
const isCodemodeOnly = toolFilter.enabledGroups.size === 1 && toolFilter.enabledGroups.has("codemode");
|
|
1725
|
+
if (!toolFilter.enabledGroups.has(group) && !isCodemodeOnly) {
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1728
|
+
const content = HELP_CONTENT.get(key);
|
|
1729
|
+
if (!content) continue;
|
|
1730
|
+
server.registerResource(
|
|
1731
|
+
`sqlite_help_${key}`,
|
|
1732
|
+
`sqlite://help/${key}`,
|
|
1733
|
+
{
|
|
1734
|
+
description: `Tool reference for the ${group} tool group`,
|
|
1735
|
+
mimeType: "text/markdown",
|
|
1736
|
+
annotations: ASSISTANT_FOCUSED
|
|
1737
|
+
},
|
|
1738
|
+
() => ({
|
|
1739
|
+
contents: [
|
|
1740
|
+
{
|
|
1741
|
+
uri: `sqlite://help/${key}`,
|
|
1742
|
+
mimeType: "text/markdown",
|
|
1743
|
+
text: content
|
|
1744
|
+
}
|
|
1745
|
+
]
|
|
1746
|
+
})
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
const registeredHelp = ["sqlite://help"];
|
|
1750
|
+
for (const { group, key } of groupHelpKeys) {
|
|
1751
|
+
if (toolFilter.enabledGroups.has(group)) {
|
|
1752
|
+
registeredHelp.push(`sqlite://help/${key}`);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
logger.info(`Help resources: ${registeredHelp.join(", ")}`, {
|
|
1756
|
+
module: "SERVER"
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
function redactSqlLiterals(text) {
|
|
1760
|
+
return text.replace(/'(?:''|[^'])*'/g, "'***'");
|
|
1761
|
+
}
|
|
1762
|
+
function validateDdl(sql) {
|
|
1763
|
+
const cleanSql = sql.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--.*$/gm, "");
|
|
1764
|
+
const upperSql = cleanSql.toUpperCase();
|
|
1765
|
+
if (upperSql.includes("ATTACH ") || upperSql.includes("DETACH ") || upperSql.includes("PRAGMA ") || upperSql.includes("LOAD_EXTENSION(")) {
|
|
1766
|
+
throw new Error(
|
|
1767
|
+
`DDL validation failed: unauthorized command or function call`
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
if (upperSql.includes(" ON MAIN.") || upperSql.includes(" ON TEMP.")) {
|
|
1771
|
+
throw new Error(
|
|
1772
|
+
`DDL validation failed: trigger attempts to target a specific database`
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
function registerAuditResource(server, auditLogger, backupManager) {
|
|
1777
|
+
if (!auditLogger) return;
|
|
1778
|
+
server.registerResource(
|
|
1779
|
+
"sqlite_audit",
|
|
1780
|
+
"sqlite://audit",
|
|
1781
|
+
{
|
|
1782
|
+
description: "Recent audit log entries and backup statistics. Shows the last 50 tool invocations with timing, outcomes, and token estimates.",
|
|
1783
|
+
mimeType: "application/json"
|
|
1784
|
+
},
|
|
1785
|
+
async () => {
|
|
1786
|
+
const recent = await auditLogger.recent(50);
|
|
1787
|
+
const backupStats = backupManager ? await backupManager.getStats() : void 0;
|
|
1788
|
+
const payload = {
|
|
1789
|
+
entries: recent,
|
|
1790
|
+
stats: {
|
|
1791
|
+
totalEntries: recent.length,
|
|
1792
|
+
...backupStats && { backups: backupStats }
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
const payloadStr = JSON.stringify(payload, null, 2);
|
|
1796
|
+
return {
|
|
1797
|
+
contents: [
|
|
1798
|
+
{
|
|
1799
|
+
uri: "sqlite://audit",
|
|
1800
|
+
mimeType: "application/json",
|
|
1801
|
+
text: redactSqlLiterals(payloadStr)
|
|
1802
|
+
}
|
|
1803
|
+
]
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
function registerAuditBackupTools(server, backupManager, adaptersMap) {
|
|
1809
|
+
if (!backupManager) return;
|
|
1810
|
+
server.registerTool(
|
|
1811
|
+
"sqlite_audit_list_backups",
|
|
1812
|
+
{
|
|
1813
|
+
title: "List Audit Backups",
|
|
1814
|
+
description: "List pre-mutation DDL snapshots captured before destructive operations. Returns metadata for each snapshot including timestamp, tool, target, and size.",
|
|
1815
|
+
inputSchema: AuditListBackupsSchema,
|
|
1816
|
+
outputSchema: AuditListBackupsOutputSchema,
|
|
1817
|
+
annotations: {
|
|
1818
|
+
readOnlyHint: true,
|
|
1819
|
+
destructiveHint: false,
|
|
1820
|
+
idempotentHint: true,
|
|
1821
|
+
openWorldHint: false
|
|
1822
|
+
// Requires admin scope
|
|
1823
|
+
}
|
|
1824
|
+
},
|
|
1825
|
+
async (args) => {
|
|
1826
|
+
const authCtx = getAuthContext();
|
|
1827
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "sqlite_audit_list_backups")) {
|
|
1828
|
+
throw new InsufficientScopeError(["admin", "full"], authCtx.scopes);
|
|
1829
|
+
}
|
|
1830
|
+
let parsed;
|
|
1831
|
+
try {
|
|
1832
|
+
parsed = AuditListBackupsSchema.parse(args ?? {});
|
|
1833
|
+
} catch (error) {
|
|
1834
|
+
const structured = formatHandlerError(error);
|
|
1835
|
+
return {
|
|
1836
|
+
content: [
|
|
1837
|
+
{
|
|
1838
|
+
type: "text",
|
|
1839
|
+
text: JSON.stringify(structured, null, 2)
|
|
1840
|
+
}
|
|
1841
|
+
],
|
|
1842
|
+
isError: true,
|
|
1843
|
+
structuredContent: structured
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
const limit = parsed.limit ?? 10;
|
|
1847
|
+
const offset = parsed.offset ?? 0;
|
|
1848
|
+
const { snapshots, total } = await backupManager.listSnapshots(
|
|
1849
|
+
limit,
|
|
1850
|
+
offset
|
|
1851
|
+
);
|
|
1852
|
+
const result = {
|
|
1853
|
+
success: true,
|
|
1854
|
+
snapshots,
|
|
1855
|
+
count: snapshots.length,
|
|
1856
|
+
totalCount: total
|
|
1857
|
+
};
|
|
1858
|
+
const tokenEstimate = Math.ceil(
|
|
1859
|
+
Buffer.byteLength(JSON.stringify(result), "utf8") / 4
|
|
1860
|
+
);
|
|
1861
|
+
return {
|
|
1862
|
+
content: [
|
|
1863
|
+
{
|
|
1864
|
+
type: "text",
|
|
1865
|
+
text: JSON.stringify(
|
|
1866
|
+
{ ...result, _meta: { tokenEstimate } },
|
|
1867
|
+
null,
|
|
1868
|
+
2
|
|
1869
|
+
)
|
|
1870
|
+
}
|
|
1871
|
+
],
|
|
1872
|
+
structuredContent: result
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
);
|
|
1876
|
+
server.registerTool(
|
|
1877
|
+
"sqlite_audit_get_backup",
|
|
1878
|
+
{
|
|
1879
|
+
title: "Get Audit Backup",
|
|
1880
|
+
description: "Retrieve a specific pre-mutation DDL snapshot by filename. Returns the full snapshot content including DDL and optional data.",
|
|
1881
|
+
inputSchema: z.object({
|
|
1882
|
+
filename: z.string().default("").describe("Snapshot filename from sqlite_audit_list_backups results")
|
|
1883
|
+
}),
|
|
1884
|
+
outputSchema: AuditGetBackupOutputSchema,
|
|
1885
|
+
annotations: {
|
|
1886
|
+
readOnlyHint: true,
|
|
1887
|
+
destructiveHint: false,
|
|
1888
|
+
idempotentHint: true,
|
|
1889
|
+
openWorldHint: false
|
|
1890
|
+
// Requires admin scope
|
|
1891
|
+
}
|
|
1892
|
+
},
|
|
1893
|
+
async (args) => {
|
|
1894
|
+
const authCtx = getAuthContext();
|
|
1895
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "sqlite_audit_get_backup")) {
|
|
1896
|
+
throw new InsufficientScopeError(["admin", "full"], authCtx.scopes);
|
|
1897
|
+
}
|
|
1898
|
+
let filename;
|
|
1899
|
+
try {
|
|
1900
|
+
const parsed = z.object({ filename: z.string().min(1, "filename is required") }).parse(args);
|
|
1901
|
+
filename = parsed.filename;
|
|
1902
|
+
} catch (error) {
|
|
1903
|
+
const structured = formatHandlerError(error);
|
|
1904
|
+
return {
|
|
1905
|
+
content: [
|
|
1906
|
+
{
|
|
1907
|
+
type: "text",
|
|
1908
|
+
text: JSON.stringify(structured, null, 2)
|
|
1909
|
+
}
|
|
1910
|
+
],
|
|
1911
|
+
isError: true,
|
|
1912
|
+
structuredContent: structured
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
const snapshot = await backupManager.getSnapshot(filename);
|
|
1916
|
+
if (!snapshot) {
|
|
1917
|
+
const errRes = {
|
|
1918
|
+
success: false,
|
|
1919
|
+
error: `Snapshot not found: ${filename}`
|
|
1920
|
+
};
|
|
1921
|
+
return {
|
|
1922
|
+
content: [
|
|
1923
|
+
{
|
|
1924
|
+
type: "text",
|
|
1925
|
+
text: JSON.stringify(errRes, null, 2)
|
|
1926
|
+
}
|
|
1927
|
+
],
|
|
1928
|
+
isError: true,
|
|
1929
|
+
structuredContent: errRes
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
const result = { success: true, ...snapshot };
|
|
1933
|
+
const snapshotStr = JSON.stringify(result, null, 2);
|
|
1934
|
+
return {
|
|
1935
|
+
content: [
|
|
1936
|
+
{
|
|
1937
|
+
type: "text",
|
|
1938
|
+
text: redactSqlLiterals(snapshotStr)
|
|
1939
|
+
}
|
|
1940
|
+
],
|
|
1941
|
+
structuredContent: result
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
);
|
|
1945
|
+
server.registerTool(
|
|
1946
|
+
"sqlite_audit_cleanup",
|
|
1947
|
+
{
|
|
1948
|
+
title: "Cleanup Audit Backups",
|
|
1949
|
+
description: "Apply retention policy to audit backup snapshots. Deletes snapshots exceeding age or count limits.",
|
|
1950
|
+
outputSchema: AuditCleanupOutputSchema,
|
|
1951
|
+
annotations: {
|
|
1952
|
+
readOnlyHint: false,
|
|
1953
|
+
destructiveHint: true,
|
|
1954
|
+
idempotentHint: true,
|
|
1955
|
+
openWorldHint: false
|
|
1956
|
+
}
|
|
1957
|
+
},
|
|
1958
|
+
async () => {
|
|
1959
|
+
const authCtx = getAuthContext();
|
|
1960
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "sqlite_audit_cleanup")) {
|
|
1961
|
+
throw new InsufficientScopeError(["admin", "full"], authCtx.scopes);
|
|
1962
|
+
}
|
|
1963
|
+
const deleted = await backupManager.cleanup();
|
|
1964
|
+
const result = {
|
|
1965
|
+
success: true,
|
|
1966
|
+
deletedCount: deleted,
|
|
1967
|
+
message: deleted > 0 ? `Cleaned up ${String(deleted)} snapshot(s)` : "No snapshots to clean up"
|
|
1968
|
+
};
|
|
1969
|
+
return {
|
|
1970
|
+
content: [
|
|
1971
|
+
{
|
|
1972
|
+
type: "text",
|
|
1973
|
+
text: JSON.stringify(result, null, 2)
|
|
1974
|
+
}
|
|
1975
|
+
],
|
|
1976
|
+
structuredContent: result
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
);
|
|
1980
|
+
server.registerTool(
|
|
1981
|
+
"sqlite_audit_diff_backup",
|
|
1982
|
+
{
|
|
1983
|
+
title: "Diff Audit Backup",
|
|
1984
|
+
description: "Compare a pre-mutation DDL snapshot against the live database schema. Shows objects that have been added, removed, or modified since the snapshot was taken.",
|
|
1985
|
+
inputSchema: z.object({
|
|
1986
|
+
filename: z.string().default("").describe(
|
|
1987
|
+
"Snapshot filename to compare against the live database schema"
|
|
1988
|
+
)
|
|
1989
|
+
}),
|
|
1990
|
+
outputSchema: AuditDiffBackupOutputSchema,
|
|
1991
|
+
annotations: {
|
|
1992
|
+
readOnlyHint: true,
|
|
1993
|
+
destructiveHint: false,
|
|
1994
|
+
idempotentHint: true,
|
|
1995
|
+
openWorldHint: false
|
|
1996
|
+
// Requires admin scope
|
|
1997
|
+
}
|
|
1998
|
+
},
|
|
1999
|
+
async (args) => {
|
|
2000
|
+
const authCtx = getAuthContext();
|
|
2001
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "sqlite_audit_diff_backup")) {
|
|
2002
|
+
throw new InsufficientScopeError(["admin", "full"], authCtx.scopes);
|
|
2003
|
+
}
|
|
2004
|
+
let filename;
|
|
2005
|
+
try {
|
|
2006
|
+
const parsed = z.object({ filename: z.string().min(1, "filename is required") }).parse(args);
|
|
2007
|
+
filename = parsed.filename;
|
|
2008
|
+
} catch (error) {
|
|
2009
|
+
const structured = formatHandlerError(error);
|
|
2010
|
+
return {
|
|
2011
|
+
content: [
|
|
2012
|
+
{
|
|
2013
|
+
type: "text",
|
|
2014
|
+
text: JSON.stringify(structured, null, 2)
|
|
2015
|
+
}
|
|
2016
|
+
],
|
|
2017
|
+
isError: true,
|
|
2018
|
+
structuredContent: structured
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
const snapshot = await backupManager.getSnapshot(filename);
|
|
2022
|
+
if (!snapshot) {
|
|
2023
|
+
const errRes = {
|
|
2024
|
+
success: false,
|
|
2025
|
+
error: `Snapshot not found: ${filename}`
|
|
2026
|
+
};
|
|
2027
|
+
return {
|
|
2028
|
+
content: [
|
|
2029
|
+
{
|
|
2030
|
+
type: "text",
|
|
2031
|
+
text: JSON.stringify(errRes, null, 2)
|
|
2032
|
+
}
|
|
2033
|
+
],
|
|
2034
|
+
isError: true,
|
|
2035
|
+
structuredContent: errRes
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
const adapter = [...adaptersMap.values()][0];
|
|
2039
|
+
if (!adapter) {
|
|
2040
|
+
const errRes = {
|
|
2041
|
+
success: false,
|
|
2042
|
+
error: "No connected adapter available"
|
|
2043
|
+
};
|
|
2044
|
+
return {
|
|
2045
|
+
content: [
|
|
2046
|
+
{
|
|
2047
|
+
type: "text",
|
|
2048
|
+
text: JSON.stringify(errRes, null, 2)
|
|
2049
|
+
}
|
|
2050
|
+
],
|
|
2051
|
+
isError: true,
|
|
2052
|
+
structuredContent: errRes
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
try {
|
|
2056
|
+
const liveResult = await adapter.executeReadQuery(
|
|
2057
|
+
"SELECT name, type, sql FROM sqlite_master WHERE sql IS NOT NULL ORDER BY type, name"
|
|
2058
|
+
);
|
|
2059
|
+
const liveObjects = /* @__PURE__ */ new Map();
|
|
2060
|
+
for (const row of liveResult.rows ?? []) {
|
|
2061
|
+
liveObjects.set(row["name"], {
|
|
2062
|
+
type: row["type"],
|
|
2063
|
+
sql: row["sql"]
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
const snapshotDdl = snapshot.ddl ?? "";
|
|
2067
|
+
const snapshotObjects = /* @__PURE__ */ new Map();
|
|
2068
|
+
const statements = snapshotDdl.split(";").map((s) => s.trim()).filter(Boolean);
|
|
2069
|
+
for (const stmt of statements) {
|
|
2070
|
+
const match = /CREATE\s+(?:VIRTUAL\s+)?(?:TEMP(?:ORARY)?\s+)?(TABLE|INDEX|VIEW|TRIGGER)\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"([^"]+)"|(\S+))/i.exec(
|
|
2071
|
+
stmt
|
|
2072
|
+
);
|
|
2073
|
+
if (match) {
|
|
2074
|
+
const type = (match[1] ?? "").toLowerCase();
|
|
2075
|
+
const name = match[2] ?? match[3] ?? "";
|
|
2076
|
+
snapshotObjects.set(name, { type, sql: stmt });
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
const diffs = [];
|
|
2080
|
+
const snapshotTarget = snapshot.metadata?.target;
|
|
2081
|
+
for (const [name, live] of liveObjects) {
|
|
2082
|
+
if (name === snapshotTarget || snapshotObjects.has(name)) {
|
|
2083
|
+
if (!snapshotObjects.has(name)) {
|
|
2084
|
+
diffs.push({
|
|
2085
|
+
object: name,
|
|
2086
|
+
type: live.type,
|
|
2087
|
+
status: "added",
|
|
2088
|
+
liveDefinition: live.sql
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
for (const [name, snap] of snapshotObjects) {
|
|
2094
|
+
if (!liveObjects.has(name)) {
|
|
2095
|
+
diffs.push({
|
|
2096
|
+
object: name,
|
|
2097
|
+
type: snap.type,
|
|
2098
|
+
status: "removed",
|
|
2099
|
+
snapshotDefinition: snap.sql
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
for (const [name, snap] of snapshotObjects) {
|
|
2104
|
+
const live = liveObjects.get(name);
|
|
2105
|
+
if (live && live.sql !== snap.sql) {
|
|
2106
|
+
diffs.push({
|
|
2107
|
+
object: name,
|
|
2108
|
+
type: live.type,
|
|
2109
|
+
status: "modified",
|
|
2110
|
+
liveDefinition: live.sql,
|
|
2111
|
+
snapshotDefinition: snap.sql
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
const result = {
|
|
2116
|
+
success: true,
|
|
2117
|
+
diffs,
|
|
2118
|
+
snapshotTimestamp: snapshot.metadata?.timestamp ?? "",
|
|
2119
|
+
snapshotTarget: snapshot.metadata?.target ?? ""
|
|
2120
|
+
};
|
|
2121
|
+
return {
|
|
2122
|
+
content: [
|
|
2123
|
+
{
|
|
2124
|
+
type: "text",
|
|
2125
|
+
text: JSON.stringify(result, null, 2)
|
|
2126
|
+
}
|
|
2127
|
+
],
|
|
2128
|
+
structuredContent: result
|
|
2129
|
+
};
|
|
2130
|
+
} catch (error) {
|
|
2131
|
+
const errRes = {
|
|
2132
|
+
success: false,
|
|
2133
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2134
|
+
};
|
|
2135
|
+
return {
|
|
2136
|
+
content: [
|
|
2137
|
+
{
|
|
2138
|
+
type: "text",
|
|
2139
|
+
text: JSON.stringify(errRes, null, 2)
|
|
2140
|
+
}
|
|
2141
|
+
],
|
|
2142
|
+
isError: true,
|
|
2143
|
+
structuredContent: errRes
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
);
|
|
2148
|
+
server.registerTool(
|
|
2149
|
+
"sqlite_audit_restore_backup",
|
|
2150
|
+
{
|
|
2151
|
+
title: "Restore Audit Backup",
|
|
2152
|
+
description: "Restore a pre-mutation DDL snapshot by executing its DDL statements against the live database. Use dryRun=true to preview the DDL without applying changes.",
|
|
2153
|
+
inputSchema: z.object({
|
|
2154
|
+
filename: z.string().default("").describe("Snapshot filename to restore from"),
|
|
2155
|
+
dryRun: z.unknown().optional().describe("If true, returns the DDL without executing it (boolean)")
|
|
2156
|
+
}),
|
|
2157
|
+
outputSchema: AuditRestoreBackupOutputSchema,
|
|
2158
|
+
annotations: {
|
|
2159
|
+
readOnlyHint: false,
|
|
2160
|
+
destructiveHint: true,
|
|
2161
|
+
idempotentHint: false,
|
|
2162
|
+
openWorldHint: false
|
|
2163
|
+
}
|
|
2164
|
+
},
|
|
2165
|
+
async (args) => {
|
|
2166
|
+
const authCtx = getAuthContext();
|
|
2167
|
+
if (authCtx && !scopesGrantToolAccess(authCtx.scopes, "sqlite_audit_restore_backup")) {
|
|
2168
|
+
throw new InsufficientScopeError(["admin", "full"], authCtx.scopes);
|
|
2169
|
+
}
|
|
2170
|
+
let filename;
|
|
2171
|
+
let dryRun;
|
|
2172
|
+
try {
|
|
2173
|
+
const parsed = z.object({
|
|
2174
|
+
filename: z.string().min(1, "filename is required"),
|
|
2175
|
+
dryRun: z.boolean().optional().default(false)
|
|
2176
|
+
}).parse(args);
|
|
2177
|
+
filename = parsed.filename;
|
|
2178
|
+
dryRun = parsed.dryRun;
|
|
2179
|
+
} catch (error) {
|
|
2180
|
+
const structured = formatHandlerError(error);
|
|
2181
|
+
return {
|
|
2182
|
+
content: [
|
|
2183
|
+
{
|
|
2184
|
+
type: "text",
|
|
2185
|
+
text: JSON.stringify(structured, null, 2)
|
|
2186
|
+
}
|
|
2187
|
+
],
|
|
2188
|
+
isError: true,
|
|
2189
|
+
structuredContent: structured
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
const snapshot = await backupManager.getSnapshot(filename);
|
|
2193
|
+
if (!snapshot) {
|
|
2194
|
+
const errRes = {
|
|
2195
|
+
success: false,
|
|
2196
|
+
error: `Snapshot not found: ${filename}`
|
|
2197
|
+
};
|
|
2198
|
+
return {
|
|
2199
|
+
content: [
|
|
2200
|
+
{
|
|
2201
|
+
type: "text",
|
|
2202
|
+
text: JSON.stringify(errRes, null, 2)
|
|
2203
|
+
}
|
|
2204
|
+
],
|
|
2205
|
+
isError: true,
|
|
2206
|
+
structuredContent: errRes
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
const ddl = snapshot.ddl ?? "";
|
|
2210
|
+
if (!ddl.trim()) {
|
|
2211
|
+
const errRes = {
|
|
2212
|
+
success: false,
|
|
2213
|
+
error: "Snapshot contains no DDL statements"
|
|
2214
|
+
};
|
|
2215
|
+
return {
|
|
2216
|
+
content: [
|
|
2217
|
+
{
|
|
2218
|
+
type: "text",
|
|
2219
|
+
text: JSON.stringify(errRes, null, 2)
|
|
2220
|
+
}
|
|
2221
|
+
],
|
|
2222
|
+
isError: true,
|
|
2223
|
+
structuredContent: errRes
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
const adapter = [...adaptersMap.values()][0];
|
|
2227
|
+
if (!adapter) {
|
|
2228
|
+
const errRes = {
|
|
2229
|
+
success: false,
|
|
2230
|
+
error: "No connected adapter available"
|
|
2231
|
+
};
|
|
2232
|
+
return {
|
|
2233
|
+
content: [
|
|
2234
|
+
{
|
|
2235
|
+
type: "text",
|
|
2236
|
+
text: JSON.stringify(errRes, null, 2)
|
|
2237
|
+
}
|
|
2238
|
+
],
|
|
2239
|
+
isError: true,
|
|
2240
|
+
structuredContent: errRes
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
if (dryRun) {
|
|
2244
|
+
const result = {
|
|
2245
|
+
success: true,
|
|
2246
|
+
message: "Dry run: no changes applied",
|
|
2247
|
+
dryRun: true,
|
|
2248
|
+
ddl: redactSqlLiterals(ddl)
|
|
2249
|
+
};
|
|
2250
|
+
return {
|
|
2251
|
+
content: [
|
|
2252
|
+
{
|
|
2253
|
+
type: "text",
|
|
2254
|
+
text: JSON.stringify(result, null, 2)
|
|
2255
|
+
}
|
|
2256
|
+
],
|
|
2257
|
+
structuredContent: result
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
try {
|
|
2261
|
+
const statements = ddl.split(";").map((s) => s.trim()).filter((s) => {
|
|
2262
|
+
const cleanSql = s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--.*$/gm, "").trim();
|
|
2263
|
+
return cleanSql.length > 0;
|
|
2264
|
+
});
|
|
2265
|
+
if (statements.length === 0) {
|
|
2266
|
+
const result2 = {
|
|
2267
|
+
success: true,
|
|
2268
|
+
message: "No executable statements found in snapshot (only comments)",
|
|
2269
|
+
changesApplied: 0
|
|
2270
|
+
};
|
|
2271
|
+
return {
|
|
2272
|
+
content: [
|
|
2273
|
+
{
|
|
2274
|
+
type: "text",
|
|
2275
|
+
text: JSON.stringify(result2, null, 2)
|
|
2276
|
+
}
|
|
2277
|
+
],
|
|
2278
|
+
structuredContent: result2
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
await adapter.executeQuery("BEGIN TRANSACTION;");
|
|
2282
|
+
try {
|
|
2283
|
+
for (const stmt of statements) {
|
|
2284
|
+
validateDdl(stmt);
|
|
2285
|
+
await adapter.executeQuery(`${stmt};`);
|
|
2286
|
+
}
|
|
2287
|
+
await adapter.executeQuery("COMMIT;");
|
|
2288
|
+
} catch (e) {
|
|
2289
|
+
await adapter.executeQuery("ROLLBACK;");
|
|
2290
|
+
throw e;
|
|
2291
|
+
}
|
|
2292
|
+
const result = {
|
|
2293
|
+
success: true,
|
|
2294
|
+
message: `Successfully executed ${statements.length} statement(s) from snapshot`,
|
|
2295
|
+
changesApplied: statements.length
|
|
2296
|
+
};
|
|
2297
|
+
return {
|
|
2298
|
+
content: [
|
|
2299
|
+
{
|
|
2300
|
+
type: "text",
|
|
2301
|
+
text: JSON.stringify(result, null, 2)
|
|
2302
|
+
}
|
|
2303
|
+
],
|
|
2304
|
+
structuredContent: result
|
|
2305
|
+
};
|
|
2306
|
+
} catch (error) {
|
|
2307
|
+
const errRes = {
|
|
2308
|
+
success: false,
|
|
2309
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2310
|
+
};
|
|
2311
|
+
return {
|
|
2312
|
+
content: [
|
|
2313
|
+
{
|
|
2314
|
+
type: "text",
|
|
2315
|
+
text: JSON.stringify(errRes, null, 2)
|
|
2316
|
+
}
|
|
2317
|
+
],
|
|
2318
|
+
isError: true,
|
|
2319
|
+
structuredContent: errRes
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
);
|
|
2324
|
+
registerToolScopes(
|
|
2325
|
+
/* @__PURE__ */ new Map([
|
|
2326
|
+
["sqlite_audit_list_backups", ["admin", "full"]],
|
|
2327
|
+
["sqlite_audit_get_backup", ["admin", "full"]],
|
|
2328
|
+
["sqlite_audit_cleanup", ["admin", "full"]],
|
|
2329
|
+
["sqlite_audit_diff_backup", ["admin", "full"]],
|
|
2330
|
+
["sqlite_audit_restore_backup", ["admin", "full"]]
|
|
2331
|
+
])
|
|
2332
|
+
);
|
|
2333
|
+
logger.info(
|
|
2334
|
+
"Registered audit backup tools: sqlite_audit_list_backups, sqlite_audit_get_backup, sqlite_audit_cleanup, sqlite_audit_diff_backup, sqlite_audit_restore_backup",
|
|
2335
|
+
{ module: "AUDIT" }
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// src/server/mcp-server.ts
|
|
2340
|
+
var proto = McpServer.prototype;
|
|
2341
|
+
if (typeof proto["createToolError"] === "function") {
|
|
2342
|
+
const originalCreateToolError = proto["createToolError"];
|
|
2343
|
+
proto["createToolError"] = function(errorMessage) {
|
|
2344
|
+
const result = originalCreateToolError.call(
|
|
2345
|
+
this,
|
|
2346
|
+
errorMessage
|
|
2347
|
+
);
|
|
2348
|
+
if (result.content?.[0]?.type === "text") {
|
|
2349
|
+
const rawError = result.content[0].text;
|
|
2350
|
+
if (rawError.includes("Input validation error")) {
|
|
2351
|
+
const cleanError = rawError.replace(
|
|
2352
|
+
/^MCP error -32602: Input validation error: /,
|
|
2353
|
+
"Validation error: "
|
|
2354
|
+
);
|
|
2355
|
+
const structured = {
|
|
2356
|
+
success: false,
|
|
2357
|
+
error: cleanError,
|
|
2358
|
+
code: "VALIDATION_ERROR",
|
|
2359
|
+
category: "validation" /* VALIDATION */
|
|
2360
|
+
};
|
|
2361
|
+
result.content[0].text = JSON.stringify(structured, null, 2);
|
|
2362
|
+
result.isError = true;
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
return result;
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
var DbMcpServer = class {
|
|
2369
|
+
server;
|
|
2370
|
+
adapters = /* @__PURE__ */ new Map();
|
|
2371
|
+
toolFilter;
|
|
2372
|
+
config;
|
|
2373
|
+
auditLogger = null;
|
|
2374
|
+
backupManager = null;
|
|
2375
|
+
auditInterceptor = null;
|
|
2376
|
+
auditInitPromise = null;
|
|
2377
|
+
constructor(config) {
|
|
2378
|
+
this.config = config;
|
|
2379
|
+
this.toolFilter = config.toolFilter ? parseToolFilter(config.toolFilter) : getToolFilterFromEnv();
|
|
2380
|
+
this.server = new McpServer(
|
|
2381
|
+
{
|
|
2382
|
+
name: config.name,
|
|
2383
|
+
version: config.version
|
|
2384
|
+
},
|
|
2385
|
+
{
|
|
2386
|
+
capabilities: {
|
|
2387
|
+
logging: {}
|
|
2388
|
+
},
|
|
2389
|
+
instructions: INSTRUCTIONS
|
|
2390
|
+
}
|
|
2391
|
+
);
|
|
2392
|
+
logger.info(getFilterSummary(this.toolFilter), { module: "FILTER" });
|
|
2393
|
+
logger.info("MCP Server initialized", {
|
|
2394
|
+
module: "SERVER",
|
|
2395
|
+
name: config.name,
|
|
2396
|
+
version: config.version,
|
|
2397
|
+
toolFilter: config.toolFilter ?? "none",
|
|
2398
|
+
capabilities: ["logging"]
|
|
2399
|
+
});
|
|
2400
|
+
registerBuiltInTools(
|
|
2401
|
+
this.server,
|
|
2402
|
+
this.adapters,
|
|
2403
|
+
this.config,
|
|
2404
|
+
this.toolFilter
|
|
2405
|
+
);
|
|
2406
|
+
registerHelpResources(this.server, this.toolFilter);
|
|
2407
|
+
const internalMcp = this.server;
|
|
2408
|
+
const handlers = internalMcp.server._requestHandlers;
|
|
2409
|
+
if (!handlers?.has("tools/list")) {
|
|
2410
|
+
throw new DbMcpError(
|
|
2411
|
+
"Security: SDK _requestHandlers monkey-patch failed. Scope filtering is disabled.",
|
|
2412
|
+
"SERVER_START_FAILED",
|
|
2413
|
+
"internal" /* INTERNAL */
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
const originalListToolsHandler = handlers.get("tools/list");
|
|
2417
|
+
if (originalListToolsHandler) {
|
|
2418
|
+
handlers.set("tools/list", async (request, extra) => {
|
|
2419
|
+
const result = await originalListToolsHandler(request, extra);
|
|
2420
|
+
const authCtx = getAuthContext();
|
|
2421
|
+
if (authCtx && Array.isArray(result.tools)) {
|
|
2422
|
+
result.tools = result.tools.filter(
|
|
2423
|
+
(t) => scopesGrantToolAccess(authCtx.scopes, t.name)
|
|
2424
|
+
);
|
|
2425
|
+
}
|
|
2426
|
+
return result;
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
if (config.audit?.enabled) {
|
|
2430
|
+
this.auditInitPromise = this.initializeAudit(config);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Register a database adapter
|
|
2435
|
+
*/
|
|
2436
|
+
async registerAdapter(adapter, config) {
|
|
2437
|
+
const adapterId = `${adapter.type}:${config.connectionString ?? "default"}`;
|
|
2438
|
+
if (this.auditInitPromise) {
|
|
2439
|
+
await this.auditInitPromise;
|
|
2440
|
+
}
|
|
2441
|
+
await adapter.connect(config);
|
|
2442
|
+
this.adapters.set(adapterId, adapter);
|
|
2443
|
+
if (this.auditInterceptor && "setAuditInterceptor" in adapter) {
|
|
2444
|
+
adapter.setAuditInterceptor(this.auditInterceptor);
|
|
2445
|
+
this.auditInterceptor.setQueryAdapter({
|
|
2446
|
+
executeQuery: (sql, params) => adapter.executeReadQuery(sql, params)
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
if (this.backupManager && "setBackupManager" in adapter) {
|
|
2450
|
+
adapter.setBackupManager(this.backupManager);
|
|
2451
|
+
}
|
|
2452
|
+
adapter.registerTools(this.server, this.toolFilter);
|
|
2453
|
+
adapter.registerResources(this.server);
|
|
2454
|
+
adapter.registerPrompts(this.server);
|
|
2455
|
+
const toolDefs = adapter.getToolDefinitions();
|
|
2456
|
+
const scopesMap = /* @__PURE__ */ new Map();
|
|
2457
|
+
for (const tool of toolDefs) {
|
|
2458
|
+
if (tool.requiredScopes) {
|
|
2459
|
+
scopesMap.set(tool.name, tool.requiredScopes);
|
|
2460
|
+
scopesMap.set(`sqlite_${tool.name}`, tool.requiredScopes);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
registerToolScopes(scopesMap);
|
|
2464
|
+
logger.info(`Registered adapter: ${adapter.name} (${adapterId})`, {
|
|
2465
|
+
module: "SERVER"
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Get an adapter by ID
|
|
2470
|
+
*/
|
|
2471
|
+
getAdapter(adapterId) {
|
|
2472
|
+
return this.adapters.get(adapterId);
|
|
2473
|
+
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Get all registered adapters
|
|
2476
|
+
*/
|
|
2477
|
+
getAdapters() {
|
|
2478
|
+
return this.adapters;
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Start the server with the configured transport
|
|
2482
|
+
*/
|
|
2483
|
+
async start() {
|
|
2484
|
+
switch (this.config.transport) {
|
|
2485
|
+
case "stdio":
|
|
2486
|
+
await this.startStdio();
|
|
2487
|
+
break;
|
|
2488
|
+
case "http":
|
|
2489
|
+
await this.startHttp();
|
|
2490
|
+
break;
|
|
2491
|
+
default:
|
|
2492
|
+
throw new DbMcpError(
|
|
2493
|
+
`Unsupported transport: ${this.config.transport}`,
|
|
2494
|
+
"SERVER_START_FAILED",
|
|
2495
|
+
"config" /* CONFIGURATION */
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Start server with stdio transport
|
|
2501
|
+
*/
|
|
2502
|
+
async startStdio() {
|
|
2503
|
+
const transport = new StdioServerTransport();
|
|
2504
|
+
await this.server.connect(transport);
|
|
2505
|
+
logger.info(`db-mcp server started (stdio transport)`, {
|
|
2506
|
+
module: "SERVER"
|
|
2507
|
+
});
|
|
2508
|
+
logger.warning(
|
|
2509
|
+
"SECURITY WARNING: Running in STDIO mode grants blanket 'admin' access to all tools. This implies full trust in the local client/agent. If this server is exposed over SSH or used by an untrusted client, consider using HTTP transport with OAuth (--oauth-enabled) for granular scope enforcement.",
|
|
2510
|
+
{ module: "SERVER", code: "STDIO_ADMIN_RISK" }
|
|
2511
|
+
);
|
|
2512
|
+
}
|
|
2513
|
+
/**
|
|
2514
|
+
* Start server with HTTP transport (Streamable HTTP with SSE support)
|
|
2515
|
+
*/
|
|
2516
|
+
async startHttp() {
|
|
2517
|
+
const { HttpTransport } = await import('./http-HWTUVFIA.js');
|
|
2518
|
+
const oauthConfig = {
|
|
2519
|
+
enabled: this.config.oauth?.enabled ?? false,
|
|
2520
|
+
authorizationServerUrl: this.config.oauth?.authorizationServerUrl ?? "",
|
|
2521
|
+
audience: this.config.oauth?.audience ?? this.config.name
|
|
2522
|
+
};
|
|
2523
|
+
if (this.config.oauth?.issuer !== void 0) {
|
|
2524
|
+
oauthConfig.issuer = this.config.oauth.issuer;
|
|
2525
|
+
}
|
|
2526
|
+
if (this.config.oauth?.jwksUri !== void 0) {
|
|
2527
|
+
oauthConfig.jwksUri = this.config.oauth.jwksUri;
|
|
2528
|
+
}
|
|
2529
|
+
if (this.config.oauth?.clockTolerance !== void 0) {
|
|
2530
|
+
oauthConfig.clockTolerance = this.config.oauth.clockTolerance;
|
|
2531
|
+
}
|
|
2532
|
+
if (this.config.oauth?.publicPaths !== void 0) {
|
|
2533
|
+
oauthConfig.publicPaths = this.config.oauth.publicPaths;
|
|
2534
|
+
}
|
|
2535
|
+
const transport = new HttpTransport({
|
|
2536
|
+
port: this.config.port ?? 3e3,
|
|
2537
|
+
...this.config.host !== void 0 && { host: this.config.host },
|
|
2538
|
+
...this.config.authToken !== void 0 && {
|
|
2539
|
+
authToken: this.config.authToken
|
|
2540
|
+
},
|
|
2541
|
+
...this.config.enableHSTS !== void 0 && {
|
|
2542
|
+
enableHSTS: this.config.enableHSTS
|
|
2543
|
+
},
|
|
2544
|
+
oauth: oauthConfig,
|
|
2545
|
+
stateless: this.config.statelessHttp ?? false,
|
|
2546
|
+
noAuthEnforcement: this.config.noAuthEnforcement ?? false
|
|
2547
|
+
});
|
|
2548
|
+
await transport.initialize(this.server);
|
|
2549
|
+
await transport.start();
|
|
2550
|
+
const mode = this.config.statelessHttp ? "stateless" : "stateful";
|
|
2551
|
+
const host = this.config.host ?? "127.0.0.1";
|
|
2552
|
+
const port = String(this.config.port ?? 3e3);
|
|
2553
|
+
logger.info(
|
|
2554
|
+
`db-mcp server started (HTTP transport on ${host}:${port}, ${mode} mode)`,
|
|
2555
|
+
{ module: "TRANSPORT", mode }
|
|
2556
|
+
);
|
|
2557
|
+
}
|
|
2558
|
+
/**
|
|
2559
|
+
* Gracefully shut down the server
|
|
2560
|
+
*/
|
|
2561
|
+
async shutdown() {
|
|
2562
|
+
logger.info("Shutting down db-mcp server...", { module: "SERVER" });
|
|
2563
|
+
for (const [id, adapter] of this.adapters) {
|
|
2564
|
+
try {
|
|
2565
|
+
await adapter.disconnect();
|
|
2566
|
+
logger.info(`Disconnected adapter: ${id}`, { module: "SERVER" });
|
|
2567
|
+
} catch (error) {
|
|
2568
|
+
logger.error(`Error disconnecting adapter ${id}`, {
|
|
2569
|
+
module: "SERVER",
|
|
2570
|
+
error: error instanceof Error ? error : void 0
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
if (this.auditLogger) {
|
|
2575
|
+
await this.auditLogger.close();
|
|
2576
|
+
logger.info("Audit logger closed", { module: "AUDIT" });
|
|
2577
|
+
}
|
|
2578
|
+
if (this.backupManager) {
|
|
2579
|
+
await this.backupManager.flush();
|
|
2580
|
+
logger.info("Backup manager flushed", { module: "AUDIT" });
|
|
2581
|
+
}
|
|
2582
|
+
await this.server.close();
|
|
2583
|
+
logger.info("Server shutdown complete", { module: "SERVER" });
|
|
2584
|
+
}
|
|
2585
|
+
/**
|
|
2586
|
+
* Initialize the audit subsystem: logger, interceptor, backup manager,
|
|
2587
|
+
* sqlite://audit resource, and audit backup tools.
|
|
2588
|
+
*/
|
|
2589
|
+
async initializeAudit(config) {
|
|
2590
|
+
const auditConfig = config.audit;
|
|
2591
|
+
if (!auditConfig?.enabled) return;
|
|
2592
|
+
this.auditLogger = new AuditLogger(auditConfig);
|
|
2593
|
+
await this.auditLogger.init();
|
|
2594
|
+
if (auditConfig.backup?.enabled) {
|
|
2595
|
+
this.backupManager = new BackupManager(
|
|
2596
|
+
auditConfig.backup,
|
|
2597
|
+
auditConfig.logPath
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2600
|
+
this.auditInterceptor = createAuditInterceptor(
|
|
2601
|
+
this.auditLogger,
|
|
2602
|
+
this.backupManager ?? void 0
|
|
2603
|
+
);
|
|
2604
|
+
registerAuditResource(this.server, this.auditLogger, this.backupManager);
|
|
2605
|
+
if (this.backupManager && this.toolFilter.enabledGroups.has("admin")) {
|
|
2606
|
+
registerAuditBackupTools(this.server, this.backupManager, this.adapters);
|
|
2607
|
+
}
|
|
2608
|
+
logger.info(
|
|
2609
|
+
`Audit logging enabled (${auditConfig.logPath}, redact=${String(auditConfig.redact)}, reads=${String(auditConfig.auditReads)}, backup=${String(!!auditConfig.backup?.enabled)})`,
|
|
2610
|
+
{ module: "AUDIT" }
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
};
|
|
2614
|
+
function createServer(config) {
|
|
2615
|
+
return new DbMcpServer(config);
|
|
2616
|
+
}
|
|
2617
|
+
var DEFAULT_CONFIG = {
|
|
2618
|
+
name: NAME,
|
|
2619
|
+
version: VERSION,
|
|
2620
|
+
transport: "stdio",
|
|
2621
|
+
databases: []
|
|
2622
|
+
};
|
|
2623
|
+
|
|
2624
|
+
export { DEFAULT_CONFIG, DbMcpServer, createServer };
|