@runcontext/db 0.5.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eric Kittelson
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.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,508 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
4
+ // src/server.ts
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { z } from "zod";
8
+ import { createAdapter } from "@runcontext/core";
9
+
10
+ // src/guardrails.ts
11
+ function enforceReadOnly(adapter) {
12
+ switch (adapter) {
13
+ case "postgres":
14
+ return "SET default_transaction_read_only = ON";
15
+ case "mysql":
16
+ return "SET SESSION TRANSACTION READ ONLY";
17
+ case "duckdb":
18
+ return null;
19
+ // DuckDB: no session-level read-only SQL; use access_mode=read_only in connection config
20
+ case "mssql":
21
+ return null;
22
+ // MSSQL: no session-level read-only SQL; use db_query validation instead
23
+ case "snowflake":
24
+ return null;
25
+ // Snowflake uses role-based access; no session-level read-only SQL
26
+ case "bigquery":
27
+ return null;
28
+ // BigQuery is read-only for queries by default (no DML in standard SQL mode)
29
+ case "clickhouse":
30
+ return "SET readonly = 1";
31
+ case "databricks":
32
+ return null;
33
+ // Databricks uses role-based access control
34
+ case "sqlite":
35
+ return "PRAGMA query_only = ON";
36
+ default:
37
+ return null;
38
+ }
39
+ }
40
+ function applyRowLimit(query, limit) {
41
+ const trimmed = query.trim().replace(/;+\s*$/, "");
42
+ const limitMatch = trimmed.match(/\bLIMIT\s+(\d+)\s*$/i);
43
+ if (limitMatch) {
44
+ const existingLimit = parseInt(limitMatch[1], 10);
45
+ const effectiveLimit = Math.min(existingLimit, limit);
46
+ return trimmed.replace(/\bLIMIT\s+\d+\s*$/i, `LIMIT ${effectiveLimit}`);
47
+ }
48
+ return `${trimmed}
49
+ LIMIT ${limit}`;
50
+ }
51
+ function applyTimeout(adapter, ms) {
52
+ switch (adapter) {
53
+ case "postgres":
54
+ return `SET statement_timeout = ${ms}`;
55
+ case "mysql":
56
+ return `SET SESSION MAX_EXECUTION_TIME = ${ms}`;
57
+ case "duckdb":
58
+ return null;
59
+ // DuckDB doesn't support statement timeouts via SQL
60
+ case "mssql":
61
+ return null;
62
+ // MSSQL uses connection-level timeouts, not SQL
63
+ case "snowflake":
64
+ return `ALTER SESSION SET STATEMENT_TIMEOUT_IN_SECONDS = ${Math.ceil(ms / 1e3)}`;
65
+ case "bigquery":
66
+ return null;
67
+ // BigQuery uses API-level timeouts
68
+ case "clickhouse":
69
+ return `SET max_execution_time = ${Math.ceil(ms / 1e3)}`;
70
+ case "databricks":
71
+ return null;
72
+ // Databricks uses API-level timeouts
73
+ case "sqlite":
74
+ return null;
75
+ // SQLite uses API-level timeouts
76
+ default:
77
+ return null;
78
+ }
79
+ }
80
+ var DEFAULT_ROW_LIMIT = 100;
81
+ var MAX_QUERY_ROW_LIMIT = 1e3;
82
+ var DEFAULT_TIMEOUT_MS = 3e4;
83
+ var DDL_KEYWORDS = ["CREATE", "DROP", "ALTER", "TRUNCATE", "RENAME"];
84
+ var DML_KEYWORDS = ["INSERT", "UPDATE", "DELETE", "MERGE", "UPSERT"];
85
+ var ADMIN_KEYWORDS = [
86
+ "GRANT",
87
+ "REVOKE",
88
+ "CALL",
89
+ "EXEC",
90
+ "COPY",
91
+ "LOAD",
92
+ "VACUUM",
93
+ "BEGIN",
94
+ "COMMIT",
95
+ "ROLLBACK",
96
+ "LOCK",
97
+ "SET",
98
+ "RESET"
99
+ ];
100
+ var ALLOWED_KEYWORDS = ["SELECT", "WITH", "EXPLAIN", "SHOW", "DESCRIBE", "VALUES"];
101
+ function validateReadOnlySQL(sql) {
102
+ const trimmed = sql.trim();
103
+ if (!trimmed) {
104
+ return { valid: false, reason: "Query is empty" };
105
+ }
106
+ const withoutTrailingSemicolon = trimmed.replace(/;+\s*$/, "");
107
+ if (withoutTrailingSemicolon.includes(";")) {
108
+ return { valid: false, reason: "Multi-statement queries are not allowed" };
109
+ }
110
+ const firstWord = trimmed.split(/\s+/)[0].toUpperCase();
111
+ const allDisallowed = [...DDL_KEYWORDS, ...DML_KEYWORDS, ...ADMIN_KEYWORDS];
112
+ if (allDisallowed.includes(firstWord)) {
113
+ return { valid: false, reason: `Statement type '${firstWord}' is not allowed. Only read-only queries are permitted.` };
114
+ }
115
+ if (!ALLOWED_KEYWORDS.includes(firstWord)) {
116
+ return { valid: false, reason: `Statement type '${firstWord}' is not recognized as a read-only query` };
117
+ }
118
+ if (firstWord === "WITH") {
119
+ const allMutating = [...DDL_KEYWORDS, ...DML_KEYWORDS, ...ADMIN_KEYWORDS];
120
+ const mutatingPattern = new RegExp(
121
+ `\\b(${allMutating.join("|")})\\b`,
122
+ "i"
123
+ );
124
+ if (mutatingPattern.test(withoutTrailingSemicolon)) {
125
+ return {
126
+ valid: false,
127
+ reason: "CTE body contains a mutating statement. Only read-only queries are permitted."
128
+ };
129
+ }
130
+ }
131
+ return { valid: true };
132
+ }
133
+
134
+ // src/tools.ts
135
+ async function listSchemas(adapter, adapterType) {
136
+ let sql;
137
+ switch (adapterType) {
138
+ case "sqlite":
139
+ case "duckdb":
140
+ sql = "SELECT DISTINCT schema_name FROM information_schema.schemata ORDER BY schema_name";
141
+ break;
142
+ case "bigquery":
143
+ sql = "SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA ORDER BY schema_name";
144
+ break;
145
+ case "databricks":
146
+ sql = "SHOW SCHEMAS";
147
+ break;
148
+ default:
149
+ sql = "SELECT schema_name FROM information_schema.schemata ORDER BY schema_name";
150
+ }
151
+ try {
152
+ const result = await adapter.query(sql);
153
+ const schemas = result.rows.map((row) => {
154
+ const firstCol = result.columns[0];
155
+ return String(firstCol ? row[firstCol] : Object.values(row)[0]);
156
+ });
157
+ return { schemas };
158
+ } catch {
159
+ return { schemas: [] };
160
+ }
161
+ }
162
+ async function listTables(adapter) {
163
+ const tables = await adapter.listTables();
164
+ return { tables };
165
+ }
166
+ async function describeTable(adapter, table) {
167
+ const columns = await adapter.listColumns(table);
168
+ return { table, columns };
169
+ }
170
+ async function sampleValues(adapter, table, limit) {
171
+ const effectiveLimit = Math.min(limit ?? DEFAULT_ROW_LIMIT, DEFAULT_ROW_LIMIT);
172
+ const sql = applyRowLimit(`SELECT * FROM ${quoteIdentifier(table)}`, effectiveLimit);
173
+ const result = await adapter.query(sql);
174
+ return {
175
+ table,
176
+ query: sql,
177
+ columns: result.columns,
178
+ rows: result.rows,
179
+ row_count: result.row_count
180
+ };
181
+ }
182
+ async function listRelationships(adapter, adapterType, table) {
183
+ let sql;
184
+ switch (adapterType) {
185
+ case "postgres":
186
+ case "mysql":
187
+ case "mssql":
188
+ case "snowflake": {
189
+ const whereClause = table ? `AND kcu.table_name = '${escapeString(table)}'` : "";
190
+ sql = `
191
+ SELECT
192
+ kcu.constraint_name,
193
+ kcu.table_name AS source_table,
194
+ kcu.column_name AS source_column,
195
+ ccu.table_name AS target_table,
196
+ ccu.column_name AS target_column
197
+ FROM information_schema.key_column_usage kcu
198
+ JOIN information_schema.constraint_column_usage ccu
199
+ ON kcu.constraint_name = ccu.constraint_name
200
+ JOIN information_schema.table_constraints tc
201
+ ON kcu.constraint_name = tc.constraint_name
202
+ WHERE tc.constraint_type = 'FOREIGN KEY'
203
+ ${whereClause}
204
+ ORDER BY kcu.constraint_name, kcu.ordinal_position
205
+ `;
206
+ break;
207
+ }
208
+ case "sqlite": {
209
+ if (!table) {
210
+ const tables = await adapter.listTables();
211
+ const allRels = [];
212
+ for (const t of tables) {
213
+ try {
214
+ const result = await adapter.query(`PRAGMA foreign_key_list("${t.name.replace(/"/g, '""')}")`);
215
+ for (const row of result.rows) {
216
+ allRels.push({
217
+ constraint_name: `fk_${t.name}_${row["from"]}`,
218
+ source_table: t.name,
219
+ source_column: String(row["from"]),
220
+ target_table: String(row["table"]),
221
+ target_column: String(row["to"])
222
+ });
223
+ }
224
+ } catch {
225
+ }
226
+ }
227
+ return { relationships: allRels };
228
+ }
229
+ sql = `PRAGMA foreign_key_list("${table.replace(/"/g, '""')}")`;
230
+ try {
231
+ const result = await adapter.query(sql);
232
+ const relationships = result.rows.map((row) => ({
233
+ constraint_name: `fk_${table}_${row["from"]}`,
234
+ source_table: table,
235
+ source_column: String(row["from"]),
236
+ target_table: String(row["table"]),
237
+ target_column: String(row["to"])
238
+ }));
239
+ return { relationships };
240
+ } catch {
241
+ return { relationships: [] };
242
+ }
243
+ }
244
+ case "duckdb": {
245
+ const whereClause = table ? `WHERE source_table = '${escapeString(table)}'` : "";
246
+ sql = `
247
+ SELECT
248
+ constraint_name,
249
+ source_table,
250
+ source_column,
251
+ target_table,
252
+ target_column
253
+ FROM (
254
+ SELECT
255
+ dc.constraint_name,
256
+ dc.table_name AS source_table,
257
+ unnest(dc.constraint_column_names) AS source_column,
258
+ rc.table_name AS target_table,
259
+ unnest(rc.constraint_column_names) AS target_column
260
+ FROM duckdb_constraints() dc
261
+ JOIN duckdb_constraints() rc
262
+ ON dc.constraint_name IS NOT NULL
263
+ AND dc.constraint_type = 'FOREIGN KEY'
264
+ AND rc.constraint_type = 'PRIMARY KEY'
265
+ ) sub
266
+ ${whereClause}
267
+ ORDER BY constraint_name
268
+ `;
269
+ break;
270
+ }
271
+ default:
272
+ return { relationships: [] };
273
+ }
274
+ try {
275
+ const result = await adapter.query(sql);
276
+ const relationships = result.rows.map((row) => ({
277
+ constraint_name: String(row["constraint_name"] ?? ""),
278
+ source_table: String(row["source_table"] ?? ""),
279
+ source_column: String(row["source_column"] ?? ""),
280
+ target_table: String(row["target_table"] ?? ""),
281
+ target_column: String(row["target_column"] ?? "")
282
+ }));
283
+ return { relationships };
284
+ } catch {
285
+ return { relationships: [] };
286
+ }
287
+ }
288
+ async function executeQuery(adapter, sql, limit) {
289
+ const validation = validateReadOnlySQL(sql);
290
+ if (!validation.valid) {
291
+ throw new Error(validation.reason);
292
+ }
293
+ const effectiveLimit = Math.min(limit ?? DEFAULT_ROW_LIMIT, MAX_QUERY_ROW_LIMIT);
294
+ const limitedSql = applyRowLimit(sql, effectiveLimit);
295
+ const result = await adapter.query(limitedSql);
296
+ return {
297
+ query: limitedSql,
298
+ columns: result.columns,
299
+ rows: result.rows,
300
+ row_count: result.row_count,
301
+ truncated: result.row_count >= effectiveLimit
302
+ };
303
+ }
304
+ function quoteIdentifier(name) {
305
+ if (name.includes(".")) {
306
+ return name.split(".").map((part) => `"${part.replace(/"/g, '""')}"`).join(".");
307
+ }
308
+ return `"${name.replace(/"/g, '""')}"`;
309
+ }
310
+ function escapeString(value) {
311
+ return value.replace(/'/g, "''");
312
+ }
313
+
314
+ // src/server.ts
315
+ function detectAdapter(url2) {
316
+ if (url2.startsWith("postgres://") || url2.startsWith("postgresql://")) return "postgres";
317
+ if (url2.startsWith("mysql://")) return "mysql";
318
+ if (url2.startsWith("mssql://") || url2.startsWith("sqlserver://")) return "mssql";
319
+ if (url2.startsWith("clickhouse://")) return "clickhouse";
320
+ if (url2.endsWith(".duckdb") || url2.endsWith(".db") || url2.startsWith("duckdb://")) return "duckdb";
321
+ if (url2.endsWith(".sqlite") || url2.endsWith(".sqlite3") || url2.startsWith("sqlite://")) return "sqlite";
322
+ if (url2.includes("snowflake")) return "snowflake";
323
+ if (url2.includes("bigquery")) return "bigquery";
324
+ if (url2.includes("databricks")) return "databricks";
325
+ throw new Error(`Cannot detect adapter from URL: ${url2}. Please provide a recognizable connection string.`);
326
+ }
327
+ function buildConfig(url2, adapterType) {
328
+ switch (adapterType) {
329
+ case "postgres":
330
+ case "mysql":
331
+ case "mssql":
332
+ return { adapter: adapterType, connection: url2 };
333
+ case "duckdb":
334
+ return { adapter: "duckdb", path: url2.replace(/^duckdb:\/\//, "") };
335
+ case "sqlite":
336
+ return { adapter: "sqlite", path: url2.replace(/^sqlite:\/\//, "") };
337
+ default:
338
+ return { adapter: adapterType, connection: url2 };
339
+ }
340
+ }
341
+ function createDbServer(adapter, adapterType) {
342
+ const server = new McpServer({
343
+ name: "runcontext-db",
344
+ version: "0.5.2"
345
+ });
346
+ server.tool(
347
+ "db_list_schemas",
348
+ "List all schemas (databases/namespaces) available in the connected database",
349
+ {},
350
+ async () => {
351
+ const result = await listSchemas(adapter, adapterType);
352
+ return {
353
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
354
+ };
355
+ }
356
+ );
357
+ server.tool(
358
+ "db_list_tables",
359
+ "List all tables and views with row counts, types, and schema information",
360
+ {},
361
+ async () => {
362
+ const result = await listTables(adapter);
363
+ return {
364
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
365
+ };
366
+ }
367
+ );
368
+ server.tool(
369
+ "db_describe_table",
370
+ "Describe a table's columns including data types, nullability, and primary keys",
371
+ {
372
+ table: z.string().describe('Table name to describe (e.g. "users" or "public.users")')
373
+ },
374
+ async ({ table }) => {
375
+ const result = await describeTable(adapter, table);
376
+ return {
377
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
378
+ };
379
+ }
380
+ );
381
+ server.tool(
382
+ "db_sample_values",
383
+ "Sample rows from a table (max 100 rows). Read-only, with automatic row limit guardrails.",
384
+ {
385
+ table: z.string().describe("Table name to sample from"),
386
+ limit: z.number().int().min(1).max(100).optional().describe("Number of rows to return (default 100, max 100)")
387
+ },
388
+ async ({ table, limit }) => {
389
+ const result = await sampleValues(adapter, table, limit);
390
+ return {
391
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
392
+ };
393
+ }
394
+ );
395
+ server.tool(
396
+ "db_relationships",
397
+ "List foreign key relationships. Optionally filter by a specific table.",
398
+ {
399
+ table: z.string().optional().describe("Optional table name to filter relationships for")
400
+ },
401
+ async ({ table }) => {
402
+ const result = await listRelationships(adapter, adapterType, table);
403
+ return {
404
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
405
+ };
406
+ }
407
+ );
408
+ server.tool(
409
+ "db_query",
410
+ "Execute a read-only SQL query. Only SELECT statements are allowed. Automatically enforces row limits and timeouts.",
411
+ {
412
+ sql: z.string().describe("SQL query to execute (SELECT only)"),
413
+ limit: z.number().int().min(1).max(1e3).optional().describe("Maximum rows to return (default 100, max 1000)")
414
+ },
415
+ async ({ sql, limit }) => {
416
+ try {
417
+ const result = await executeQuery(adapter, sql, limit);
418
+ return {
419
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
420
+ };
421
+ } catch (err) {
422
+ const message = err instanceof Error ? err.message : String(err);
423
+ return {
424
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
425
+ isError: true
426
+ };
427
+ }
428
+ }
429
+ );
430
+ return server;
431
+ }
432
+ async function startServer(url2) {
433
+ const adapterType = detectAdapter(url2);
434
+ const config = buildConfig(url2, adapterType);
435
+ const adapter = await createAdapter(config);
436
+ await adapter.connect();
437
+ const readOnlySql = enforceReadOnly(adapterType);
438
+ if (readOnlySql) {
439
+ try {
440
+ await adapter.query(readOnlySql);
441
+ } catch {
442
+ console.error(`Warning: Could not set read-only mode for ${adapterType}`);
443
+ }
444
+ }
445
+ const timeoutSql = applyTimeout(adapterType, DEFAULT_TIMEOUT_MS);
446
+ if (timeoutSql) {
447
+ try {
448
+ await adapter.query(timeoutSql);
449
+ } catch {
450
+ console.error(`Warning: Could not set query timeout for ${adapterType}`);
451
+ }
452
+ }
453
+ const server = createDbServer(adapter, adapterType);
454
+ const transport = new StdioServerTransport();
455
+ await server.connect(transport);
456
+ process.on("SIGINT", async () => {
457
+ await adapter.disconnect();
458
+ process.exit(0);
459
+ });
460
+ process.on("SIGTERM", async () => {
461
+ await adapter.disconnect();
462
+ process.exit(0);
463
+ });
464
+ return server;
465
+ }
466
+
467
+ // src/index.ts
468
+ var args = process.argv.slice(2);
469
+ function getArg(name) {
470
+ const idx = args.indexOf(name);
471
+ if (idx !== -1 && idx + 1 < args.length) {
472
+ return args[idx + 1];
473
+ }
474
+ const prefix = `${name}=`;
475
+ const match = args.find((a) => a.startsWith(prefix));
476
+ return match?.slice(prefix.length);
477
+ }
478
+ var url = getArg("--url");
479
+ var authKey = getArg("--auth");
480
+ if (!url && !authKey) {
481
+ console.error("Usage: runcontext-db --url <connection-string>");
482
+ console.error(" runcontext-db --auth <provider:key>");
483
+ console.error("");
484
+ console.error("Examples:");
485
+ console.error(" runcontext-db --url postgres://user:pass@localhost:5432/mydb");
486
+ console.error(" runcontext-db --url mysql://user:pass@localhost:3306/mydb");
487
+ console.error(" runcontext-db --url /path/to/database.duckdb");
488
+ console.error(" runcontext-db --url /path/to/database.sqlite");
489
+ console.error(" runcontext-db --auth neon:my-project");
490
+ process.exit(1);
491
+ }
492
+ if (authKey) {
493
+ import("@runcontext/core").then(async ({ createDefaultRegistry, CredentialStore, resolveAuthConnection }) => {
494
+ const registry = createDefaultRegistry();
495
+ const store = new CredentialStore();
496
+ const resolvedUrl = await resolveAuthConnection(authKey, registry, store);
497
+ return startServer(resolvedUrl);
498
+ }).catch((err) => {
499
+ console.error("Failed to resolve auth credentials:", err);
500
+ process.exit(1);
501
+ });
502
+ } else {
503
+ startServer(url).catch((err) => {
504
+ console.error("Failed to start runcontext-db server:", err);
505
+ process.exit(1);
506
+ });
507
+ }
508
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/guardrails.ts","../src/tools.ts","../src/index.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod';\nimport type { DataAdapter, AdapterType } from '@runcontext/core';\nimport { createAdapter } from '@runcontext/core';\n\nimport { enforceReadOnly, applyTimeout, DEFAULT_TIMEOUT_MS } from './guardrails.js';\nimport { listSchemas, listTables, describeTable, sampleValues, listRelationships, executeQuery } from './tools.js';\n\n/**\n * Detect the adapter type from a connection URL.\n */\nexport function detectAdapter(url: string): AdapterType {\n if (url.startsWith('postgres://') || url.startsWith('postgresql://')) return 'postgres';\n if (url.startsWith('mysql://')) return 'mysql';\n if (url.startsWith('mssql://') || url.startsWith('sqlserver://')) return 'mssql';\n if (url.startsWith('clickhouse://')) return 'clickhouse';\n if (url.endsWith('.duckdb') || url.endsWith('.db') || url.startsWith('duckdb://')) return 'duckdb';\n if (url.endsWith('.sqlite') || url.endsWith('.sqlite3') || url.startsWith('sqlite://')) return 'sqlite';\n if (url.includes('snowflake')) return 'snowflake';\n if (url.includes('bigquery')) return 'bigquery';\n if (url.includes('databricks')) return 'databricks';\n\n throw new Error(`Cannot detect adapter from URL: ${url}. Please provide a recognizable connection string.`);\n}\n\n/**\n * Build a DataSourceConfig from a URL string and detected adapter type.\n */\nfunction buildConfig(url: string, adapterType: AdapterType) {\n switch (adapterType) {\n case 'postgres':\n case 'mysql':\n case 'mssql':\n return { adapter: adapterType, connection: url };\n case 'duckdb':\n return { adapter: 'duckdb' as const, path: url.replace(/^duckdb:\\/\\//, '') };\n case 'sqlite':\n return { adapter: 'sqlite' as const, path: url.replace(/^sqlite:\\/\\//, '') };\n default:\n // For other adapters, pass connection as a generic config\n return { adapter: adapterType, connection: url };\n }\n}\n\n/**\n * Create and configure the MCP server with all db tools registered.\n */\nexport function createDbServer(adapter: DataAdapter, adapterType: AdapterType): McpServer {\n const server = new McpServer({\n name: 'runcontext-db',\n version: '0.5.2',\n });\n\n // --- db_list_schemas ---\n server.tool(\n 'db_list_schemas',\n 'List all schemas (databases/namespaces) available in the connected database',\n {},\n async () => {\n const result = await listSchemas(adapter, adapterType);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n },\n );\n\n // --- db_list_tables ---\n server.tool(\n 'db_list_tables',\n 'List all tables and views with row counts, types, and schema information',\n {},\n async () => {\n const result = await listTables(adapter);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n },\n );\n\n // --- db_describe_table ---\n server.tool(\n 'db_describe_table',\n 'Describe a table\\'s columns including data types, nullability, and primary keys',\n {\n table: z.string().describe('Table name to describe (e.g. \"users\" or \"public.users\")'),\n },\n async ({ table }) => {\n const result = await describeTable(adapter, table);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n },\n );\n\n // --- db_sample_values ---\n server.tool(\n 'db_sample_values',\n 'Sample rows from a table (max 100 rows). Read-only, with automatic row limit guardrails.',\n {\n table: z.string().describe('Table name to sample from'),\n limit: z.number().int().min(1).max(100).optional().describe('Number of rows to return (default 100, max 100)'),\n },\n async ({ table, limit }) => {\n const result = await sampleValues(adapter, table, limit);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n },\n );\n\n // --- db_relationships ---\n server.tool(\n 'db_relationships',\n 'List foreign key relationships. Optionally filter by a specific table.',\n {\n table: z.string().optional().describe('Optional table name to filter relationships for'),\n },\n async ({ table }) => {\n const result = await listRelationships(adapter, adapterType, table);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n },\n );\n\n // --- db_query ---\n server.tool(\n 'db_query',\n 'Execute a read-only SQL query. Only SELECT statements are allowed. Automatically enforces row limits and timeouts.',\n {\n sql: z.string().describe('SQL query to execute (SELECT only)'),\n limit: z.number().int().min(1).max(1000).optional().describe('Maximum rows to return (default 100, max 1000)'),\n },\n async ({ sql, limit }) => {\n try {\n const result = await executeQuery(adapter, sql, limit);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: message }) }],\n isError: true,\n };\n }\n },\n );\n\n return server;\n}\n\n/**\n * Connect to the database, apply guardrails, and start the MCP server over stdio.\n */\nexport async function startServer(url: string): Promise<McpServer> {\n const adapterType = detectAdapter(url);\n const config = buildConfig(url, adapterType);\n const adapter = await createAdapter(config);\n\n // Connect to the database\n await adapter.connect();\n\n // Apply read-only guardrail\n const readOnlySql = enforceReadOnly(adapterType);\n if (readOnlySql) {\n try {\n await adapter.query(readOnlySql);\n } catch {\n // Some adapters may not support this; continue\n console.error(`Warning: Could not set read-only mode for ${adapterType}`);\n }\n }\n\n // Apply timeout guardrail\n const timeoutSql = applyTimeout(adapterType, DEFAULT_TIMEOUT_MS);\n if (timeoutSql) {\n try {\n await adapter.query(timeoutSql);\n } catch {\n console.error(`Warning: Could not set query timeout for ${adapterType}`);\n }\n }\n\n const server = createDbServer(adapter, adapterType);\n\n // Connect via stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n // Cleanup on exit\n process.on('SIGINT', async () => {\n await adapter.disconnect();\n process.exit(0);\n });\n\n process.on('SIGTERM', async () => {\n await adapter.disconnect();\n process.exit(0);\n });\n\n return server;\n}\n","import type { AdapterType } from '@runcontext/core';\n\n/**\n * Returns SQL statement(s) to set the session to read-only mode for the given adapter.\n * Returns null if the adapter does not support session-level read-only enforcement.\n */\nexport function enforceReadOnly(adapter: AdapterType): string | null {\n switch (adapter) {\n case 'postgres':\n return 'SET default_transaction_read_only = ON';\n case 'mysql':\n return 'SET SESSION TRANSACTION READ ONLY';\n case 'duckdb':\n return null; // DuckDB: no session-level read-only SQL; use access_mode=read_only in connection config\n case 'mssql':\n return null; // MSSQL: no session-level read-only SQL; use db_query validation instead\n case 'snowflake':\n return null; // Snowflake uses role-based access; no session-level read-only SQL\n case 'bigquery':\n return null; // BigQuery is read-only for queries by default (no DML in standard SQL mode)\n case 'clickhouse':\n return 'SET readonly = 1';\n case 'databricks':\n return null; // Databricks uses role-based access control\n case 'sqlite':\n return 'PRAGMA query_only = ON';\n default:\n return null;\n }\n}\n\n/**\n * Wraps a SQL query with a LIMIT clause to cap the number of rows returned.\n * If the query already contains a LIMIT clause, replaces it with the lower value.\n */\nexport function applyRowLimit(query: string, limit: number): string {\n const trimmed = query.trim().replace(/;+\\s*$/, '');\n\n // Check if query already has a LIMIT clause (case-insensitive)\n const limitMatch = trimmed.match(/\\bLIMIT\\s+(\\d+)\\s*$/i);\n if (limitMatch) {\n const existingLimit = parseInt(limitMatch[1]!, 10);\n const effectiveLimit = Math.min(existingLimit, limit);\n return trimmed.replace(/\\bLIMIT\\s+\\d+\\s*$/i, `LIMIT ${effectiveLimit}`);\n }\n\n return `${trimmed}\\nLIMIT ${limit}`;\n}\n\n/**\n * Returns SQL statement to set query timeout for the given adapter.\n * Returns null if the adapter does not support statement-level timeouts via SQL.\n */\nexport function applyTimeout(adapter: AdapterType, ms: number): string | null {\n switch (adapter) {\n case 'postgres':\n return `SET statement_timeout = ${ms}`;\n case 'mysql':\n // MySQL MAX_EXECUTION_TIME is in milliseconds\n return `SET SESSION MAX_EXECUTION_TIME = ${ms}`;\n case 'duckdb':\n return null; // DuckDB doesn't support statement timeouts via SQL\n case 'mssql':\n return null; // MSSQL uses connection-level timeouts, not SQL\n case 'snowflake':\n return `ALTER SESSION SET STATEMENT_TIMEOUT_IN_SECONDS = ${Math.ceil(ms / 1000)}`;\n case 'bigquery':\n return null; // BigQuery uses API-level timeouts\n case 'clickhouse':\n return `SET max_execution_time = ${Math.ceil(ms / 1000)}`;\n case 'databricks':\n return null; // Databricks uses API-level timeouts\n case 'sqlite':\n return null; // SQLite uses API-level timeouts\n default:\n return null;\n }\n}\n\n/** Default row limit for sample queries. */\nexport const DEFAULT_ROW_LIMIT = 100;\n\n/** Maximum row limit for db_query tool. */\nexport const MAX_QUERY_ROW_LIMIT = 1000;\n\n/** Default query timeout in milliseconds. */\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\n// ---------------------------------------------------------------------------\n// SQL validation for db_query\n// ---------------------------------------------------------------------------\n\nexport interface SQLValidation {\n valid: boolean;\n reason?: string;\n}\n\n/** Disallowed statement prefixes (case-insensitive). */\nconst DDL_KEYWORDS = ['CREATE', 'DROP', 'ALTER', 'TRUNCATE', 'RENAME'];\nconst DML_KEYWORDS = ['INSERT', 'UPDATE', 'DELETE', 'MERGE', 'UPSERT'];\nconst ADMIN_KEYWORDS = [\n 'GRANT', 'REVOKE', 'CALL', 'EXEC', 'COPY', 'LOAD',\n 'VACUUM', 'BEGIN', 'COMMIT', 'ROLLBACK', 'LOCK', 'SET', 'RESET',\n];\nconst ALLOWED_KEYWORDS = ['SELECT', 'WITH', 'EXPLAIN', 'SHOW', 'DESCRIBE', 'VALUES'];\n\n/**\n * Validate that a SQL string is a read-only query safe for execution.\n * Returns `{ valid: true }` if allowed, or `{ valid: false, reason }` if rejected.\n */\nexport function validateReadOnlySQL(sql: string): SQLValidation {\n const trimmed = sql.trim();\n\n if (!trimmed) {\n return { valid: false, reason: 'Query is empty' };\n }\n\n // Reject multi-statement queries (semicolons not at the very end)\n const withoutTrailingSemicolon = trimmed.replace(/;+\\s*$/, '');\n if (withoutTrailingSemicolon.includes(';')) {\n return { valid: false, reason: 'Multi-statement queries are not allowed' };\n }\n\n // Extract the first keyword\n const firstWord = trimmed.split(/\\s+/)[0]!.toUpperCase();\n\n // Check against disallowed keywords\n const allDisallowed = [...DDL_KEYWORDS, ...DML_KEYWORDS, ...ADMIN_KEYWORDS];\n if (allDisallowed.includes(firstWord)) {\n return { valid: false, reason: `Statement type '${firstWord}' is not allowed. Only read-only queries are permitted.` };\n }\n\n // Check against allowed keywords\n if (!ALLOWED_KEYWORDS.includes(firstWord)) {\n return { valid: false, reason: `Statement type '${firstWord}' is not recognized as a read-only query` };\n }\n\n // If query starts with WITH (CTE), check all CTE bodies for mutating keywords.\n // This prevents bypass via e.g. WITH cte AS (DELETE FROM t RETURNING *) SELECT * FROM cte\n if (firstWord === 'WITH') {\n const allMutating = [...DDL_KEYWORDS, ...DML_KEYWORDS, ...ADMIN_KEYWORDS];\n const mutatingPattern = new RegExp(\n `\\\\b(${allMutating.join('|')})\\\\b`,\n 'i',\n );\n if (mutatingPattern.test(withoutTrailingSemicolon)) {\n return {\n valid: false,\n reason: 'CTE body contains a mutating statement. Only read-only queries are permitted.',\n };\n }\n }\n\n return { valid: true };\n}\n","import type { DataAdapter, AdapterType, TableInfo, ColumnInfo, QueryResult } from '@runcontext/core';\nimport { applyRowLimit, DEFAULT_ROW_LIMIT, validateReadOnlySQL, MAX_QUERY_ROW_LIMIT } from './guardrails.js';\n\n// ---------------------------------------------------------------------------\n// db_list_schemas\n// ---------------------------------------------------------------------------\n\nexport interface ListSchemasResult {\n schemas: string[];\n}\n\n/**\n * List all schemas in the database.\n * Falls back to adapter-specific queries when information_schema is unavailable.\n */\nexport async function listSchemas(\n adapter: DataAdapter,\n adapterType: AdapterType,\n): Promise<ListSchemasResult> {\n let sql: string;\n\n switch (adapterType) {\n case 'sqlite':\n case 'duckdb':\n sql = \"SELECT DISTINCT schema_name FROM information_schema.schemata ORDER BY schema_name\";\n break;\n case 'bigquery':\n sql = \"SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA ORDER BY schema_name\";\n break;\n case 'databricks':\n sql = \"SHOW SCHEMAS\";\n break;\n default:\n // postgres, mysql, mssql, snowflake, clickhouse all support information_schema\n sql = \"SELECT schema_name FROM information_schema.schemata ORDER BY schema_name\";\n }\n\n try {\n const result = await adapter.query(sql);\n const schemas = result.rows.map((row) => {\n const firstCol = result.columns[0];\n return String(firstCol ? row[firstCol] : Object.values(row)[0]);\n });\n return { schemas };\n } catch {\n // Fallback: return empty if information_schema not available\n return { schemas: [] };\n }\n}\n\n// ---------------------------------------------------------------------------\n// db_list_tables\n// ---------------------------------------------------------------------------\n\nexport interface ListTablesResult {\n tables: TableInfo[];\n}\n\n/**\n * List all tables with metadata (row counts, types, schema).\n * Delegates to the adapter's listTables().\n */\nexport async function listTables(adapter: DataAdapter): Promise<ListTablesResult> {\n const tables = await adapter.listTables();\n return { tables };\n}\n\n// ---------------------------------------------------------------------------\n// db_describe_table\n// ---------------------------------------------------------------------------\n\nexport interface DescribeTableResult {\n table: string;\n columns: ColumnInfo[];\n}\n\n/**\n * Describe a table's columns including types, nullability, and primary keys.\n * Delegates to the adapter's listColumns().\n */\nexport async function describeTable(\n adapter: DataAdapter,\n table: string,\n): Promise<DescribeTableResult> {\n const columns = await adapter.listColumns(table);\n return { table, columns };\n}\n\n// ---------------------------------------------------------------------------\n// db_sample_values\n// ---------------------------------------------------------------------------\n\nexport interface SampleValuesResult {\n table: string;\n query: string;\n columns: string[];\n rows: Record<string, unknown>[];\n row_count: number;\n}\n\n/**\n * Sample rows from a table with guardrails applied.\n * Maximum of DEFAULT_ROW_LIMIT (100) rows.\n */\nexport async function sampleValues(\n adapter: DataAdapter,\n table: string,\n limit?: number,\n): Promise<SampleValuesResult> {\n const effectiveLimit = Math.min(limit ?? DEFAULT_ROW_LIMIT, DEFAULT_ROW_LIMIT);\n const sql = applyRowLimit(`SELECT * FROM ${quoteIdentifier(table)}`, effectiveLimit);\n const result = await adapter.query(sql);\n return {\n table,\n query: sql,\n columns: result.columns,\n rows: result.rows,\n row_count: result.row_count,\n };\n}\n\n// ---------------------------------------------------------------------------\n// db_relationships\n// ---------------------------------------------------------------------------\n\nexport interface Relationship {\n constraint_name: string;\n source_table: string;\n source_column: string;\n target_table: string;\n target_column: string;\n}\n\nexport interface RelationshipsResult {\n relationships: Relationship[];\n}\n\n/**\n * List foreign key relationships from information_schema.\n * Falls back to adapter-specific queries or empty results.\n */\nexport async function listRelationships(\n adapter: DataAdapter,\n adapterType: AdapterType,\n table?: string,\n): Promise<RelationshipsResult> {\n let sql: string;\n\n switch (adapterType) {\n case 'postgres':\n case 'mysql':\n case 'mssql':\n case 'snowflake': {\n const whereClause = table\n ? `AND kcu.table_name = '${escapeString(table)}'`\n : '';\n sql = `\n SELECT\n kcu.constraint_name,\n kcu.table_name AS source_table,\n kcu.column_name AS source_column,\n ccu.table_name AS target_table,\n ccu.column_name AS target_column\n FROM information_schema.key_column_usage kcu\n JOIN information_schema.constraint_column_usage ccu\n ON kcu.constraint_name = ccu.constraint_name\n JOIN information_schema.table_constraints tc\n ON kcu.constraint_name = tc.constraint_name\n WHERE tc.constraint_type = 'FOREIGN KEY'\n ${whereClause}\n ORDER BY kcu.constraint_name, kcu.ordinal_position\n `;\n break;\n }\n case 'sqlite': {\n if (!table) {\n // SQLite requires table-specific PRAGMA; list all tables then query each\n const tables = await adapter.listTables();\n const allRels: Relationship[] = [];\n for (const t of tables) {\n try {\n const result = await adapter.query(`PRAGMA foreign_key_list(\"${t.name.replace(/\"/g, '\"\"')}\")`);\n for (const row of result.rows) {\n allRels.push({\n constraint_name: `fk_${t.name}_${row['from']}`,\n source_table: t.name,\n source_column: String(row['from']),\n target_table: String(row['table']),\n target_column: String(row['to']),\n });\n }\n } catch {\n // skip tables without FK support\n }\n }\n return { relationships: allRels };\n }\n sql = `PRAGMA foreign_key_list(\"${table.replace(/\"/g, '\"\"')}\")`;\n try {\n const result = await adapter.query(sql);\n const relationships = result.rows.map((row) => ({\n constraint_name: `fk_${table}_${row['from']}`,\n source_table: table,\n source_column: String(row['from']),\n target_table: String(row['table']),\n target_column: String(row['to']),\n }));\n return { relationships };\n } catch {\n return { relationships: [] };\n }\n }\n case 'duckdb': {\n const whereClause = table\n ? `WHERE source_table = '${escapeString(table)}'`\n : '';\n sql = `\n SELECT\n constraint_name,\n source_table,\n source_column,\n target_table,\n target_column\n FROM (\n SELECT\n dc.constraint_name,\n dc.table_name AS source_table,\n unnest(dc.constraint_column_names) AS source_column,\n rc.table_name AS target_table,\n unnest(rc.constraint_column_names) AS target_column\n FROM duckdb_constraints() dc\n JOIN duckdb_constraints() rc\n ON dc.constraint_name IS NOT NULL\n AND dc.constraint_type = 'FOREIGN KEY'\n AND rc.constraint_type = 'PRIMARY KEY'\n ) sub\n ${whereClause}\n ORDER BY constraint_name\n `;\n break;\n }\n default:\n // bigquery, clickhouse, databricks generally don't enforce FKs\n return { relationships: [] };\n }\n\n try {\n const result = await adapter.query(sql);\n const relationships: Relationship[] = result.rows.map((row) => ({\n constraint_name: String(row['constraint_name'] ?? ''),\n source_table: String(row['source_table'] ?? ''),\n source_column: String(row['source_column'] ?? ''),\n target_table: String(row['target_table'] ?? ''),\n target_column: String(row['target_column'] ?? ''),\n }));\n return { relationships };\n } catch {\n return { relationships: [] };\n }\n}\n\n// ---------------------------------------------------------------------------\n// db_query\n// ---------------------------------------------------------------------------\n\nexport interface ExecuteQueryResult {\n query: string;\n columns: string[];\n rows: Record<string, unknown>[];\n row_count: number;\n truncated: boolean;\n}\n\n/**\n * Execute a read-only SQL query with guardrails.\n * Validates the query, applies row limits, and returns the results.\n */\nexport async function executeQuery(\n adapter: DataAdapter,\n sql: string,\n limit?: number,\n): Promise<ExecuteQueryResult> {\n const validation = validateReadOnlySQL(sql);\n if (!validation.valid) {\n throw new Error(validation.reason);\n }\n\n const effectiveLimit = Math.min(limit ?? DEFAULT_ROW_LIMIT, MAX_QUERY_ROW_LIMIT);\n const limitedSql = applyRowLimit(sql, effectiveLimit);\n const result = await adapter.query(limitedSql);\n\n return {\n query: limitedSql,\n columns: result.columns,\n rows: result.rows,\n row_count: result.row_count,\n truncated: result.row_count >= effectiveLimit,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction quoteIdentifier(name: string): string {\n // Simple quoting — handles most cases. Schema.table supported.\n if (name.includes('.')) {\n return name\n .split('.')\n .map((part) => `\"${part.replace(/\"/g, '\"\"')}\"`)\n .join('.');\n }\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\nfunction escapeString(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n","#!/usr/bin/env node\n\nimport { startServer } from './server.js';\n\nconst args = process.argv.slice(2);\n\nfunction getArg(name: string): string | undefined {\n const idx = args.indexOf(name);\n if (idx !== -1 && idx + 1 < args.length) {\n return args[idx + 1];\n }\n // Also support --name=value format\n const prefix = `${name}=`;\n const match = args.find((a) => a.startsWith(prefix));\n return match?.slice(prefix.length);\n}\n\nconst url = getArg('--url');\nconst authKey = getArg('--auth');\n\nif (!url && !authKey) {\n console.error('Usage: runcontext-db --url <connection-string>');\n console.error(' runcontext-db --auth <provider:key>');\n console.error('');\n console.error('Examples:');\n console.error(' runcontext-db --url postgres://user:pass@localhost:5432/mydb');\n console.error(' runcontext-db --url mysql://user:pass@localhost:3306/mydb');\n console.error(' runcontext-db --url /path/to/database.duckdb');\n console.error(' runcontext-db --url /path/to/database.sqlite');\n console.error(' runcontext-db --auth neon:my-project');\n process.exit(1);\n}\n\nif (authKey) {\n import('@runcontext/core').then(async ({ createDefaultRegistry, CredentialStore, resolveAuthConnection }) => {\n const registry = createDefaultRegistry();\n const store = new CredentialStore();\n const resolvedUrl = await resolveAuthConnection(authKey, registry, store);\n return startServer(resolvedUrl);\n }).catch((err) => {\n console.error('Failed to resolve auth credentials:', err);\n process.exit(1);\n });\n} else {\n startServer(url!).catch((err) => {\n console.error('Failed to start runcontext-db server:', err);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":["url"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@runcontext/db",
3
+ "version": "0.5.3",
4
+ "description": "Read-only, guardrailed database connector MCP server. Connect any database to AI agents with automatic safety constraints.",
5
+ "license": "MIT",
6
+ "author": "Eric Kittelson",
7
+ "homepage": "https://github.com/RunContext/runcontext",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/RunContext/runcontext.git",
11
+ "directory": "packages/db"
12
+ },
13
+ "bugs": "https://github.com/RunContext/runcontext/issues",
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "database",
21
+ "read-only",
22
+ "guardrails",
23
+ "ai-agent",
24
+ "runcontext",
25
+ "claude-code",
26
+ "cursor",
27
+ "copilot",
28
+ "data-connector"
29
+ ],
30
+ "type": "module",
31
+ "main": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "bin": {
34
+ "runcontext-db": "./dist/index.js"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.12.0",
41
+ "zod": "^3.24.0",
42
+ "@runcontext/core": "^0.5.3"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^25.3.3",
46
+ "tsup": "^8.4.0",
47
+ "typescript": "^5.7.0",
48
+ "vitest": "^3.2.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "clean": "rm -rf dist"
53
+ }
54
+ }