@pilat/mcp-datalink 1.1.0 → 1.2.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 (75) hide show
  1. package/README.md +19 -97
  2. package/dist/adapters/factory.d.ts +0 -4
  3. package/dist/adapters/factory.d.ts.map +1 -1
  4. package/dist/adapters/factory.js +3 -8
  5. package/dist/adapters/factory.js.map +1 -1
  6. package/dist/adapters/mysql/adapter.d.ts +2 -31
  7. package/dist/adapters/mysql/adapter.d.ts.map +1 -1
  8. package/dist/adapters/mysql/adapter.js +27 -283
  9. package/dist/adapters/mysql/adapter.js.map +1 -1
  10. package/dist/adapters/postgresql/adapter.d.ts +2 -22
  11. package/dist/adapters/postgresql/adapter.d.ts.map +1 -1
  12. package/dist/adapters/postgresql/adapter.js +18 -175
  13. package/dist/adapters/postgresql/adapter.js.map +1 -1
  14. package/dist/adapters/sqlite/adapter.d.ts +3 -25
  15. package/dist/adapters/sqlite/adapter.d.ts.map +1 -1
  16. package/dist/adapters/sqlite/adapter.js +65 -364
  17. package/dist/adapters/sqlite/adapter.js.map +1 -1
  18. package/dist/adapters/sqlite/pragma-check.d.ts +19 -0
  19. package/dist/adapters/sqlite/pragma-check.d.ts.map +1 -0
  20. package/dist/adapters/sqlite/pragma-check.js +25 -0
  21. package/dist/adapters/sqlite/pragma-check.js.map +1 -0
  22. package/dist/adapters/sqlite/url-parser.d.ts +34 -0
  23. package/dist/adapters/sqlite/url-parser.d.ts.map +1 -0
  24. package/dist/adapters/sqlite/url-parser.js +73 -0
  25. package/dist/adapters/sqlite/url-parser.js.map +1 -0
  26. package/dist/adapters/types.d.ts +10 -113
  27. package/dist/adapters/types.d.ts.map +1 -1
  28. package/dist/config/loader.d.ts.map +1 -1
  29. package/dist/config/loader.js.map +1 -1
  30. package/dist/server.d.ts.map +1 -1
  31. package/dist/server.js +31 -20
  32. package/dist/server.js.map +1 -1
  33. package/dist/tools/describe-table.d.ts +5 -6
  34. package/dist/tools/describe-table.d.ts.map +1 -1
  35. package/dist/tools/describe-table.js +65 -9
  36. package/dist/tools/describe-table.js.map +1 -1
  37. package/dist/tools/execute.d.ts +5 -6
  38. package/dist/tools/execute.d.ts.map +1 -1
  39. package/dist/tools/execute.js +13 -11
  40. package/dist/tools/execute.js.map +1 -1
  41. package/dist/tools/explain.d.ts +5 -10
  42. package/dist/tools/explain.d.ts.map +1 -1
  43. package/dist/tools/explain.js +25 -26
  44. package/dist/tools/explain.js.map +1 -1
  45. package/dist/tools/list-databases.d.ts +5 -7
  46. package/dist/tools/list-databases.d.ts.map +1 -1
  47. package/dist/tools/list-databases.js +31 -0
  48. package/dist/tools/list-databases.js.map +1 -1
  49. package/dist/tools/list-tables.d.ts +5 -10
  50. package/dist/tools/list-tables.d.ts.map +1 -1
  51. package/dist/tools/list-tables.js +27 -9
  52. package/dist/tools/list-tables.js.map +1 -1
  53. package/dist/tools/query.d.ts +1 -6
  54. package/dist/tools/query.d.ts.map +1 -1
  55. package/dist/tools/query.js +7 -27
  56. package/dist/tools/query.js.map +1 -1
  57. package/dist/types.d.ts +39 -5
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/utils/formatter.d.ts +7 -4
  60. package/dist/utils/formatter.d.ts.map +1 -1
  61. package/dist/utils/formatter.js +20 -10
  62. package/dist/utils/formatter.js.map +1 -1
  63. package/dist/utils/sql-parser.d.ts +51 -0
  64. package/dist/utils/sql-parser.d.ts.map +1 -0
  65. package/dist/utils/sql-parser.js +310 -0
  66. package/dist/utils/sql-parser.js.map +1 -0
  67. package/dist/utils/truncate.d.ts +4 -13
  68. package/dist/utils/truncate.d.ts.map +1 -1
  69. package/dist/utils/truncate.js +38 -18
  70. package/dist/utils/truncate.js.map +1 -1
  71. package/dist/utils/validation.d.ts +35 -0
  72. package/dist/utils/validation.d.ts.map +1 -0
  73. package/dist/utils/validation.js +89 -0
  74. package/dist/utils/validation.js.map +1 -0
  75. package/package.json +10 -11
