@pilat/mcp-datalink 1.0.3 → 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 (82) hide show
  1. package/README.md +30 -387
  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 +10 -7
  29. package/dist/config/loader.d.ts.map +1 -1
  30. package/dist/config/loader.js +20 -97
  31. package/dist/config/loader.js.map +1 -1
  32. package/dist/index.d.ts +0 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +10 -9
  35. package/dist/index.js.map +1 -1
  36. package/dist/server.d.ts.map +1 -1
  37. package/dist/server.js +113 -40
  38. package/dist/server.js.map +1 -1
  39. package/dist/tools/describe-table.d.ts +5 -6
  40. package/dist/tools/describe-table.d.ts.map +1 -1
  41. package/dist/tools/describe-table.js +65 -9
  42. package/dist/tools/describe-table.js.map +1 -1
  43. package/dist/tools/execute.d.ts +5 -6
  44. package/dist/tools/execute.d.ts.map +1 -1
  45. package/dist/tools/execute.js +13 -11
  46. package/dist/tools/execute.js.map +1 -1
  47. package/dist/tools/explain.d.ts +5 -10
  48. package/dist/tools/explain.d.ts.map +1 -1
  49. package/dist/tools/explain.js +25 -26
  50. package/dist/tools/explain.js.map +1 -1
  51. package/dist/tools/list-databases.d.ts +5 -7
  52. package/dist/tools/list-databases.d.ts.map +1 -1
  53. package/dist/tools/list-databases.js +31 -0
  54. package/dist/tools/list-databases.js.map +1 -1
  55. package/dist/tools/list-tables.d.ts +5 -10
  56. package/dist/tools/list-tables.d.ts.map +1 -1
  57. package/dist/tools/list-tables.js +27 -9
  58. package/dist/tools/list-tables.js.map +1 -1
  59. package/dist/tools/query.d.ts +1 -6
  60. package/dist/tools/query.d.ts.map +1 -1
  61. package/dist/tools/query.js +7 -27
  62. package/dist/tools/query.js.map +1 -1
  63. package/dist/types.d.ts +39 -12
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/utils/formatter.d.ts +7 -4
  66. package/dist/utils/formatter.d.ts.map +1 -1
  67. package/dist/utils/formatter.js +20 -10
  68. package/dist/utils/formatter.js.map +1 -1
  69. package/dist/utils/sql-parser.d.ts +51 -0
  70. package/dist/utils/sql-parser.d.ts.map +1 -0
  71. package/dist/utils/sql-parser.js +310 -0
  72. package/dist/utils/sql-parser.js.map +1 -0
  73. package/dist/utils/truncate.d.ts +4 -13
  74. package/dist/utils/truncate.d.ts.map +1 -1
  75. package/dist/utils/truncate.js +38 -18
  76. package/dist/utils/truncate.js.map +1 -1
  77. package/dist/utils/validation.d.ts +35 -0
  78. package/dist/utils/validation.d.ts.map +1 -0
  79. package/dist/utils/validation.js +89 -0
  80. package/dist/utils/validation.js.map +1 -0
  81. package/package.json +11 -13
  82. package/databases.example.json +0 -18
package/dist/server.js CHANGED
@@ -5,14 +5,28 @@
5
5
  */
6
6
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
7
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
9
- import { listDatabases } from './tools/list-databases.js';
10
- import { listTables } from './tools/list-tables.js';
11
- import { describeTable } from './tools/describe-table.js';
12
- import { query, formatQueryResultAsMarkdown } from './tools/query.js';
13
- import { execute } from './tools/execute.js';
14
- import { explain } from './tools/explain.js';
8
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
9
+ import { describeTable, formatTableDescriptionAsMarkdown } from './tools/describe-table.js';
10
+ import { execute, formatExecuteResultAsMarkdown } from './tools/execute.js';
11
+ import { explain, formatExplainResultAsMarkdown } from './tools/explain.js';
12
+ import { formatListDatabasesResultAsMarkdown, listDatabases } from './tools/list-databases.js';
13
+ import { formatListTablesResultAsMarkdown, listTables } from './tools/list-tables.js';
14
+ import { formatQueryResultAsMarkdown, query } from './tools/query.js';
15
15
  import { DbMcpError } from './utils/errors.js';
