db-read 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.db-read.example.yml +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +81 -0
  4. package/dist/adapters/base.d.ts +10 -0
  5. package/dist/adapters/base.js +2 -0
  6. package/dist/adapters/base.js.map +1 -0
  7. package/dist/adapters/createAdapter.d.ts +3 -0
  8. package/dist/adapters/createAdapter.js +14 -0
  9. package/dist/adapters/createAdapter.js.map +1 -0
  10. package/dist/adapters/mongoAdapter.d.ts +12 -0
  11. package/dist/adapters/mongoAdapter.js +79 -0
  12. package/dist/adapters/mongoAdapter.js.map +1 -0
  13. package/dist/adapters/mysqlAdapter.d.ts +11 -0
  14. package/dist/adapters/mysqlAdapter.js +68 -0
  15. package/dist/adapters/mysqlAdapter.js.map +1 -0
  16. package/dist/adapters/postgresAdapter.d.ts +11 -0
  17. package/dist/adapters/postgresAdapter.js +64 -0
  18. package/dist/adapters/postgresAdapter.js.map +1 -0
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.js +81 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/config/loadConfig.d.ts +20 -0
  23. package/dist/config/loadConfig.js +158 -0
  24. package/dist/config/loadConfig.js.map +1 -0
  25. package/dist/lib/errors.d.ts +9 -0
  26. package/dist/lib/errors.js +19 -0
  27. package/dist/lib/errors.js.map +1 -0
  28. package/dist/lib/mongoGuard.d.ts +2 -0
  29. package/dist/lib/mongoGuard.js +20 -0
  30. package/dist/lib/mongoGuard.js.map +1 -0
  31. package/dist/lib/sqlGuard.d.ts +1 -0
  32. package/dist/lib/sqlGuard.js +41 -0
  33. package/dist/lib/sqlGuard.js.map +1 -0
  34. package/dist/lib/types.d.ts +64 -0
  35. package/dist/lib/types.js +2 -0
  36. package/dist/lib/types.js.map +1 -0
  37. package/dist/mcp/server.d.ts +2 -0
  38. package/dist/mcp/server.js +172 -0
  39. package/dist/mcp/server.js.map +1 -0
  40. package/package.json +46 -0