@@ -2,9 +2,22 @@
2
2
  * Formatting utilities for query results
3
3
  */
4
4
  /**
5
- * Format a single value for display
6
- * Handles null, dates, objects, and primitive values
5
+ * JSON replacer function that handles BigInt values by converting them to strings.
6
+ * This prevents "TypeError: Do not know how to serialize a BigInt" when using JSON.stringify.
7
7
  */
8
+ export function bigIntReplacer(_key, value) {
9
+ if (typeof value === 'bigint') {
10
+ return value.toString();
11
+ }
12
+ return value;
13
+ }
14
+ /**
15
+ * Safely stringify a value to JSON, handling BigInt values.
16
+ */
17
+ export function safeJsonStringify(value) {
18
+ return JSON.stringify(value, bigIntReplacer);
19
+ }
20
+ /** Format a value for display (handles null, dates, objects, primitives) */
8
21
  export function formatValue(value) {
9
22
  if (value === null || value === undefined) {
10
23
  return 'NULL';
@@ -19,19 +32,16 @@ export function formatValue(value) {
19
32
  return value.toISOString();
20
33
  }
21
34
  if (typeof value === 'object') {
22
- return JSON.stringify(value);
35
+ return safeJsonStringify(value);
23
36
  }
24
37
  return String(value);
25
38
  }
26
- /**
27
- * Escape pipe characters in cell content for Markdown tables
28
- */
29
39
  function escapeForMarkdown(value) {
30
- return value.replace(/\|/g, '\\|');
40
+ return value
41
+ .replace(/\|/g, '\\|')
42
+ .replace(/\r\n|\r|\n/g, ' ')
43
+ .replace(/`/g, '\\`');
31
44
  }
32
- /**
33
- * Format query results as a Markdown table
34
- */
35
45
  export function formatAsMarkdownTable(columns, rows) {
36
46
  if (columns.length === 0) {
37
47
  return '';
@@ -1 +1 @@
1
- {"version":3,"file":"formatter.js","sourceRoot":"","sources":["../../src/utils/formatter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC5D,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;IAC7B,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAiB,EACjB,IAAyB;IAEzB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,aAAa;IACb,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;IACjE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAElD,gBAAgB;IAChB,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;IAChD,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAErD,YAAY;IACZ,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YAC7B,MAAM,KAAK,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5C,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"formatter.js","sourceRoot":"","sources":["../../src/utils/formatter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,KAAc;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC1B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;AAC/C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC5D,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;IAC7B,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACtC,OAAO,KAAK;SACT,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,OAAiB,EACjB,IAAyB;IAEzB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,aAAa;IACb,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;IACjE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAElD,gBAAgB;IAChB,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;IAChD,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAErD,YAAY;IACZ,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YAC7B,MAAM,KAAK,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5C,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Unified SQL Parser Utility
3
+ *
4
+ * Provides SQL parsing and validation for PostgreSQL, MySQL, and SQLite
5
+ * using node-sql-parser library.
6
+ */
7
+ import type { QueryType } from '../types.js';
8
+ /**
9
+ * Supported SQL dialects
10
+ */
11
+ export type SqlDialect = 'PostgreSQL' | 'MySQL' | 'SQLite';
12
+ /**
13
+ * Inject a LIMIT clause into a SELECT query if it doesn't have one
14
+ *
15
+ * @param sql - SQL query
16
+ * @param limit - Maximum number of rows
17
+ * @param dialect - SQL dialect
18
+ * @returns SQL with LIMIT clause
19
+ */
20
+ export declare function injectLimit(sql: string, limit: number, dialect: SqlDialect): string;
21
+ /**
22
+ * Result of parsing a SQL query
23
+ */
24
+ export interface ParseResult {
25
+ type: QueryType;
26
+ hasLimit: boolean;
27
+ isDangerous: boolean;
28
+ dangerousReason?: string;
29
+ sql: string;
30
+ }
31
+ /**
32
+ * Parse and validate a SQL query
33
+ *
34
+ * @param sql - SQL query to parse
35
+ * @param dialect - SQL dialect
36
+ * @returns Parsed query information
37
+ * @throws DbMcpError if query is invalid or contains multiple statements
38
+ */
39
+ export declare function parseQuery(sql: string, dialect: SqlDialect): ParseResult;
40
+ /**
41
+ * Validate that a parsed SQL query is appropriate for a specific tool
42
+ *
43
+ * This function contains the shared validation logic used by all database adapters.
44
+ * Adapters may perform additional adapter-specific checks before calling this.
45
+ *
46
+ * @param parsed - Parsed query result from parseQuery
47
+ * @param tool - Tool being used ('query' for SELECT, 'execute' for INSERT/UPDATE/DELETE)
48
+ * @throws DbMcpError if the query is not appropriate for the specified tool
49
+ */
50
+ export declare function validateQueryForTool(parsed: ParseResult, tool: 'query' | 'execute'): void;
51
+ //# sourceMappingURL=sql-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-parser.d.ts","sourceRoot":"","sources":["../../src/utils/sql-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;AA6J3D;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CAyCnF;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;CACb;AA6CD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,WAAW,CAwExE;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,OAAO,GAAG,SAAS,GACxB,IAAI,CA6CN"}
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Unified SQL Parser Utility
3
+ *
4
+ * Provides SQL parsing and validation for PostgreSQL, MySQL, and SQLite
5
+ * using node-sql-parser library.
6
+ */
7
+ import { Parser } from 'node-sql-parser';
8
+ import { DbMcpError, ErrorCode } from './errors.js';
9
+ /**
10
+ * Prefixes that indicate dangerous SQL operations
11
+ */
12
+ const DANGEROUS_PREFIXES = [
13
+ 'drop',
14
+ 'truncate',
15
+ 'alter',
16
+ 'create',
17
+ 'grant',
18
+ 'revoke',
19
+ 'rename',
20
+ ];
21
+ /**
22
+ * SQLite-specific dangerous operations
23
+ */
24
+ const SQLITE_DANGEROUS_OPERATIONS = [
25
+ 'attach',
26
+ 'detach',
27
+ 'vacuum',
28
+ 'reindex',
29
+ ];
30
+ /**
31
+ * SQLite-specific operations that node-sql-parser cannot parse.
32
+ * These need to be detected before parsing to throw QUERY_BLOCKED instead of INVALID_SQL.
33
+ * Note: ATTACH is parseable by node-sql-parser, so it's excluded from this list.
34
+ */
35
+ const SQLITE_UNPARSEABLE_OPERATIONS = [
36
+ 'detach',
37
+ 'vacuum',
38
+ 'reindex',
39
+ ];
40
+ /**
41
+ * Check if SQL starts with a SQLite-specific dangerous operation
42
+ * that node-sql-parser cannot parse
43
+ *
44
+ * @param sql - SQL query to check
45
+ * @returns Object with isDangerous flag and optional reason
46
+ */
47
+ function checkSqliteUnparseableDangerousPrefix(sql) {
48
+ const normalized = sql.trim().toLowerCase();
49
+ for (const op of SQLITE_UNPARSEABLE_OPERATIONS) {
50
+ if (normalized.startsWith(op)) {
51
+ return {
52
+ isDangerous: true,
53
+ reason: `${op.toUpperCase()} statements are not allowed`,
54
+ };
55
+ }
56
+ }
57
+ return { isDangerous: false };
58
+ }
59
+ /**
60
+ * Create a parser instance for the specified dialect
61
+ */
62
+ function createParser() {
63
+ return new Parser();
64
+ }
65
+ /**
66
+ * Parse SQL and return AST
67
+ *
68
+ * @param sql - SQL query to parse
69
+ * @param dialect - SQL dialect (PostgreSQL, MySQL, SQLite)
70
+ * @returns Parsed AST
71
+ * @throws DbMcpError if parsing fails or SQL contains dangerous operations
72
+ */
73
+ function parseSQL(sql, dialect) {
74
+ // For SQLite, check for dangerous unparseable operations before parsing
75
+ // since node-sql-parser may not recognize these statements
76
+ if (dialect === 'SQLite') {
77
+ const { isDangerous, reason } = checkSqliteUnparseableDangerousPrefix(sql);
78
+ if (isDangerous) {
79
+ throw new DbMcpError(ErrorCode.QUERY_BLOCKED, reason ?? 'Statement is not allowed', { sql });
80
+ }
81
+ }
82
+ const parser = createParser();
83
+ try {
84
+ return parser.astify(sql, { database: dialect });
85
+ }
86
+ catch (error) {
87
+ const message = error instanceof Error ? error.message : 'Unknown parse error';
88
+ throw new DbMcpError(ErrorCode.INVALID_SQL, 'Failed to parse SQL: ' + message, { sql });
89
+ }
90
+ }
91
+ /**
92
+ * Convert AST back to SQL string
93
+ *
94
+ * @param ast - AST to convert
95
+ * @param dialect - SQL dialect
96
+ * @returns SQL string
97
+ */
98
+ function astToSQL(ast, dialect) {
99
+ const parser = createParser();
100
+ return parser.sqlify(ast, { database: dialect });
101
+ }
102
+ /**
103
+ * Check if a SQL statement type is dangerous
104
+ *
105
+ * @param stmtType - Statement type from AST
106
+ * @param dialect - SQL dialect
107
+ * @returns Object with isDangerous flag and optional reason
108
+ */
109
+ function checkDangerousType(stmtType, dialect) {
110
+ const normalizedType = stmtType.toLowerCase();
111
+ // Check common dangerous prefixes
112
+ for (const prefix of DANGEROUS_PREFIXES) {
113
+ if (normalizedType === prefix || normalizedType.startsWith(prefix)) {
114
+ return {
115
+ isDangerous: true,
116
+ reason: `${prefix.toUpperCase()} statements are not allowed`,
117
+ };
118
+ }
119
+ }
120
+ // Check SQLite-specific dangerous operations
121
+ if (dialect === 'SQLite') {
122
+ for (const op of SQLITE_DANGEROUS_OPERATIONS) {
123
+ if (normalizedType === op || normalizedType.startsWith(op)) {
124
+ return {
125
+ isDangerous: true,
126
+ reason: `${op.toUpperCase()} statements are not allowed`,
127
+ };
128
+ }
129
+ }
130
+ }
131
+ return { isDangerous: false };
132
+ }
133
+ /**
134
+ * Regular expression to check for LIMIT clause in SQL
135
+ * Matches LIMIT followed by a number, handling whitespace
136
+ */
137
+ const LIMIT_PATTERN = /\bLIMIT\s+\d+/i;
138
+ /**
139
+ * Inject a LIMIT clause into a SELECT query if it doesn't have one
140
+ *
141
+ * @param sql - SQL query
142
+ * @param limit - Maximum number of rows
143
+ * @param dialect - SQL dialect
144
+ * @returns SQL with LIMIT clause
145
+ */
146
+ export function injectLimit(sql, limit, dialect) {
147
+ const ast = parseSQL(sql, dialect);
148
+ const statements = Array.isArray(ast) ? ast : [ast];
149
+ if (statements.length !== 1) {
150
+ return sql;
151
+ }
152
+ const stmt = statements[0];
153
+ if (stmt.type !== 'select') {
154
+ return sql;
155
+ }
156
+ const selectStmt = stmt;
157
+ // Check if already has limit (value array must have entries)
158
+ if (selectStmt.limit && Array.isArray(selectStmt.limit.value) && selectStmt.limit.value.length > 0) {
159
+ return sql;
160
+ }
161
+ // Add limit clause
162
+ selectStmt.limit = {
163
+ seperator: '',
164
+ value: [{ type: 'number', value: limit }],
165
+ };
166
+ const result = astToSQL(stmt, dialect);
167
+ // Verify that LIMIT was actually added to the result
168
+ if (!LIMIT_PATTERN.test(result)) {
169
+ // LIMIT injection failed - fall back to original SQL
170
+ // This is a safety measure in case astToSQL doesn't properly serialize the limit
171
+ console.warn(`[mcp-datalink] LIMIT injection verification failed: astToSQL did not produce LIMIT clause. ` +
172
+ `Returning original SQL. Dialect: ${dialect}`);
173
+ return sql;
174
+ }
175
+ return result;
176
+ }
177
+ /**
178
+ * Allowed query types for the execute tool
179
+ */
180
+ const EXECUTE_ALLOWED_TYPES = ['insert', 'update', 'delete'];
181
+ /**
182
+ * Data-modifying statement types that should not be in CTEs for query tool
183
+ */
184
+ const DATA_MODIFYING_TYPES = ['insert', 'update', 'delete'];
185
+ /**
186
+ * Check if a CTE contains data-modifying statements
187
+ *
188
+ * PostgreSQL supports data-modifying CTEs like:
189
+ * WITH inserted AS (INSERT ... RETURNING *) SELECT * FROM inserted
190
+ *
191
+ * These modify data even though the outer query is a SELECT.
192
+ *
193
+ * @param ast - Parsed AST
194
+ * @returns Object with hasDataModifyingCTE flag and optional reason
195
+ */
196
+ function checkDataModifyingCTE(ast) {
197
+ const withClause = ast.with;
198
+ if (!Array.isArray(withClause)) {
199
+ return { hasDataModifyingCTE: false };
200
+ }
201
+ for (const cte of withClause) {
202
+ const stmtType = cte.stmt?.type?.toLowerCase();
203
+ if (stmtType && DATA_MODIFYING_TYPES.includes(stmtType)) {
204
+ return {
205
+ hasDataModifyingCTE: true,
206
+ reason: `CTE contains data-modifying ${stmtType.toUpperCase()} statement`,
207
+ };
208
+ }
209
+ }
210
+ return { hasDataModifyingCTE: false };
211
+ }
212
+ /**
213
+ * Parse and validate a SQL query
214
+ *
215
+ * @param sql - SQL query to parse
216
+ * @param dialect - SQL dialect
217
+ * @returns Parsed query information
218
+ * @throws DbMcpError if query is invalid or contains multiple statements
219
+ */
220
+ export function parseQuery(sql, dialect) {
221
+ const ast = parseSQL(sql, dialect);
222
+ const statements = Array.isArray(ast) ? ast : [ast];
223
+ // Filter out null/undefined entries
224
+ const validStatements = statements.filter((s) => s !== null && s !== undefined);
225
+ if (validStatements.length === 0) {
226
+ throw new DbMcpError(ErrorCode.INVALID_SQL, 'No valid SQL statement found', { sql });
227
+ }
228
+ if (validStatements.length > 1) {
229
+ throw new DbMcpError(ErrorCode.MULTI_STATEMENT, 'Multiple SQL statements are not allowed. Please provide a single statement.', { sql, statementCount: validStatements.length });
230
+ }
231
+ const stmt = validStatements[0];
232
+ const stmtType = (stmt.type ?? '').toLowerCase();
233
+ // Determine query type
234
+ let queryType;
235
+ switch (stmtType) {
236
+ case 'select':
237
+ queryType = 'select';
238
+ break;
239
+ case 'insert':
240
+ queryType = 'insert';
241
+ break;
242
+ case 'update':
243
+ queryType = 'update';
244
+ break;
245
+ case 'delete':
246
+ queryType = 'delete';
247
+ break;
248
+ default:
249
+ queryType = 'other';
250
+ }
251
+ // Check for dangerous operations
252
+ let { isDangerous, reason } = checkDangerousType(stmtType, dialect);
253
+ // Check for data-modifying CTEs (e.g. WITH x AS (INSERT ...) SELECT ...)
254
+ if (!isDangerous) {
255
+ const cteCheck = checkDataModifyingCTE(stmt);
256
+ if (cteCheck.hasDataModifyingCTE) {
257
+ isDangerous = true;
258
+ reason = cteCheck.reason;
259
+ }
260
+ }
261
+ // Check for LIMIT clause
262
+ let hasLimit = false;
263
+ if (queryType === 'select') {
264
+ const selectStmt = stmt;
265
+ // node-sql-parser returns limit: { value: [] } even when no LIMIT is present
266
+ hasLimit = !!(selectStmt.limit && Array.isArray(selectStmt.limit.value) && selectStmt.limit.value.length > 0);
267
+ }
268
+ return {
269
+ type: queryType,
270
+ hasLimit,
271
+ isDangerous,
272
+ dangerousReason: reason,
273
+ sql,
274
+ };
275
+ }
276
+ /**
277
+ * Validate that a parsed SQL query is appropriate for a specific tool
278
+ *
279
+ * This function contains the shared validation logic used by all database adapters.
280
+ * Adapters may perform additional adapter-specific checks before calling this.
281
+ *
282
+ * @param parsed - Parsed query result from parseQuery
283
+ * @param tool - Tool being used ('query' for SELECT, 'execute' for INSERT/UPDATE/DELETE)
284
+ * @throws DbMcpError if the query is not appropriate for the specified tool
285
+ */
286
+ export function validateQueryForTool(parsed, tool) {
287
+ if (tool === 'query') {
288
+ if (parsed.type !== 'select') {
289
+ throw new DbMcpError(ErrorCode.QUERY_BLOCKED, 'The query tool only accepts SELECT statements. Use the execute tool for ' +
290
+ parsed.type.toUpperCase() +
291
+ ' statements.', { sql: parsed.sql, queryType: parsed.type, tool });
292
+ }
293
+ // Block data-modifying CTEs in query tool
294
+ if (parsed.isDangerous) {
295
+ throw new DbMcpError(ErrorCode.QUERY_BLOCKED, parsed.dangerousReason ?? 'This operation is not allowed', { sql: parsed.sql, queryType: parsed.type, tool });
296
+ }
297
+ }
298
+ else if (tool === 'execute') {
299
+ if (parsed.type === 'select') {
300
+ throw new DbMcpError(ErrorCode.QUERY_BLOCKED, 'The execute tool does not accept SELECT statements. Use the query tool instead.', { sql: parsed.sql, queryType: parsed.type, tool });
301
+ }
302
+ if (parsed.isDangerous) {
303
+ throw new DbMcpError(ErrorCode.QUERY_BLOCKED, parsed.dangerousReason ?? 'This operation is not allowed', { sql: parsed.sql, queryType: parsed.type, tool });
304
+ }
305
+ if (!EXECUTE_ALLOWED_TYPES.includes(parsed.type)) {
306
+ throw new DbMcpError(ErrorCode.QUERY_BLOCKED, 'The execute tool only accepts INSERT, UPDATE, or DELETE statements.', { sql: parsed.sql, queryType: parsed.type, tool });
307
+ }
308
+ }
309
+ }
310
+ //# sourceMappingURL=sql-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-parser.js","sourceRoot":"","sources":["../../src/utils/sql-parser.ts"],"names":[],"mappings":"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"}
@@ -8,30 +8,21 @@ export interface TruncationInfo {
8
8
  returned?: number;
9
9
  hint?: string;
10
10
  }
11
- /**
12
- * Truncate array of rows to maxRows limit
13
- */
14
11
  export declare function truncateRows(rows: unknown[][], maxRows: number, totalAvailable?: number): {
15
12
  rows: unknown[][];
16
13
  info: TruncationInfo;
17
14
  };
18
- /**
19
- * Truncate individual cell value to maxLength
20
- */
21
15
  export declare function truncateCell(value: unknown, maxLength: number): {
22
16
  value: string;
23
17
  truncated: boolean;
24
18
  originalLength?: number;
25
19
  };
26
- /**
27
- * Truncate columns array to maxColumns limit
28
- */
29
20
  export declare function truncateColumns(columns: string[], maxColumns: number): {
30
21
  columns: string[];
31
22
  truncated: boolean;
32
23
  };
33
- /**
34
- * Check if total response size exceeds maxSize
35
- */
36
- export declare function checkTotalSize(data: string, maxSize: number): TruncationInfo;
24
+ export declare function checkTotalSize(data: string, maxSize: number): {
25
+ data: string;
26
+ info: TruncationInfo;
27
+ };
37
28
  //# sourceMappingURL=truncate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../../src/utils/truncate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,SAAS,GAAG,eAAe,GAAG,cAAc,GAAG,YAAY,CAAC;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,OAAO,EAAE,EAAE,EACjB,OAAO,EAAE,MAAM,EACf,cAAc,CAAC,EAAE,MAAM,GACtB;IAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAoB7C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CA8BhE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,EAAE,MAAM,GACjB;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAY3C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,cAAc,CAYhB"}
1
+ {"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../../src/utils/truncate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,SAAS,GAAG,eAAe,GAAG,cAAc,GAAG,YAAY,CAAC;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,OAAO,EAAE,EAAE,EACjB,OAAO,EAAE,MAAM,EACf,cAAc,CAAC,EAAE,MAAM,GACtB;IAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAoB7C;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CA4ChE;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,EAAE,MAAM,GACjB;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAY3C;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAmCxC"}
@@ -1,9 +1,7 @@
1
1
  /**
2
2
  * Truncation utilities for limiting MCP response sizes
3
3
  */
4
- /**
5
- * Truncate array of rows to maxRows limit
6
- */
4
+ import { safeJsonStringify } from './formatter.js';
7
5
  export function truncateRows(rows, maxRows, totalAvailable) {
8
6
  const available = totalAvailable ?? rows.length;
9
7
  if (rows.length <= maxRows) {
@@ -23,9 +21,6 @@ export function truncateRows(rows, maxRows, totalAvailable) {
23
21
  },
24
22
  };
25
23
  }
26
- /**
27
- * Truncate individual cell value to maxLength
28
- */
29
24
  export function truncateCell(value, maxLength) {
30
25
  // Convert value to string
31
26
  let stringValue;
@@ -42,7 +37,7 @@ export function truncateCell(value, maxLength) {
42
37
  stringValue = value.toISOString();
43
38
  }
44
39
  else if (typeof value === 'object') {
45
- stringValue = JSON.stringify(value);
40
+ stringValue = safeJsonStringify(value);
46
41
  }
47
42
  else {
48
43
  stringValue = String(value);
@@ -53,15 +48,23 @@ export function truncateCell(value, maxLength) {
53
48
  truncated: false,
54
49
  };
55
50
  }
51
+ // Account for ellipsis length (3 chars) when truncating
52
+ const ellipsis = '...';
53
+ // Edge case: if maxLength is too small for ellipsis, just truncate without it
54
+ if (maxLength < ellipsis.length) {
55
+ return {
56
+ value: stringValue.slice(0, maxLength),
57
+ truncated: true,
58
+ originalLength: stringValue.length,
59
+ };
60
+ }
61
+ const truncateAt = maxLength - ellipsis.length;
56
62
  return {
57
- value: stringValue.slice(0, maxLength) + '...',
63
+ value: stringValue.slice(0, truncateAt) + ellipsis,
58
64
  truncated: true,
59
65
  originalLength: stringValue.length,
60
66
  };
61
67
  }
62
- /**
63
- * Truncate columns array to maxColumns limit
64
- */
65
68
  export function truncateColumns(columns, maxColumns) {
66
69
  if (columns.length <= maxColumns) {
67
70
  return {
@@ -74,18 +77,35 @@ export function truncateColumns(columns, maxColumns) {
74
77
  truncated: true,
75
78
  };
76
79
  }
77
- /**
78
- * Check if total response size exceeds maxSize
79
- */
80
80
  export function checkTotalSize(data, maxSize) {
81
81
  const byteLength = Buffer.byteLength(data, 'utf8');
82
82
  if (byteLength <= maxSize) {
83
- return { truncated: false };
83
+ return { data, info: { truncated: false } };
84
84
  }
85
+ // Truncate to fit within maxSize bytes
86
+ // We need to be careful with multi-byte UTF-8 characters
87
+ // Binary search for the right character position
88
+ let low = 0;
89
+ let high = data.length;
90
+ while (low < high) {
91
+ const mid = Math.floor((low + high + 1) / 2);
92
+ const slice = data.slice(0, mid);
93
+ const sliceBytes = Buffer.byteLength(slice, 'utf8');
94
+ if (sliceBytes <= maxSize) {
95
+ low = mid;
96
+ }
97
+ else {
98
+ high = mid - 1;
99
+ }
100
+ }
101
+ const truncatedData = data.slice(0, low);
85
102
  return {
86
- truncated: true,
87
- truncationReason: 'maxTotalSize',
88
- hint: 'Use LIMIT/OFFSET or WHERE clause to paginate',
103
+ data: truncatedData,
104
+ info: {
105
+ truncated: true,
106
+ truncationReason: 'maxTotalSize',
107
+ hint: 'Use LIMIT/OFFSET or WHERE clause to paginate',
108
+ },
89
109
  };
90
110
  }
91
111
  //# sourceMappingURL=truncate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"truncate.js","sourceRoot":"","sources":["../../src/utils/truncate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAiB,EACjB,OAAe,EACf,cAAuB;IAEvB,MAAM,SAAS,GAAG,cAAc,IAAI,IAAI,CAAC,MAAM,CAAC;IAEhD,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,EAAE,CAAC;QAC3B,OAAO;YACL,IAAI;YACJ,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;SAC3B,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC;QAC5B,IAAI,EAAE;YACJ,SAAS,EAAE,IAAI;YACf,gBAAgB,EAAE,SAAS;YAC3B,cAAc,EAAE,SAAS;YACzB,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,8CAA8C;SACrD;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAc,EACd,SAAiB;IAEjB,0BAA0B;IAC1B,IAAI,WAAmB,CAAC;IAExB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,WAAW,GAAG,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACrC,WAAW,GAAG,KAAK,CAAC;IACtB,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QACnE,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;SAAM,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;QACjC,WAAW,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACpC,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACrC,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;SAAM,CAAC;QACN,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;QACpC,OAAO;YACL,KAAK,EAAE,WAAW;YAClB,SAAS,EAAE,KAAK;SACjB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,KAAK;QAC9C,SAAS,EAAE,IAAI;QACf,cAAc,EAAE,WAAW,CAAC,MAAM;KACnC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAiB,EACjB,UAAkB;IAElB,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;QACjC,OAAO;YACL,OAAO;YACP,SAAS,EAAE,KAAK;SACjB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;QACrC,SAAS,EAAE,IAAI;KAChB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,OAAe;IAEf,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAEnD,IAAI,UAAU,IAAI,OAAO,EAAE,CAAC;QAC1B,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED,OAAO;QACL,SAAS,EAAE,IAAI;QACf,gBAAgB,EAAE,cAAc;QAChC,IAAI,EAAE,8CAA8C;KACrD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"truncate.js","sourceRoot":"","sources":["../../src/utils/truncate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Validation utilities for database operations
3
+ */
4
+ import type { Config, DatabaseConfig } from '../types.js';
5
+ /**
6
+ * Get a validated database configuration, throwing if not found
7
+ *
8
+ * @param database - The database name to look up
9
+ * @param config - The application configuration
10
+ * @returns The database configuration
11
+ * @throws DbMcpError with DATABASE_NOT_FOUND if database not configured
12
+ */
13
+ export declare function getValidatedDatabase(database: string, config: Config): DatabaseConfig;
14
+ /**
15
+ * Count placeholder parameters in SQL query
16
+ *
17
+ * Counts PostgreSQL-style placeholders ($1, $2, etc.) in SQL.
18
+ * Ignores placeholders inside string literals (single or double quoted).
19
+ *
20
+ * Note: LLM always uses $N style placeholders. For MySQL/SQLite,
21
+ * adapters convert $N to ? via convertPlaceholders() after validation.
22
+ *
23
+ * @param sql - SQL query to analyze
24
+ * @returns Number of unique placeholders found
25
+ */
26
+ export declare function countPlaceholders(sql: string): number;
27
+ /**
28
+ * Validate that the number of parameters matches the placeholder count in SQL
29
+ *
30
+ * @param sql - SQL query with $1, $2, etc. placeholders
31
+ * @param params - Array of parameter values
32
+ * @throws DbMcpError with INVALID_SQL if counts don't match
33
+ */
34
+ export declare function validateParamCount(sql: string, params: unknown[]): void;
35
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAI1D;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAUrF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA8CrD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAWvE"}