16
+ /**
17
+ * Server instructions for LLM agents.
18
+ * Provides workflow guidance for proper tool usage sequence.
19
+ */
20
+ const SERVER_INSTRUCTIONS = `Database Query Workflow:
21
+ 1. Discovery: Use list_databases to find available database connections.
22
+ 2. Exploration: Use list_tables to discover tables in a database schema.
23
+ 3. Schema Validation: Always call describe_table before writing queries to verify exact column names, types, and constraints. Never assume or guess column names.
24
+ 4. Query Execution: Use query for SELECT statements, execute for INSERT/UPDATE/DELETE.
25
+ 5. Security: All queries must use parameterized placeholders ($1, $2, ...). Never interpolate values directly into SQL strings.
26
+ 6. Performance: Use explain to analyze query execution plans for optimization.
27
+
28
+ The recommended sequence is: list_databases → list_tables → describe_table → query/execute.
29
+ Skipping describe_table often leads to errors due to incorrect column names or types.`;
16
30
  /**
17
31
  * Creates an MCP server configured with all db-mcp tools.
18
32
  *
@@ -27,13 +41,13 @@ export function createServer(config) {
27
41
  capabilities: {
28
42
  tools: {},
29
43
  },
44
+ instructions: SERVER_INSTRUCTIONS,
30
45
  });
31
- // Register list tools handler
32
46
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
33
47
  tools: [
34
48
  {
35
49
  name: 'list_databases',
36
- description: 'List all configured database connections',
50
+ description: 'List all configured database connections available in this server.',
37
51
  inputSchema: {
38
52
  type: 'object',
39
53
  properties: {},
@@ -42,97 +56,149 @@ export function createServer(config) {
42
56
  },
43
57
  {
44
58
  name: 'list_tables',
45
- description: 'List tables in a database schema',
59
+ description: 'List all tables and views in a database schema with row counts and metadata.',
46
60
  inputSchema: {
47
61
  type: 'object',
48
62
  properties: {
49
- database: { type: 'string', description: 'Database connection name' },
50
- schema: { type: 'string', description: 'Schema name (default: public)' },
63
+ database: {
64
+ type: 'string',
65
+ description: 'Database connection name as returned by list_databases',
66
+ },
67
+ schema: {
68
+ type: 'string',
69
+ description: 'Schema name to list tables from (default: public for PostgreSQL, none for SQLite)',
70
+ },
51
71
  },
52
72
  required: ['database'],
53
73
  },
54
74
  },
55
75
  {
56
76
  name: 'describe_table',
57
- description: 'Get table structure including columns, indexes, and foreign keys',
77
+ description: 'Retrieve detailed table structure: column names, data types, nullability, defaults, indexes, and foreign key relationships.',
58
78
  inputSchema: {
59
79
  type: 'object',
60
80
  properties: {
61
- database: { type: 'string', description: 'Database connection name' },
62
- table: { type: 'string', description: 'Table name' },
63
- schema: { type: 'string', description: 'Schema name (default: public)' },
81
+ database: {
82
+ type: 'string',
83
+ description: 'Database connection name as returned by list_databases',
84
+ },
85
+ table: {
86
+ type: 'string',
87
+ description: 'Table name as returned by list_tables',
88
+ },
89
+ schema: {
90
+ type: 'string',
91
+ description: 'Schema name (default: public for PostgreSQL)',
92
+ },
64
93
  },
65
94
  required: ['database', 'table'],
66
95
  },
67
96
  },
68
97
  {
69
98
  name: 'query',
70
- description: 'Execute a read-only SELECT query. Use $1, $2, ... placeholders for parameters.',
99
+ description: 'Execute a read-only SELECT query and return results as structured data.',
71
100
  inputSchema: {
72
101
  type: 'object',
73
102
  properties: {
74
- database: { type: 'string', description: 'Database connection name' },
75
- sql: { type: 'string', description: 'SQL SELECT query. Use $1, $2, ... for parameter placeholders' },
76
- params: { type: 'array', description: 'Parameter values in order ($1, $2, ...). Always use params instead of interpolating values into SQL.' },
103
+ database: {
104
+ type: 'string',
105
+ description: 'Database connection name as returned by list_databases',
106
+ },
107
+ sql: {
108
+ type: 'string',
109
+ description: 'SELECT query using parameterized placeholders ($1, $2, ...) for values. ' +
110
+ 'Example: SELECT * FROM users WHERE status = $1 AND created_at > $2',
111
+ },
112
+ params: {
113
+ type: 'array',
114
+ description: 'Parameter values corresponding to placeholders in order. ' +
115
+ 'Example: ["active", "2024-01-01"] for $1 and $2',
116
+ },
77
117
  },
78
118
  required: ['database', 'sql'],
79
119
  },
80
120
  },
81
121
  {
82
122
  name: 'execute',
83
- description: 'Execute INSERT/UPDATE/DELETE query. Use $1, $2, ... placeholders for parameters.',
123
+ description: 'Execute INSERT, UPDATE, or DELETE query and return affected row count.',
84
124
  inputSchema: {
85
125
  type: 'object',
86
126
  properties: {
87
- database: { type: 'string', description: 'Database connection name' },
88
- sql: { type: 'string', description: 'SQL INSERT/UPDATE/DELETE query. Use $1, $2, ... for parameter placeholders' },
89
- params: { type: 'array', description: 'Parameter values in order ($1, $2, ...). Always use params instead of interpolating values into SQL.' },
127
+ database: {
128
+ type: 'string',
129
+ description: 'Database connection name as returned by list_databases',
130
+ },
131
+ sql: {
132
+ type: 'string',
133
+ description: 'INSERT/UPDATE/DELETE query using parameterized placeholders ($1, $2, ...). ' +
134
+ 'Example: UPDATE users SET status = $1 WHERE id = $2',
135
+ },
136
+ params: {
137
+ type: 'array',
138
+ description: 'Parameter values corresponding to placeholders in order. ' +
139
+ 'Example: ["inactive", 123] for $1 and $2',
140
+ },
90
141
  },
91
142
  required: ['database', 'sql'],
92
143
  },
93
144
  },
94
145
  {
95
146
  name: 'explain',
96
- description: 'Show query execution plan',
147
+ description: 'Analyze query execution plan to understand performance characteristics and identify optimization opportunities.',
97
148
  inputSchema: {
98
149
  type: 'object',
99
150
  properties: {
100
- database: { type: 'string', description: 'Database connection name' },
101
- sql: { type: 'string', description: 'SQL query to explain' },
102
- analyze: { type: 'boolean', description: 'Run EXPLAIN ANALYZE (default: false)' },
151
+ database: {
152
+ type: 'string',
153
+ description: 'Database connection name as returned by list_databases',
154
+ },
155
+ sql: {
156
+ type: 'string',
157
+ description: 'SQL query to analyze (typically a SELECT statement)',
158
+ },
159
+ analyze: {
160
+ type: 'boolean',
161
+ description: 'If true, actually execute the query to get real timing statistics (EXPLAIN ANALYZE). ' +
162
+ 'If false, show estimated plan only. Default: false',
163
+ },
103
164
  },
104
165
  required: ['database', 'sql'],
105
166
  },
106
167
  },
107
168
  ],
108
169
  }));
109
- // Register call tool handler
110
170
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
111
171
  const { name, arguments: args } = request.params;
112
172
  try {
113
173
  switch (name) {
114
- case 'list_databases':
174
+ case 'list_databases': {
175
+ const result = listDatabases(config);
115
176
  return {
116
- content: [{ type: 'text', text: JSON.stringify(listDatabases(config), null, 2) }],
177
+ content: [{ type: 'text', text: formatListDatabasesResultAsMarkdown(result, config) }],
117
178
  };
118
- case 'list_tables':
179
+ }
180
+ case 'list_tables': {
181
+ const result = await listTables(args, config);
119
182
  return {
120
183
  content: [
121
184
  {
122
185
  type: 'text',
123
- text: JSON.stringify(await listTables(args, config), null, 2),
186
+ text: formatListTablesResultAsMarkdown(result),
124
187
  },
125
188
  ],
126
189
  };
127
- case 'describe_table':
190
+ }
191
+ case 'describe_table': {
192
+ const result = await describeTable(args, config);
128
193
  return {
129
194
  content: [
130
195
  {
131
196
  type: 'text',
132
- text: JSON.stringify(await describeTable(args, config), null, 2),
197
+ text: formatTableDescriptionAsMarkdown(result),
133
198
  },
134
199
  ],
135
200
  };
201
+ }
136
202
  case 'query': {
137
203
  const result = await query(args, config);
138
204
  return {
@@ -144,24 +210,28 @@ export function createServer(config) {
144
210
  ],
145
211
  };
146
212
  }
147
- case 'execute':
213
+ case 'execute': {
214
+ const result = await execute(args, config);
148
215
  return {
149
216
  content: [
150
217
  {
151
218
  type: 'text',
152
- text: JSON.stringify(await execute(args, config), null, 2),
219
+ text: formatExecuteResultAsMarkdown(result),
153
220
  },
154
221
  ],
155
222
  };
156
- case 'explain':
223
+ }
224
+ case 'explain': {
225
+ const result = await explain(args, config);
157
226
  return {
158
227
  content: [
159
228
  {
160
229
  type: 'text',
161
- text: JSON.stringify(await explain(args, config), null, 2),
230
+ text: formatExplainResultAsMarkdown(result),
162
231
  },
163
232
  ],
164
233
  };
234
+ }
165
235
  default:
166
236
  throw new Error(`Unknown tool: ${name}`);
167
237
  }
@@ -173,7 +243,10 @@ export function createServer(config) {
173
243
  isError: true,
174
244
  };
175
245
  }
176
- throw error;
246
+ if (error instanceof Error) {
247
+ throw error;
248
+ }
249
+ throw new Error(String(error));
177
250
  }
178
251
  });
179
252
  return server;
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAWnG,OAAO,EAAE,aAAa,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAC5F,OAAO,EAAE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,mCAAmC,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/F,OAAO,EAAE,gCAAgC,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,2BAA2B,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C;;;GAGG;AACH,MAAM,mBAAmB,GAAG;;;;;;;;;sFAS0D,CAAC;AAEvF;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;QACE,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,OAAO;KACjB,EACD;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;SACV;QACD,YAAY,EAAE,mBAAmB;KAClC,CACF,CAAC;IAEF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE;YACL;gBACE,IAAI,EAAE,gBAAgB;gBACtB,WAAW,EACT,oEAAoE;gBACtE,WAAW,EAAE;oBACX,IAAI,EAAE,QAAiB;oBACvB,UAAU,EAAE,EAAE;oBACd,QAAQ,EAAE,EAAE;iBACb;aACF;YACD;gBACE,IAAI,EAAE,aAAa;gBACnB,WAAW,EACT,8EAA8E;gBAChF,WAAW,EAAE;oBACX,IAAI,EAAE,QAAiB;oBACvB,UAAU,EAAE;wBACV,QAAQ,EAAE;4BACR,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,wDAAwD;yBACtE;wBACD,MAAM,EAAE;4BACN,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,mFAAmF;yBACjG;qBACF;oBACD,QAAQ,EAAE,CAAC,UAAU,CAAC;iBACvB;aACF;YACD;gBACE,IAAI,EAAE,gBAAgB;gBACtB,WAAW,EACT,6HAA6H;gBAC/H,WAAW,EAAE;oBACX,IAAI,EAAE,QAAiB;oBACvB,UAAU,EAAE;wBACV,QAAQ,EAAE;4BACR,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,wDAAwD;yBACtE;wBACD,KAAK,EAAE;4BACL,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,uCAAuC;yBACrD;wBACD,MAAM,EAAE;4BACN,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,8CAA8C;yBAC5D;qBACF;oBACD,QAAQ,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC;iBAChC;aACF;YACD;gBACE,IAAI,EAAE,OAAO;gBACb,WAAW,EACT,yEAAyE;gBAC3E,WAAW,EAAE;oBACX,IAAI,EAAE,QAAiB;oBACvB,UAAU,EAAE;wBACV,QAAQ,EAAE;4BACR,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,wDAAwD;yBACtE;wBACD,GAAG,EAAE;4BACH,IAAI,EAAE,QAAQ;4BACd,WAAW,EACT,0EAA0E;gCAC1E,oEAAoE;yBACvE;wBACD,MAAM,EAAE;4BACN,IAAI,EAAE,OAAO;4BACb,WAAW,EACT,2DAA2D;gCAC3D,iDAAiD;yBACpD;qBACF;oBACD,QAAQ,EAAE,CAAC,UAAU,EAAE,KAAK,CAAC;iBAC9B;aACF;YACD;gBACE,IAAI,EAAE,SAAS;gBACf,WAAW,EACT,wEAAwE;gBAC1E,WAAW,EAAE;oBACX,IAAI,EAAE,QAAiB;oBACvB,UAAU,EAAE;wBACV,QAAQ,EAAE;4BACR,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,wDAAwD;yBACtE;wBACD,GAAG,EAAE;4BACH,IAAI,EAAE,QAAQ;4BACd,WAAW,EACT,6EAA6E;gCAC7E,qDAAqD;yBACxD;wBACD,MAAM,EAAE;4BACN,IAAI,EAAE,OAAO;4BACb,WAAW,EACT,2DAA2D;gCAC3D,0CAA0C;yBAC7C;qBACF;oBACD,QAAQ,EAAE,CAAC,UAAU,EAAE,KAAK,CAAC;iBAC9B;aACF;YACD;gBACE,IAAI,EAAE,SAAS;gBACf,WAAW,EACT,iHAAiH;gBACnH,WAAW,EAAE;oBACX,IAAI,EAAE,QAAiB;oBACvB,UAAU,EAAE;wBACV,QAAQ,EAAE;4BACR,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,wDAAwD;yBACtE;wBACD,GAAG,EAAE;4BACH,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,qDAAqD;yBACnE;wBACD,OAAO,EAAE;4BACP,IAAI,EAAE,SAAS;4BACf,WAAW,EACT,uFAAuF;gCACvF,oDAAoD;yBACvD;qBACF;oBACD,QAAQ,EAAE,CAAC,UAAU,EAAE,KAAK,CAAC;iBAC9B;aACF;SACF;KACF,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QAEjD,IAAI,CAAC;YACH,QAAQ,IAAI,EAAE,CAAC;gBACb,KAAK,gBAAgB,CAAC,CAAC,CAAC;oBACtB,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;oBACrC,OAAO;wBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mCAAmC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;qBACvF,CAAC;gBACJ,CAAC;gBACD,KAAK,aAAa,CAAC,CAAC,CAAC;oBACnB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAmC,EAAE,MAAM,CAAC,CAAC;oBAC7E,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,gCAAgC,CAAC,MAAM,CAAC;6BAC/C;yBACF;qBACF,CAAC;gBACJ,CAAC;gBACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;oBACtB,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAsC,EAAE,MAAM,CAAC,CAAC;oBACnF,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,gCAAgC,CAAC,MAAM,CAAC;6BAC/C;yBACF;qBACF,CAAC;gBACJ,CAAC;gBACD,KAAK,OAAO,CAAC,CAAC,CAAC;oBACb,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAA8B,EAAE,MAAM,CAAC,CAAC;oBACnE,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,2BAA2B,CAAC,MAAM,CAAC;6BAC1C;yBACF;qBACF,CAAC;gBACJ,CAAC;gBACD,KAAK,SAAS,CAAC,CAAC,CAAC;oBACf,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAgC,EAAE,MAAM,CAAC,CAAC;oBACvE,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,6BAA6B,CAAC,MAAM,CAAC;6BAC5C;yBACF;qBACF,CAAC;gBACJ,CAAC;gBACD,KAAK,SAAS,CAAC,CAAC,CAAC;oBACf,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAgC,EAAE,MAAM,CAAC,CAAC;oBACvE,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,6BAA6B,CAAC,MAAM,CAAC;6BAC5C;yBACF;qBACF,CAAC;gBACJ,CAAC;gBACD;oBACE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;gBAChC,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;oBAC1E,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YACD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;gBAC3B,MAAM,KAAK,CAAC;YACd,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACjC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAc;IAC5C,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * describe_table tool - Returns table structure including columns, indexes, and foreign keys
3
3
  */