@@ -0,0 +1,28 @@
1
+ version: 1
2
+ defaultEnvironment: dev
3
+ defaults:
4
+ timeoutMs: 10000
5
+ maxRows: 1000
6
+ maxDocuments: 1000
7
+ environments:
8
+ dev:
9
+ connections:
10
+ local_pg:
11
+ kind: postgres
12
+ uri: postgresql://readonly:readonly@localhost:5432/app
13
+ hosted_mysql:
14
+ kind: mysql
15
+ host: mysql.example.com
16
+ port: 3306
17
+ user: readonly
18
+ password: change-me
19
+ database: billing
20
+ ssl: true
21
+ local_mongo:
22
+ kind: mongodb
23
+ uri: mongodb://readonly:readonly@localhost:27017
24
+ database: analytics
25
+ staging:
26
+ connections: {}
27
+ prod:
28
+ connections: {}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # db-read
2
+
3
+ `db-read` is a read-only MCP server for coding agents that need safe access to PostgreSQL, MySQL, and MongoDB. It supports both hosted and local databases through a git-ignored `.db-read.yml`.
4
+
5
+ ## Features
6
+
7
+ - One MCP server for PostgreSQL, MySQL, and MongoDB
8
+ - Environment-based config: `dev`, `staging`, `prod`
9
+ - Local and hosted connection support via `uri` or explicit host settings
10
+ - Read-only guards for SQL and MongoDB aggregation
11
+ - MCP tools for listing connections, describing them, inspecting schema, and running read queries
12
+
13
+ ## Config
14
+
15
+ Copy `.db-read.example.yml` to `.db-read.yml` and fill in your credentials.
16
+
17
+ ```yaml
18
+ version: 1
19
+ defaultEnvironment: dev
20
+ defaults:
21
+ timeoutMs: 10000
22
+ maxRows: 1000
23
+ maxDocuments: 1000
24
+ environments:
25
+ dev:
26
+ connections:
27
+ app_pg:
28
+ kind: postgres
29
+ uri: postgresql://readonly:secret@localhost:5432/app
30
+ billing_mysql:
31
+ kind: mysql
32
+ host: db.example.com
33
+ port: 3306
34
+ user: readonly
35
+ password: secret
36
+ database: billing
37
+ ssl: true
38
+ analytics_mongo:
39
+ kind: mongodb
40
+ uri: mongodb+srv://readonly:secret@cluster.example.mongodb.net
41
+ database: analytics
42
+ ```
43
+
44
+ ## CLI
45
+
46
+ ```bash
47
+ npm run build
48
+ node dist/cli.js validate-config --config .db-read.yml
49
+ node dist/cli.js serve --env dev --config .db-read.yml
50
+ ```
51
+
52
+ ## MCP client examples
53
+
54
+ Codex-style stdio config:
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "db-read": {
60
+ "command": "node",
61
+ "args": [
62
+ "/absolute/path/to/db-read/dist/cli.js",
63
+ "serve",
64
+ "--env",
65
+ "dev",
66
+ "--config",
67
+ "/absolute/path/to/project/.db-read.yml"
68
+ ]
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ Cursor and Claude Code can use the same `stdio` command/args pattern.
75
+
76
+ ## Safety model
77
+
78
+ - Use least-privileged read-only DB credentials.
79
+ - SQL tools reject multi-statement and non-read queries.
80
+ - MongoDB tools expose only `find` and aggregation, and block `$out` and `$merge`.
81
+ - Timeouts and result limits are enforced per request.
@@ -0,0 +1,10 @@
1
+ import type { LoadedConnection, MongoQueryResult, SchemaOverview, SqlQueryResult } from "../lib/types.js";
2
+ export interface DatabaseAdapter {
3
+ readonly connection: LoadedConnection;
4
+ describe(): Promise<Record<string, unknown>>;
5
+ getSchemaOverview(): Promise<SchemaOverview>;
6
+ runSqlQuery?(sql: string): Promise<SqlQueryResult>;
7
+ runMongoFind?(collection: string, filter: Record<string, unknown>, limit?: number): Promise<MongoQueryResult>;
8
+ runMongoAggregate?(collection: string, pipeline: unknown[], limit?: number): Promise<MongoQueryResult>;
9
+ close(): Promise<void>;
10
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/adapters/base.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { LoadedConnection } from "../lib/types.js";
2
+ import type { DatabaseAdapter } from "./base.js";
3
+ export declare function createAdapter(connection: LoadedConnection): DatabaseAdapter;
@@ -0,0 +1,14 @@
1
+ import { MongoAdapter } from "./mongoAdapter.js";
2
+ import { MysqlAdapter } from "./mysqlAdapter.js";
3
+ import { PostgresAdapter } from "./postgresAdapter.js";
4
+ export function createAdapter(connection) {
5
+ switch (connection.kind) {
6
+ case "postgres":
7
+ return new PostgresAdapter(connection);
8
+ case "mysql":
9
+ return new MysqlAdapter(connection);
10
+ case "mongodb":
11
+ return new MongoAdapter(connection);
12
+ }
13
+ }
14
+ //# sourceMappingURL=createAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createAdapter.js","sourceRoot":"","sources":["../../src/adapters/createAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,UAAU,aAAa,CAAC,UAA4B;IACxD,QAAQ,UAAU,CAAC,IAAI,EAAE,CAAC;QACxB,KAAK,UAAU;YACb,OAAO,IAAI,eAAe,CAAC,UAAU,CAAC,CAAC;QACzC,KAAK,OAAO;YACV,OAAO,IAAI,YAAY,CAAC,UAAU,CAAC,CAAC;QACtC,KAAK,SAAS;YACZ,OAAO,IAAI,YAAY,CAAC,UAAU,CAAC,CAAC;IACxC,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { LoadedConnection, MongoQueryResult, SchemaOverview } from "../lib/types.js";
2
+ import type { DatabaseAdapter } from "./base.js";
3
+ export declare class MongoAdapter implements DatabaseAdapter {
4
+ #private;
5
+ readonly connection: LoadedConnection;
6
+ constructor(connection: LoadedConnection);
7
+ describe(): Promise<Record<string, unknown>>;
8
+ getSchemaOverview(): Promise<SchemaOverview>;
9
+ runMongoFind(collection: string, filter: Record<string, unknown>, limit?: number): Promise<MongoQueryResult>;
10
+ runMongoAggregate(collection: string, pipeline: unknown[], limit?: number): Promise<MongoQueryResult>;
11
+ close(): Promise<void>;
12
+ }
@@ -0,0 +1,79 @@
1
+ import { MongoClient } from "mongodb";
2
+ import { buildMongoConnectionOptions } from "../config/loadConfig.js";
3
+ import { ExecutionError } from "../lib/errors.js";
4
+ import { assertSafeMongoCollection, assertSafeMongoPipeline } from "../lib/mongoGuard.js";
5
+ export class MongoAdapter {
6
+ connection;
7
+ #client;
8
+ #databaseName;
9
+ constructor(connection) {
10
+ this.connection = connection;
11
+ const options = buildMongoConnectionOptions(connection);
12
+ this.#databaseName = options.database;
13
+ this.#client = new MongoClient(options.uri, options.options);
14
+ }
15
+ async describe() {
16
+ return {
17
+ connection: this.connection.name,
18
+ kind: this.connection.kind,
19
+ database: this.#databaseName,
20
+ host: this.connection.config.host ?? null
21
+ };
22
+ }
23
+ async getSchemaOverview() {
24
+ try {
25
+ const database = this.#client.db(this.#databaseName);
26
+ const collections = await database.listCollections({}, { nameOnly: true }).toArray();
27
+ return {
28
+ connection: this.connection.name,
29
+ kind: this.connection.kind,
30
+ summary: {
31
+ database: this.#databaseName,
32
+ collections: collections.map((collection) => collection.name)
33
+ }
34
+ };
35
+ }
36
+ catch (error) {
37
+ throw new ExecutionError(`MongoDB schema lookup failed: ${String(error)}`);
38
+ }
39
+ }
40
+ async runMongoFind(collection, filter, limit) {
41
+ assertSafeMongoCollection(collection);
42
+ try {
43
+ const database = this.#client.db(this.#databaseName);
44
+ const resolvedLimit = Math.min(limit ?? this.connection.defaults.maxDocuments, this.connection.defaults.maxDocuments);
45
+ const cursor = database.collection(collection).find(filter).limit(resolvedLimit + 1);
46
+ const documents = await cursor.toArray();
47
+ return {
48
+ documents: documents.slice(0, resolvedLimit),
49
+ count: Math.min(documents.length, resolvedLimit),
50
+ truncated: documents.length > resolvedLimit
51
+ };
52
+ }
53
+ catch (error) {
54
+ throw new ExecutionError(`MongoDB find failed: ${String(error)}`);
55
+ }
56
+ }
57
+ async runMongoAggregate(collection, pipeline, limit) {
58
+ assertSafeMongoCollection(collection);
59
+ assertSafeMongoPipeline(pipeline);
60
+ try {
61
+ const database = this.#client.db(this.#databaseName);
62
+ const resolvedLimit = Math.min(limit ?? this.connection.defaults.maxDocuments, this.connection.defaults.maxDocuments);
63
+ const stages = [...pipeline, { $limit: resolvedLimit + 1 }];
64
+ const documents = await database.collection(collection).aggregate(stages).toArray();
65
+ return {
66
+ documents: documents.slice(0, resolvedLimit),
67
+ count: Math.min(documents.length, resolvedLimit),
68
+ truncated: documents.length > resolvedLimit
69
+ };
70
+ }
71
+ catch (error) {
72
+ throw new ExecutionError(`MongoDB aggregate failed: ${String(error)}`);
73
+ }
74
+ }
75
+ async close() {
76
+ await this.#client.close();
77
+ }
78
+ }
79
+ //# sourceMappingURL=mongoAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongoAdapter.js","sourceRoot":"","sources":["../../src/adapters/mongoAdapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { LoadedConnection, SchemaOverview, SqlQueryResult } from "../lib/types.js";
2
+ import type { DatabaseAdapter } from "./base.js";
3
+ export declare class MysqlAdapter implements DatabaseAdapter {
4
+ #private;
5
+ readonly connection: LoadedConnection;
6
+ constructor(connection: LoadedConnection);
7
+ describe(): Promise<Record<string, unknown>>;
8
+ getSchemaOverview(): Promise<SchemaOverview>;
9
+ runSqlQuery(sql: string): Promise<SqlQueryResult>;
10
+ close(): Promise<void>;
11
+ }
@@ -0,0 +1,68 @@
1
+ import mysql from "mysql2/promise";
2
+ import { buildSqlConnectionOptions } from "../config/loadConfig.js";
3
+ import { ExecutionError } from "../lib/errors.js";
4
+ import { assertReadOnlySql } from "../lib/sqlGuard.js";
5
+ export class MysqlAdapter {
6
+ connection;
7
+ #pool;
8
+ constructor(connection) {
9
+ this.connection = connection;
10
+ this.#pool = mysql.createPool({
11
+ ...buildSqlConnectionOptions(connection),
12
+ waitForConnections: true,
13
+ connectionLimit: 5,
14
+ queueLimit: 0
15
+ });
16
+ }
17
+ async describe() {
18
+ return {
19
+ connection: this.connection.name,
20
+ kind: this.connection.kind,
21
+ database: this.connection.config.database ?? null,
22
+ host: this.connection.config.host ?? null
23
+ };
24
+ }
25
+ async getSchemaOverview() {
26
+ const [rows] = await this.#pool.query(`
27
+ select table_schema, table_name
28
+ from information_schema.tables
29
+ where table_schema not in ('information_schema', 'mysql', 'performance_schema', 'sys')
30
+ order by table_schema, table_name
31
+ limit ?
32
+ `, [this.connection.defaults.maxRows]);
33
+ const grouped = new Map();
34
+ for (const row of rows) {
35
+ const schema = String(row.table_schema);
36
+ const table = String(row.table_name);
37
+ const tables = grouped.get(schema) ?? [];
38
+ tables.push(table);
39
+ grouped.set(schema, tables);
40
+ }
41
+ return {
42
+ connection: this.connection.name,
43
+ kind: this.connection.kind,
44
+ summary: Object.fromEntries(grouped)
45
+ };
46
+ }
47
+ async runSqlQuery(sql) {
48
+ assertReadOnlySql(sql);
49
+ const limitedSql = `select * from (${sql}) as db_read_subquery limit ${this.connection.defaults.maxRows + 1}`;
50
+ try {
51
+ const [rows, fields] = await this.#pool.query(limitedSql);
52
+ const limitedRows = rows.slice(0, this.connection.defaults.maxRows);
53
+ return {
54
+ columns: (fields ?? []).map((field) => field.name),
55
+ rows: limitedRows,
56
+ rowCount: limitedRows.length,
57
+ truncated: rows.length > this.connection.defaults.maxRows
58
+ };
59
+ }
60
+ catch (error) {
61
+ throw new ExecutionError(`MySQL query failed: ${String(error)}`);
62
+ }
63
+ }
64
+ async close() {
65
+ await this.#pool.end();
66
+ }
67
+ }
68
+ //# sourceMappingURL=mysqlAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysqlAdapter.js","sourceRoot":"","sources":["../../src/adapters/mysqlAdapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { LoadedConnection, SchemaOverview, SqlQueryResult } from "../lib/types.js";
2
+ import type { DatabaseAdapter } from "./base.js";
3
+ export declare class PostgresAdapter implements DatabaseAdapter {
4
+ #private;
5
+ readonly connection: LoadedConnection;
6
+ constructor(connection: LoadedConnection);
7
+ describe(): Promise<Record<string, unknown>>;
8
+ getSchemaOverview(): Promise<SchemaOverview>;
9
+ runSqlQuery(sql: string): Promise<SqlQueryResult>;
10
+ close(): Promise<void>;
11
+ }
@@ -0,0 +1,64 @@
1
+ import pg from "pg";
2
+ import { buildSqlConnectionOptions } from "../config/loadConfig.js";
3
+ import { ExecutionError } from "../lib/errors.js";
4
+ import { assertReadOnlySql } from "../lib/sqlGuard.js";
5
+ export class PostgresAdapter {
6
+ connection;
7
+ #pool;
8
+ constructor(connection) {
9
+ this.connection = connection;
10
+ this.#pool = new pg.Pool(buildSqlConnectionOptions(connection));
11
+ }
12
+ async describe() {
13
+ return {
14
+ connection: this.connection.name,
15
+ kind: this.connection.kind,
16
+ database: this.connection.config.database ?? null,
17
+ host: this.connection.config.host ?? null
18
+ };
19
+ }
20
+ async getSchemaOverview() {
21
+ const sql = `
22
+ select table_schema, table_name
23
+ from information_schema.tables
24
+ where table_schema not in ('pg_catalog', 'information_schema')
25
+ order by table_schema, table_name
26
+ limit $1
27
+ `;
28
+ const result = await this.#pool.query(sql, [this.connection.defaults.maxRows]);
29
+ const grouped = new Map();
30
+ for (const row of result.rows) {
31
+ const schema = String(row.table_schema);
32
+ const table = String(row.table_name);
33
+ const tables = grouped.get(schema) ?? [];
34
+ tables.push(table);
35
+ grouped.set(schema, tables);
36
+ }
37
+ return {
38
+ connection: this.connection.name,
39
+ kind: this.connection.kind,
40
+ summary: Object.fromEntries(grouped)
41
+ };
42
+ }
43
+ async runSqlQuery(sql) {
44
+ assertReadOnlySql(sql);
45
+ const limitedSql = `select * from (${sql}) as db_read_subquery limit ${this.connection.defaults.maxRows + 1}`;
46
+ try {
47
+ const result = await this.#pool.query(limitedSql);
48
+ const rows = result.rows.slice(0, this.connection.defaults.maxRows);
49
+ return {
50
+ columns: result.fields.map((field) => field.name),
51
+ rows,
52
+ rowCount: rows.length,
53
+ truncated: result.rows.length > this.connection.defaults.maxRows
54
+ };
55
+ }
56
+ catch (error) {
57
+ throw new ExecutionError(`PostgreSQL query failed: ${String(error)}`);
58
+ }
59
+ }
60
+ async close() {
61
+ await this.#pool.end();
62
+ }
63
+ }
64
+ //# sourceMappingURL=postgresAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgresAdapter.js","sourceRoot":"","sources":["../../src/adapters/postgresAdapter.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import process from "node:process";
3
+ import { loadConfig } from "./config/loadConfig.js";
4
+ import { ConfigError, ExecutionError, ValidationError } from "./lib/errors.js";
5
+ import { startMcpServer } from "./mcp/server.js";
6
+ async function main() {
7
+ const [command, ...args] = process.argv.slice(2);
8
+ switch (command) {
9
+ case "serve":
10
+ await serveCommand(args);
11
+ return;
12
+ case "validate-config":
13
+ await validateConfigCommand(args);
14
+ return;
15
+ default:
16
+ printUsage();
17
+ process.exitCode = 1;
18
+ }
19
+ }
20
+ async function serveCommand(args) {
21
+ const options = parseArgs(args);
22
+ const config = await loadConfig({
23
+ configPath: options.configPath,
24
+ environment: options.environment,
25
+ connectionNames: options.connections
26
+ });
27
+ await startMcpServer(config);
28
+ }
29
+ async function validateConfigCommand(args) {
30
+ const options = parseArgs(args);
31
+ const config = await loadConfig({
32
+ configPath: options.configPath,
33
+ environment: options.environment,
34
+ connectionNames: options.connections
35
+ });
36
+ process.stdout.write(`${JSON.stringify({
37
+ ok: true,
38
+ environment: config.activeEnvironment.name,
39
+ connectionCount: config.activeEnvironment.connections.size
40
+ }, null, 2)}\n`);
41
+ }
42
+ function parseArgs(args) {
43
+ let configPath = ".db-read.yml";
44
+ let environment;
45
+ let connections;
46
+ for (let index = 0; index < args.length; index += 1) {
47
+ const arg = args[index];
48
+ const next = args[index + 1];
49
+ if (arg === "--config" && next) {
50
+ configPath = next;
51
+ index += 1;
52
+ continue;
53
+ }
54
+ if (arg === "--env" && next) {
55
+ environment = next;
56
+ index += 1;
57
+ continue;
58
+ }
59
+ if (arg === "--connections" && next) {
60
+ connections = next.split(",").map((value) => value.trim()).filter(Boolean);
61
+ index += 1;
62
+ continue;
63
+ }
64
+ }
65
+ return { configPath, environment, connections };
66
+ }
67
+ function printUsage() {
68
+ process.stderr.write([
69
+ "Usage:",
70
+ " db-read serve --env <environment> [--config .db-read.yml] [--connections a,b,c]",
71
+ " db-read validate-config [--env <environment>] [--config .db-read.yml] [--connections a,b,c]"
72
+ ].join("\n") + "\n");
73
+ }
74
+ main().catch((error) => {
75
+ const message = error instanceof ConfigError || error instanceof ValidationError || error instanceof ExecutionError
76
+ ? error.message
77
+ : String(error);
78
+ process.stderr.write(`${message}\n`);
79
+ process.exitCode = 1;
80
+ });
81
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { ConfigDefaults, LoadedConnection, LoadedEnvironment } from "../lib/types.js";
2
+ export interface LoadConfigOptions {
3
+ configPath: string;
4
+ environment?: string;
5
+ connectionNames?: string[];
6
+ }
7
+ export interface LoadedConfig {
8
+ configPath: string;
9
+ defaults: ConfigDefaults;
10
+ activeEnvironment: LoadedEnvironment;
11
+ environments: string[];
12
+ }
13
+ export declare function loadConfig(options: LoadConfigOptions): Promise<LoadedConfig>;
14
+ export declare function maskConnection(connection: LoadedConnection): Record<string, unknown>;
15
+ export declare function buildSqlConnectionOptions(connection: LoadedConnection): Record<string, unknown>;
16
+ export declare function buildMongoConnectionOptions(connection: LoadedConnection): {
17
+ uri: string;
18
+ database: string;
19
+ options: Record<string, unknown>;
20
+ };
@@ -0,0 +1,158 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { parse } from "yaml";
4
+ import { z } from "zod";
5
+ import { ConfigError } from "../lib/errors.js";
6
+ const DEFAULTS = {
7
+ timeoutMs: 10_000,
8
+ maxRows: 1_000,
9
+ maxDocuments: 1_000
10
+ };
11
+ const baseConnectionSchema = z.object({
12
+ kind: z.enum(["postgres", "mysql", "mongodb"]),
13
+ uri: z.string().min(1).optional(),
14
+ host: z.string().min(1).optional(),
15
+ port: z.number().int().positive().optional(),
16
+ user: z.string().min(1).optional(),
17
+ password: z.string().min(1).optional(),
18
+ database: z.string().min(1).optional(),
19
+ ssl: z.boolean().optional(),
20
+ options: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional()
21
+ });
22
+ const configSchema = z.object({
23
+ version: z.literal(1),
24
+ defaultEnvironment: z.string().min(1).optional(),
25
+ defaults: z.object({
26
+ timeoutMs: z.number().int().positive().optional(),
27
+ maxRows: z.number().int().positive().optional(),
28
+ maxDocuments: z.number().int().positive().optional()
29
+ }).optional(),
30
+ environments: z.record(z.string(), z.object({
31
+ connections: z.record(z.string(), baseConnectionSchema)
32
+ }))
33
+ });
34
+ export async function loadConfig(options) {
35
+ const resolvedPath = path.resolve(options.configPath);
36
+ const raw = await readFile(resolvedPath, "utf8").catch((error) => {
37
+ throw new ConfigError(`Unable to read config file at ${resolvedPath}: ${String(error)}`);
38
+ });
39
+ const parsed = parse(raw);
40
+ const config = configSchema.safeParse(parsed);
41
+ if (!config.success) {
42
+ throw new ConfigError(`Invalid config file: ${config.error.message}`);
43
+ }
44
+ const defaults = {
45
+ ...DEFAULTS,
46
+ ...config.data.defaults
47
+ };
48
+ const environmentName = options.environment ?? config.data.defaultEnvironment ?? firstEnvironmentName(config.data);
49
+ const envConfig = config.data.environments[environmentName];
50
+ if (!envConfig) {
51
+ throw new ConfigError(`Environment '${environmentName}' was not found in ${resolvedPath}.`);
52
+ }
53
+ const requestedNames = options.connectionNames?.length ? new Set(options.connectionNames) : null;
54
+ const connections = new Map();
55
+ for (const [name, connection] of Object.entries(envConfig.connections)) {
56
+ if (requestedNames && !requestedNames.has(name)) {
57
+ continue;
58
+ }
59
+ validateConnectionShape(name, connection);
60
+ connections.set(name, {
61
+ name,
62
+ environment: environmentName,
63
+ kind: connection.kind,
64
+ config: connection,
65
+ defaults
66
+ });
67
+ }
68
+ if (requestedNames) {
69
+ for (const name of requestedNames) {
70
+ if (!connections.has(name)) {
71
+ throw new ConfigError(`Connection '${name}' was not found in environment '${environmentName}'.`);
72
+ }
73
+ }
74
+ }
75
+ return {
76
+ configPath: resolvedPath,
77
+ defaults,
78
+ activeEnvironment: {
79
+ name: environmentName,
80
+ connections
81
+ },
82
+ environments: Object.keys(config.data.environments)
83
+ };
84
+ }
85
+ function firstEnvironmentName(config) {
86
+ const name = Object.keys(config.environments)[0];
87
+ if (!name) {
88
+ throw new ConfigError("At least one environment is required.");
89
+ }
90
+ return name;
91
+ }
92
+ function validateConnectionShape(name, connection) {
93
+ const hasUri = Boolean(connection.uri);
94
+ const hasHostShape = Boolean(connection.host && connection.user && connection.database);
95
+ if (!hasUri && !hasHostShape) {
96
+ throw new ConfigError(`Connection '${name}' must define either 'uri' or host/user/database fields.`);
97
+ }
98
+ if (connection.kind === "mongodb" && !connection.database) {
99
+ throw new ConfigError(`MongoDB connection '${name}' requires 'database'.`);
100
+ }
101
+ }
102
+ export function maskConnection(connection) {
103
+ return {
104
+ name: connection.name,
105
+ environment: connection.environment,
106
+ kind: connection.kind,
107
+ database: connection.config.database ?? null,
108
+ host: connection.config.host ?? null,
109
+ hasUri: Boolean(connection.config.uri),
110
+ ssl: connection.config.ssl ?? false
111
+ };
112
+ }
113
+ export function buildSqlConnectionOptions(connection) {
114
+ const options = { ...(connection.config.options ?? {}) };
115
+ if (connection.config.uri) {
116
+ return {
117
+ connectionString: connection.config.uri,
118
+ ssl: connection.config.ssl ? { rejectUnauthorized: false } : undefined,
119
+ ...options
120
+ };
121
+ }
122
+ return {
123
+ host: connection.config.host,
124
+ port: connection.config.port,
125
+ user: connection.config.user,
126
+ password: connection.config.password,
127
+ database: connection.config.database,
128
+ ssl: connection.config.ssl ? { rejectUnauthorized: false } : undefined,
129
+ ...options
130
+ };
131
+ }
132
+ export function buildMongoConnectionOptions(connection) {
133
+ const database = connection.config.database;
134
+ if (!database) {
135
+ throw new ConfigError(`MongoDB connection '${connection.name}' is missing 'database'.`);
136
+ }
137
+ if (connection.config.uri) {
138
+ return {
139
+ uri: connection.config.uri,
140
+ database,
141
+ options: { ...(connection.config.options ?? {}) }
142
+ };
143
+ }
144
+ const auth = connection.config.user
145
+ ? `${encodeURIComponent(connection.config.user)}:${encodeURIComponent(connection.config.password ?? "")}@`
146
+ : "";
147
+ const host = connection.config.host ?? "localhost";
148
+ const port = connection.config.port ?? 27017;
149
+ return {
150
+ uri: `${connection.config.uri ?? `mongodb://${auth}${host}:${port}`}`,
151
+ database,
152
+ options: {
153
+ ...(connection.config.ssl ? { tls: true } : {}),
154
+ ...(connection.config.options ?? {})
155
+ }
156
+ };
157
+ }
158
+ //# sourceMappingURL=loadConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadConfig.js","sourceRoot":"","sources":["../../src/config/loadConfig.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare class ConfigError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class ValidationError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ export declare class ExecutionError extends Error {
8
+ constructor(message: string);
9
+ }
@@ -0,0 +1,19 @@
1
+ export class ConfigError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "ConfigError";
5
+ }
6
+ }
7
+ export class ValidationError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "ValidationError";
11
+ }
12
+ }
13
+ export class ExecutionError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "ExecutionError";
17
+ }
18
+ }
19
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export declare function assertSafeMongoCollection(name: string): void;
2
+ export declare function assertSafeMongoPipeline(pipeline: unknown[]): void;
@@ -0,0 +1,20 @@
1
+ import { ValidationError } from "./errors.js";
2
+ const FORBIDDEN_STAGES = new Set(["$out", "$merge"]);
3
+ export function assertSafeMongoCollection(name) {
4
+ if (!name.trim()) {
5
+ throw new ValidationError("Collection name is required.");
6
+ }
7
+ }
8
+ export function assertSafeMongoPipeline(pipeline) {
9
+ for (const stage of pipeline) {
10
+ if (!stage || typeof stage !== "object" || Array.isArray(stage)) {
11
+ throw new ValidationError("Each pipeline stage must be an object.");
12
+ }
13
+ for (const key of Object.keys(stage)) {
14
+ if (FORBIDDEN_STAGES.has(key)) {
15
+ throw new ValidationError(`Aggregation stage ${key} is not allowed.`);
16
+ }
17
+ }
18
+ }
19
+ }
20
+ //# sourceMappingURL=mongoGuard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongoGuard.js","sourceRoot":"","sources":["../../src/lib/mongoGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAErD,MAAM,UAAU,yBAAyB,CAAC,IAAY;IACpD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,eAAe,CAAC,8BAA8B,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,QAAmB;IACzD,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChE,MAAM,IAAI,eAAe,CAAC,wCAAwC,CAAC,CAAC;QACtE,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,EAAE,CAAC;YAChE,IAAI,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,IAAI,eAAe,CAAC,qBAAqB,GAAG,kBAAkB,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function assertReadOnlySql(sql: string): void;
@@ -0,0 +1,41 @@
1
+ import { ValidationError } from "./errors.js";
2
+ const READ_ONLY_KEYWORDS = new Set([
3
+ "select",
4
+ "with"
5
+ ]);
6
+ const FORBIDDEN_SQL_PATTERNS = [
7
+ /\binsert\b/i,
8
+ /\bupdate\b/i,
9
+ /\bdelete\b/i,
10
+ /\bdrop\b/i,
11
+ /\btruncate\b/i,
12
+ /\balter\b/i,
13
+ /\bcreate\b/i,
14
+ /\bgrant\b/i,
15
+ /\brevoke\b/i,
16
+ /\bcopy\b/i,
17
+ /\bmerge\b/i,
18
+ /\bcall\b/i,
19
+ /\bdo\b/i,
20
+ /\bset\b/i,
21
+ /\block\b/i
22
+ ];
23
+ export function assertReadOnlySql(sql) {
24
+ const trimmed = sql.trim();
25
+ if (!trimmed) {
26
+ throw new ValidationError("SQL query is required.");
27
+ }
28
+ if (trimmed.includes(";")) {
29
+ throw new ValidationError("Only a single SQL statement is allowed.");
30
+ }
31
+ const firstToken = trimmed.split(/\s+/u, 1)[0]?.toLowerCase();
32
+ if (!firstToken || !READ_ONLY_KEYWORDS.has(firstToken)) {
33
+ throw new ValidationError(`Only read-only SQL statements are allowed. Received: ${firstToken ?? "unknown"}`);
34
+ }
35
+ for (const pattern of FORBIDDEN_SQL_PATTERNS) {
36
+ if (pattern.test(trimmed)) {
37
+ throw new ValidationError("Write or session-modifying SQL is not allowed.");
38
+ }
39
+ }
40
+ }
41
+ //# sourceMappingURL=sqlGuard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlGuard.js","sourceRoot":"","sources":["../../src/lib/sqlGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,QAAQ;IACR,MAAM;CACP,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG;IAC7B,aAAa;IACb,aAAa;IACb,aAAa;IACb,WAAW;IACX,eAAe;IACf,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,aAAa;IACb,WAAW;IACX,YAAY;IACZ,WAAW;IACX,SAAS;IACT,UAAU;IACV,WAAW;CACZ,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAE3B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CAAC,wBAAwB,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,eAAe,CAAC,yCAAyC,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IAC9D,IAAI,CAAC,UAAU,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,eAAe,CAAC,wDAAwD,UAAU,IAAI,SAAS,EAAE,CAAC,CAAC;IAC/G,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,sBAAsB,EAAE,CAAC;QAC7C,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,eAAe,CAAC,gDAAgD,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,64 @@
1
+ export type ConnectionKind = "postgres" | "mysql" | "mongodb";
2
+ export interface ConfigDefaults {
3
+ timeoutMs: number;
4
+ maxRows: number;
5
+ maxDocuments: number;
6
+ }
7
+ interface BaseConnectionConfig {
8
+ kind: ConnectionKind;
9
+ uri?: string;
10
+ host?: string;
11
+ port?: number;
12
+ user?: string;
13
+ password?: string;
14
+ database?: string;
15
+ ssl?: boolean;
16
+ options?: Record<string, string | number | boolean>;
17
+ }
18
+ export interface PostgresConnectionConfig extends BaseConnectionConfig {
19
+ kind: "postgres";
20
+ }
21
+ export interface MysqlConnectionConfig extends BaseConnectionConfig {
22
+ kind: "mysql";
23
+ }
24
+ export interface MongoConnectionConfig extends BaseConnectionConfig {
25
+ kind: "mongodb";
26
+ }
27
+ export type ConnectionConfig = PostgresConnectionConfig | MysqlConnectionConfig | MongoConnectionConfig;
28
+ export interface EnvironmentConfig {
29
+ connections: Record<string, ConnectionConfig>;
30
+ }
31
+ export interface DbReadConfig {
32
+ version: 1;
33
+ defaultEnvironment?: string;
34
+ defaults?: Partial<ConfigDefaults>;
35
+ environments: Record<string, EnvironmentConfig>;
36
+ }
37
+ export interface LoadedConnection {
38
+ name: string;
39
+ environment: string;
40
+ kind: ConnectionKind;
41
+ config: ConnectionConfig;
42
+ defaults: ConfigDefaults;
43
+ }
44
+ export interface LoadedEnvironment {
45
+ name: string;
46
+ connections: Map<string, LoadedConnection>;
47
+ }
48
+ export interface SqlQueryResult {
49
+ columns: string[];
50
+ rows: Array<Record<string, unknown>>;
51
+ rowCount: number;
52
+ truncated: boolean;
53
+ }
54
+ export interface MongoQueryResult {
55
+ documents: Array<Record<string, unknown>>;
56
+ count: number;
57
+ truncated: boolean;
58
+ }
59
+ export interface SchemaOverview {
60
+ connection: string;
61
+ kind: ConnectionKind;
62
+ summary: Record<string, unknown>;
63
+ }
64
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ import type { LoadedConfig } from "../config/loadConfig.js";
2
+ export declare function startMcpServer(config: LoadedConfig): Promise<void>;
@@ -0,0 +1,172 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { createAdapter } from "../adapters/createAdapter.js";
5
+ import { maskConnection } from "../config/loadConfig.js";
6
+ import { ExecutionError, ValidationError } from "../lib/errors.js";
7
+ export async function startMcpServer(config) {
8
+ const server = new McpServer({
9
+ name: "db-read",
10
+ version: "0.1.0"
11
+ });
12
+ const adapters = new Map(Array.from(config.activeEnvironment.connections.values()).map((connection) => [connection.name, createAdapter(connection)]));
13
+ server.registerResource("connections", "dbread://connections", {
14
+ title: "Configured DB connections",
15
+ description: "Configured database connections for the active environment.",
16
+ mimeType: "application/json"
17
+ }, async () => ({
18
+ contents: [{
19
+ uri: "dbread://connections",
20
+ mimeType: "application/json",
21
+ text: JSON.stringify(Array.from(config.activeEnvironment.connections.values()).map(maskConnection), null, 2)
22
+ }]
23
+ }));
24
+ for (const [name, adapter] of adapters.entries()) {
25
+ server.registerResource(`${name}-schema`, `dbread://connections/${name}/schema`, {
26
+ title: `${name} schema overview`,
27
+ description: `High-level schema overview for ${name}.`,
28
+ mimeType: "application/json"
29
+ }, async () => {
30
+ const overview = await withAdapter(adapter, () => adapter.getSchemaOverview());
31
+ return {
32
+ contents: [{
33
+ uri: `dbread://connections/${name}/schema`,
34
+ mimeType: "application/json",
35
+ text: JSON.stringify(overview, null, 2)
36
+ }]
37
+ };
38
+ });
39
+ }
40
+ server.registerTool("list_connections", {
41
+ title: "List connections",
42
+ description: "List the configured database connections for the active environment."
43
+ }, async () => ({
44
+ content: [{
45
+ type: "text",
46
+ text: JSON.stringify(Array.from(config.activeEnvironment.connections.values()).map(maskConnection), null, 2)
47
+ }]
48
+ }));
49
+ server.registerTool("describe_connection", {
50
+ title: "Describe connection",
51
+ description: "Describe one configured database connection.",
52
+ inputSchema: {
53
+ name: z.string().min(1)
54
+ }
55
+ }, async ({ name }) => {
56
+ const adapter = requireAdapter(adapters, name);
57
+ const description = await withAdapter(adapter, () => adapter.describe());
58
+ return {
59
+ content: [{
60
+ type: "text",
61
+ text: JSON.stringify(description, null, 2)
62
+ }]
63
+ };
64
+ });
65
+ server.registerTool("get_schema_overview", {
66
+ title: "Get schema overview",
67
+ description: "Inspect the high-level schema for one configured connection.",
68
+ inputSchema: {
69
+ name: z.string().min(1)
70
+ }
71
+ }, async ({ name }) => {
72
+ const adapter = requireAdapter(adapters, name);
73
+ const overview = await withAdapter(adapter, () => adapter.getSchemaOverview());
74
+ return {
75
+ content: [{
76
+ type: "text",
77
+ text: JSON.stringify(overview, null, 2)
78
+ }]
79
+ };
80
+ });
81
+ server.registerTool("sql_query", {
82
+ title: "Run SQL query",
83
+ description: "Run a single read-only SQL query against a PostgreSQL or MySQL connection.",
84
+ inputSchema: {
85
+ name: z.string().min(1),
86
+ sql: z.string().min(1)
87
+ }
88
+ }, async ({ name, sql }) => {
89
+ const adapter = requireAdapter(adapters, name);
90
+ if (!adapter.runSqlQuery) {
91
+ throw new ValidationError(`Connection '${name}' does not support SQL queries.`);
92
+ }
93
+ const result = await withAdapter(adapter, () => adapter.runSqlQuery(sql));
94
+ return {
95
+ content: [{
96
+ type: "text",
97
+ text: JSON.stringify(result, null, 2)
98
+ }]
99
+ };
100
+ });
101
+ server.registerTool("mongo_find", {
102
+ title: "Run Mongo find",
103
+ description: "Run a read-only MongoDB find query against one collection.",
104
+ inputSchema: {
105
+ name: z.string().min(1),
106
+ collection: z.string().min(1),
107
+ filter: z.record(z.string(), z.unknown()).default({}),
108
+ limit: z.number().int().positive().optional()
109
+ }
110
+ }, async ({ name, collection, filter, limit }) => {
111
+ const adapter = requireAdapter(adapters, name);
112
+ if (!adapter.runMongoFind) {
113
+ throw new ValidationError(`Connection '${name}' does not support MongoDB find queries.`);
114
+ }
115
+ const result = await withAdapter(adapter, () => adapter.runMongoFind(collection, filter, limit));
116
+ return {
117
+ content: [{
118
+ type: "text",
119
+ text: JSON.stringify(result, null, 2)
120
+ }]
121
+ };
122
+ });
123
+ server.registerTool("mongo_aggregate", {
124
+ title: "Run Mongo aggregate",
125
+ description: "Run a read-only MongoDB aggregation pipeline.",
126
+ inputSchema: {
127
+ name: z.string().min(1),
128
+ collection: z.string().min(1),
129
+ pipeline: z.array(z.unknown()),
130
+ limit: z.number().int().positive().optional()
131
+ }
132
+ }, async ({ name, collection, pipeline, limit }) => {
133
+ const adapter = requireAdapter(adapters, name);
134
+ if (!adapter.runMongoAggregate) {
135
+ throw new ValidationError(`Connection '${name}' does not support MongoDB aggregation.`);
136
+ }
137
+ const result = await withAdapter(adapter, () => adapter.runMongoAggregate(collection, pipeline, limit));
138
+ return {
139
+ content: [{
140
+ type: "text",
141
+ text: JSON.stringify(result, null, 2)
142
+ }]
143
+ };
144
+ });
145
+ const transport = new StdioServerTransport();
146
+ await server.connect(transport);
147
+ }
148
+ function requireAdapter(adapters, name) {
149
+ const adapter = adapters.get(name);
150
+ if (!adapter) {
151
+ throw new ValidationError(`Unknown connection '${name}'.`);
152
+ }
153
+ return adapter;
154
+ }
155
+ async function withAdapter(adapter, fn) {
156
+ const timeoutMs = adapter.connection.defaults.timeoutMs;
157
+ let timeout;
158
+ try {
159
+ return await Promise.race([
160
+ fn(),
161
+ new Promise((_, reject) => {
162
+ timeout = setTimeout(() => reject(new ExecutionError(`Operation timed out after ${timeoutMs}ms.`)), timeoutMs);
163
+ })
164
+ ]);
165
+ }
166
+ finally {
167
+ if (timeout) {
168
+ clearTimeout(timeout);
169
+ }
170
+ }
171
+ }
172
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "db-read",
3
+ "version": "0.1.0",
4
+ "description": "Read-only MCP server for PostgreSQL, MySQL, and MongoDB.",
5
+ "type": "module",
6
+ "bin": "dist/cli.js",
7
+ "files": [
8
+ "dist",
9
+ ".db-read.example.yml",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc -p tsconfig.json",
14
+ "dev": "tsx src/cli.ts serve --env dev --config .db-read.yml",
15
+ "prepublishOnly": "npm run typecheck && npm test && npm run build",
16
+ "test": "vitest run",
17
+ "typecheck": "tsc --noEmit -p tsconfig.json"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "database",
22
+ "postgres",
23
+ "mysql",
24
+ "mongodb",
25
+ "read-only"
26
+ ],
27
+ "license": "MIT",
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.29.0",
33
+ "mongodb": "^6.21.0",
34
+ "mysql2": "^3.22.3",
35
+ "pg": "^8.21.0",
36
+ "yaml": "^2.9.0",
37
+ "zod": "^4.4.3"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^25.9.1",
41
+ "@types/pg": "^8.20.0",
42
+ "tsx": "^4.22.3",
43
+ "typescript": "^6.0.3",
44
+ "vitest": "^4.1.7"
45
+ }
46
+ }