4
- import type { Config, TableDescription } from '../types.js';
5
- export interface DescribeTableParams {
6
- database: string;
7
- table: string;
8
- schema?: string;
9
- }
4
+ import type { Config, DescribeTableParams, TableDescription } from '../types.js';
5
+ /**
6
+ * Format TableDescription as Markdown with sections for columns, indexes, and foreign keys
7
+ */
8
+ export declare function formatTableDescriptionAsMarkdown(result: TableDescription): string;
10
9
  export declare function describeTable(params: DescribeTableParams, config: Config): Promise<TableDescription>;
11
10
  //# sourceMappingURL=describe-table.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"describe-table.d.ts","sourceRoot":"","sources":["../../src/tools/describe-table.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAI5D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,gBAAgB,CAAC,CAwB3B"}
1
+ {"version":3,"file":"describe-table.d.ts","sourceRoot":"","sources":["../../src/tools/describe-table.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAKjF;;GAEG;AACH,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CA6DjF;AAGD,wBAAsB,aAAa,CACjC,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,gBAAgB,CAAC,CAU3B"}
@@ -2,19 +2,75 @@
2
2
  * describe_table tool - Returns table structure including columns, indexes, and foreign keys
3
3
  */
4
4
  import { createAdapter } from '../adapters/index.js';
5
- import { DbMcpError, ErrorCode } from '../utils/errors.js';
5
+ import { formatAsMarkdownTable } from '../utils/formatter.js';
6
+ /**
7
+ * Format TableDescription as Markdown with sections for columns, indexes, and foreign keys
8
+ */
9
+ export function formatTableDescriptionAsMarkdown(result) {
10
+ const parts = [];
11
+ // Header
12
+ parts.push(`## ${result.schema}.${result.table}`);
13
+ parts.push('');
14
+ // Columns section
15
+ parts.push('### Columns');
16
+ if (result.columns.length > 0) {
17
+ const columnHeaders = ['name', 'type', 'nullable', 'default', 'primary_key'];
18
+ const columnRows = result.columns.map((col) => [
19
+ col.name,
20
+ col.type,
21
+ col.nullable ? 'YES' : 'NO',
22
+ col.default ?? 'NULL',
23
+ col.primaryKey ? 'YES' : 'NO',
24
+ ]);
25
+ parts.push(formatAsMarkdownTable(columnHeaders, columnRows));
26
+ }
27
+ else {
28
+ parts.push('_No columns_');
29
+ }
30
+ parts.push('');
31
+ // Indexes section
32
+ parts.push('### Indexes');
33
+ if (result.indexes.length > 0) {
34
+ const indexHeaders = ['name', 'columns', 'unique', 'primary'];
35
+ const indexRows = result.indexes.map((idx) => [
36
+ idx.name,
37
+ idx.columns.join(', '),
38
+ idx.unique ? 'YES' : 'NO',
39
+ idx.primary ? 'YES' : 'NO',
40
+ ]);
41
+ parts.push(formatAsMarkdownTable(indexHeaders, indexRows));
42
+ }
43
+ else {
44
+ parts.push('_No indexes_');
45
+ }
46
+ parts.push('');
47
+ // Foreign keys section
48
+ parts.push('### Foreign Keys');
49
+ if (result.foreignKeys.length > 0) {
50
+ const fkHeaders = ['column', 'references_table', 'references_column'];
51
+ const fkRows = result.foreignKeys.map((fk) => [
52
+ fk.column,
53
+ fk.references.table,
54
+ fk.references.column,
55
+ ]);
56
+ parts.push(formatAsMarkdownTable(fkHeaders, fkRows));
57
+ }
58
+ else {
59
+ parts.push('_No foreign keys_');
60
+ }
61
+ // Truncation info
62
+ if (result.truncated && result.truncationReason) {
63
+ parts.push('');
64
+ parts.push(`**Note:** Results truncated (${result.truncationReason})`);
65
+ }
66
+ return parts.join('\n');
67
+ }
68
+ import { getValidatedDatabase } from '../utils/validation.js';
6
69
  export async function describeTable(params, config) {
7
70
  const { table, database } = params;
8
71
  const { maxColumns, maxIndexes } = config.defaults;
9
- // Get database config
10
- const dbConfig = config.databases[database];
11
- if (!dbConfig) {
12
- throw new DbMcpError(ErrorCode.DATABASE_NOT_FOUND, `Database "${database}" not found in configuration`, { database, available: Object.keys(config.databases) });
13
- }
14
- // Create adapter for this database
72
+ const dbConfig = getValidatedDatabase(database, config);
15
73
  const adapter = createAdapter(dbConfig, config.defaults);
16
- // Use provided schema or adapter's default schema
17
- // PostgreSQL: "public", MySQL: database name, SQLite: "main"
18
74
  const schema = params.schema ?? adapter.getDefaultSchema();
19
75
  return adapter.withConnection(async (conn) => {
20
76
  return conn.describeTable(table, schema, { maxColumns, maxIndexes });
@@ -1 +1 @@
1
- {"version":3,"file":"describe-table.js","sourceRoot":"","sources":["../../src/tools/describe-table.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAQ3D,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAA2B,EAC3B,MAAc;IAEd,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IACnC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;IAEnD,sBAAsB;IACtB,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,UAAU,CAClB,SAAS,CAAC,kBAAkB,EAC5B,aAAa,QAAQ,8BAA8B,EACnD,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CACvD,CAAC;IACJ,CAAC;IAED,mCAAmC;IACnC,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEzD,kDAAkD;IAClD,6DAA6D;IAC7D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAE3D,OAAO,OAAO,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAC3C,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"describe-table.js","sourceRoot":"","sources":["../../src/tools/describe-table.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Execute tool for INSERT/UPDATE/DELETE statements
3
3
  */
4
- import type { Config, ExecuteResult } from '../types.js';
5
- export interface ExecuteParams {
6
- database: string;
7
- sql: string;
8
- params?: unknown[];
9
- }
4
+ import type { Config, ExecuteParams, ExecuteResult } from '../types.js';
5
+ /**
6
+ * Format ExecuteResult as Markdown
7
+ */
8
+ export declare function formatExecuteResultAsMarkdown(result: ExecuteResult): string;
10
9
  /**
11
10
  * Execute an INSERT/UPDATE/DELETE statement
12
11
  *
@@ -1 +1 @@
1
- {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/tools/execute.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIzD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,aAAa,CAAC,CA+CxB"}
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/tools/execute.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIxE;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAQ3E;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,aAAa,CAAC,CA+BxB"}
@@ -2,7 +2,18 @@
2
2
  * Execute tool for INSERT/UPDATE/DELETE statements
3
3
  */
4
4
  import { createAdapter } from '../adapters/index.js';
5
+ /**
6
+ * Format ExecuteResult as Markdown
7
+ */
8
+ export function formatExecuteResultAsMarkdown(result) {
9
+ const parts = [];
10
+ parts.push(`**Command:** ${result.command}`);
11
+ parts.push(`**Rows affected:** ${result.rowsAffected}`);
12
+ parts.push(`**Execution time:** ${result.executionTime}ms`);
13
+ return parts.join('\n');
14
+ }
5
15
  import { DbMcpError, ErrorCode } from '../utils/errors.js';
16
+ import { getValidatedDatabase, validateParamCount } from '../utils/validation.js';
6
17
  /**
7
18
  * Execute an INSERT/UPDATE/DELETE statement
8
19
  *
@@ -16,24 +27,15 @@ import { DbMcpError, ErrorCode } from '../utils/errors.js';
16
27
  */
17
28
  export async function execute(params, config) {
18
29
  const startTime = Date.now();
19
- // Get database config
20
- const dbConfig = config.databases[params.database];
21
- if (!dbConfig) {
22
- throw new DbMcpError(ErrorCode.DATABASE_NOT_FOUND, `Database "${params.database}" not found in configuration`, { database: params.database, available: Object.keys(config.databases) });
23
- }
24
- // Step 1: Check if database is readonly
30
+ const dbConfig = getValidatedDatabase(params.database, config);
25
31
  if (dbConfig.readonly) {
26
32
  throw new DbMcpError(ErrorCode.READONLY_VIOLATION, `Database "${params.database}" is configured as readonly. INSERT/UPDATE/DELETE operations are not allowed.`, { database: params.database });
27
33
  }
28
- // Create adapter for this database
29
34
  const adapter = createAdapter(dbConfig, config.defaults);
30
- // Step 2: Validate query type - blocks SELECT and dangerous DDL
31
35
  adapter.validateQueryForTool(params.sql, 'execute');
32
- // Step 3: Get the command type from parsed query
36
+ validateParamCount(params.sql, params.params ?? []);
33
37
  const parsed = adapter.parseQuery(params.sql);
34
38
  const command = parsed.type.toUpperCase();
35
- // Step 4: Convert placeholders for non-PostgreSQL dialects
36
- // PostgreSQL uses $1, $2; MySQL/SQLite use ?
37
39
  const sql = adapter.convertPlaceholders(params.sql);
38
40
  const result = await adapter.withConnection(async (conn) => {
39
41
  return conn.query(sql, params.params ?? []);
@@ -1 +1 @@
1
- {"version":3,"file":"execute.js","sourceRoot":"","sources":["../../src/tools/execute.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAQ3D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,MAAqB,EACrB,MAAc;IAEd,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,sBAAsB;IACtB,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,UAAU,CAClB,SAAS,CAAC,kBAAkB,EAC5B,aAAa,MAAM,CAAC,QAAQ,8BAA8B,EAC1D,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CACxE,CAAC;IACJ,CAAC;IAED,wCAAwC;IACxC,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,IAAI,UAAU,CAClB,SAAS,CAAC,kBAAkB,EAC5B,aAAa,MAAM,CAAC,QAAQ,+EAA+E,EAC3G,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAC9B,CAAC;IACJ,CAAC;IAED,mCAAmC;IACnC,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEzD,gEAAgE;IAChE,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAEpD,iDAAiD;IACjD,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IAE1C,2DAA2D;IAC3D,6CAA6C;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAEpD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACzD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAE7C,OAAO;QACL,OAAO;QACP,YAAY,EAAE,MAAM,CAAC,QAAQ;QAC7B,aAAa;KACd,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"execute.js","sourceRoot":"","sources":["../../src/tools/execute.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;GAEG;AACH,MAAM,UAAU,6BAA6B,CAAC,MAAqB;IACjE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,gBAAgB,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7C,KAAK,CAAC,IAAI,CAAC,sBAAsB,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;IACxD,KAAK,CAAC,IAAI,CAAC,uBAAuB,MAAM,CAAC,aAAa,IAAI,CAAC,CAAC;IAE5D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AACD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAElF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,MAAqB,EACrB,MAAc;IAEd,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAE/D,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,IAAI,UAAU,CAClB,SAAS,CAAC,kBAAkB,EAC5B,aAAa,MAAM,CAAC,QAAQ,+EAA+E,EAC3G,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAC9B,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzD,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACpD,kBAAkB,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAEpD,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAEpD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACzD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAE7C,OAAO;QACL,OAAO;QACP,YAAY,EAAE,MAAM,CAAC,QAAQ;QAC7B,aAAa;KACd,CAAC;AACJ,CAAC"}
@@ -1,16 +1,11 @@
1
1
  /**
2
2
  * Explain tool for showing query execution plans
3
3
  */
4
- import type { Config } from '../types.js';
5
- export interface ExplainParams {
6
- database: string;
7
- sql: string;
8
- analyze?: boolean;
9
- }
10
- export interface ExplainResult {
11
- plan: string;
12
- executionTime: number;
13
- }
4
+ import type { Config, ExplainParams, ExplainResult } from '../types.js';
5
+ /**
6
+ * Format ExplainResult as Markdown
7
+ */
8
+ export declare function formatExplainResultAsMarkdown(result: ExplainResult): string;
14
9
  /**
15
10
  * Get the execution plan for a SQL query
16
11
  *
@@ -1 +1 @@
1
- {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/tools/explain.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAI1C,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,aAAa,CAAC,CAmFxB"}
1
+ {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/tools/explain.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIxE;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAU3E;AAID;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,aAAa,CAAC,CA+DxB"}
@@ -2,7 +2,20 @@
2
2
  * Explain tool for showing query execution plans
3
3
  */
4
4
  import { createAdapter } from '../adapters/index.js';
5
+ /**
6
+ * Format ExplainResult as Markdown
7
+ */
8
+ export function formatExplainResultAsMarkdown(result) {
9
+ const parts = [];
10
+ parts.push('```');
11
+ parts.push(result.plan);
12
+ parts.push('```');
13
+ parts.push('');
14
+ parts.push(`**Execution time:** ${result.executionTime}ms`);
15
+ return parts.join('\n');
16
+ }
5
17
  import { DbMcpError, ErrorCode } from '../utils/errors.js';
18
+ import { getValidatedDatabase } from '../utils/validation.js';
6
19
  /**
7
20
  * Get the execution plan for a SQL query
8
21
  *
@@ -15,34 +28,19 @@ import { DbMcpError, ErrorCode } from '../utils/errors.js';
15
28
  */
16
29
  export async function explain(params, config) {
17
30
  const startTime = Date.now();
18
- // Get database config
19
- const dbConfig = config.databases[params.database];
20
- if (!dbConfig) {
21
- throw new DbMcpError(ErrorCode.DATABASE_NOT_FOUND, `Database "${params.database}" not found in configuration`, { database: params.database, available: Object.keys(config.databases) });
22
- }
23
- // Create adapter for this database
31
+ const dbConfig = getValidatedDatabase(params.database, config);
24
32
  const adapter = createAdapter(dbConfig, config.defaults);
25
- // Step 1: Parse query and check for dangerous operations
26
33
  const parsed = adapter.parseQuery(params.sql);
27
34
  if (parsed.isDangerous) {
28
35
  throw new DbMcpError(ErrorCode.QUERY_BLOCKED, parsed.dangerousReason ?? 'This operation is not allowed', { sql: params.sql, queryType: parsed.type });
29
36
  }
30
37
  const result = await adapter.withConnection(async (conn) => {
31
- // Build EXPLAIN query using adapter-specific prefix
32
38
  const explainPrefix = adapter.getExplainPrefix(params.analyze ?? false);
33
- // SQLite and MySQL don't need READ ONLY transactions for EXPLAIN:
34
- // - SQLite: Doesn't support READ ONLY transactions
35
- // - MySQL: EXPLAIN doesn't execute the query, and READ ONLY tx blocks
36
- // EXPLAIN on UPDATE/DELETE even though they're safe
37
- //
38
- // Defense in depth is still provided by:
39
- // 1. SQL parser blocking dangerous operations (DROP, TRUNCATE, etc.)
40
- // 2. EXPLAIN not executing the query (MySQL) / EXPLAIN QUERY PLAN (SQLite)
41
- // 3. Database adapter opening in readonly mode when configured
39
+ // SQLite/MySQL: EXPLAIN doesn't execute the query, no transaction needed
40
+ // PostgreSQL: EXPLAIN ANALYZE runs the query, wrap in READ ONLY transaction
42
41
  if (adapter.type === 'sqlite' || adapter.type === 'mysql') {
43
42
  return await conn.query(explainPrefix + params.sql);
44
43
  }
45
- // PostgreSQL: Use READ ONLY transaction since EXPLAIN ANALYZE executes the query
46
44
  await conn.execute('BEGIN TRANSACTION READ ONLY');
47
45
  try {
48
46
  const queryResult = await conn.query(explainPrefix + params.sql);
@@ -51,18 +49,19 @@ export async function explain(params, config) {
51
49
  }
52
50
  catch (error) {
53
51
  await conn.execute('ROLLBACK');
54
- throw error;
52
+ if (error instanceof Error) {
53
+ throw error;
54
+ }
55
+ throw new Error(String(error));
55
56
  }
56
57
  });
57
- // Step 3: Format plan as text (join rows with newlines)
58
- // Different databases have different EXPLAIN output formats:
59
- // - PostgreSQL: Single column with plan text
60
- // - MySQL: Multiple columns (id, select_type, table, type, key, rows, Extra, etc.)
61
- // - SQLite: 4 columns (id, parent, notused, detail)
62
58
  const planLines = result.rows.map((row) => {
59
+ if (row.length === 0) {
60
+ return '';
61
+ }
63
62
  if (adapter.type === 'sqlite' && row.length >= 4) {
64
63
  // SQLite: extract the 'detail' column (4th column, index 3)
65
- return String(row[3]);
64
+ return String(row[3] ?? '');
66
65
  }
67
66
  if (adapter.type === 'mysql' && row.length > 1) {
68
67
  // MySQL: Join all columns with tabs for tabular output
@@ -70,7 +69,7 @@ export async function explain(params, config) {
70
69
  return row.map((col) => (col === null ? 'NULL' : String(col))).join('\t');
71
70
  }
72
71
  // PostgreSQL: first column is the plan line
73
- return row[0];
72
+ return String(row[0] ?? '');
74
73
  });
75
74
  const executionTime = Date.now() - startTime;
76
75
  return {