@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.
- package/README.md +30 -387
- package/dist/adapters/factory.d.ts +0 -4
- package/dist/adapters/factory.d.ts.map +1 -1
- package/dist/adapters/factory.js +3 -8
- package/dist/adapters/factory.js.map +1 -1
- package/dist/adapters/mysql/adapter.d.ts +2 -31
- package/dist/adapters/mysql/adapter.d.ts.map +1 -1
- package/dist/adapters/mysql/adapter.js +27 -283
- package/dist/adapters/mysql/adapter.js.map +1 -1
- package/dist/adapters/postgresql/adapter.d.ts +2 -22
- package/dist/adapters/postgresql/adapter.d.ts.map +1 -1
- package/dist/adapters/postgresql/adapter.js +18 -175
- package/dist/adapters/postgresql/adapter.js.map +1 -1
- package/dist/adapters/sqlite/adapter.d.ts +3 -25
- package/dist/adapters/sqlite/adapter.d.ts.map +1 -1
- package/dist/adapters/sqlite/adapter.js +65 -364
- package/dist/adapters/sqlite/adapter.js.map +1 -1
- package/dist/adapters/sqlite/pragma-check.d.ts +19 -0
- package/dist/adapters/sqlite/pragma-check.d.ts.map +1 -0
- package/dist/adapters/sqlite/pragma-check.js +25 -0
- package/dist/adapters/sqlite/pragma-check.js.map +1 -0
- package/dist/adapters/sqlite/url-parser.d.ts +34 -0
- package/dist/adapters/sqlite/url-parser.d.ts.map +1 -0
- package/dist/adapters/sqlite/url-parser.js +73 -0
- package/dist/adapters/sqlite/url-parser.js.map +1 -0
- package/dist/adapters/types.d.ts +10 -113
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/config/loader.d.ts +10 -7
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +20 -97
- package/dist/config/loader.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -9
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +113 -40
- package/dist/server.js.map +1 -1
- package/dist/tools/describe-table.d.ts +5 -6
- package/dist/tools/describe-table.d.ts.map +1 -1
- package/dist/tools/describe-table.js +65 -9
- package/dist/tools/describe-table.js.map +1 -1
- package/dist/tools/execute.d.ts +5 -6
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/execute.js +13 -11
- package/dist/tools/execute.js.map +1 -1
- package/dist/tools/explain.d.ts +5 -10
- package/dist/tools/explain.d.ts.map +1 -1
- package/dist/tools/explain.js +25 -26
- package/dist/tools/explain.js.map +1 -1
- package/dist/tools/list-databases.d.ts +5 -7
- package/dist/tools/list-databases.d.ts.map +1 -1
- package/dist/tools/list-databases.js +31 -0
- package/dist/tools/list-databases.js.map +1 -1
- package/dist/tools/list-tables.d.ts +5 -10
- package/dist/tools/list-tables.d.ts.map +1 -1
- package/dist/tools/list-tables.js +27 -9
- package/dist/tools/list-tables.js.map +1 -1
- package/dist/tools/query.d.ts +1 -6
- package/dist/tools/query.d.ts.map +1 -1
- package/dist/tools/query.js +7 -27
- package/dist/tools/query.js.map +1 -1
- package/dist/types.d.ts +39 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/formatter.d.ts +7 -4
- package/dist/utils/formatter.d.ts.map +1 -1
- package/dist/utils/formatter.js +20 -10
- package/dist/utils/formatter.js.map +1 -1
- package/dist/utils/sql-parser.d.ts +51 -0
- package/dist/utils/sql-parser.d.ts.map +1 -0
- package/dist/utils/sql-parser.js +310 -0
- package/dist/utils/sql-parser.js.map +1 -0
- package/dist/utils/truncate.d.ts +4 -13
- package/dist/utils/truncate.d.ts.map +1 -1
- package/dist/utils/truncate.js +38 -18
- package/dist/utils/truncate.js.map +1 -1
- package/dist/utils/validation.d.ts +35 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +89 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +11 -13
- 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
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
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: {
|
|
50
|
-
|
|
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: '
|
|
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: {
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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: {
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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: {
|
|
88
|
-
|
|
89
|
-
|
|
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: '
|
|
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: {
|
|
101
|
-
|
|
102
|
-
|
|
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:
|
|
177
|
+
content: [{ type: 'text', text: formatListDatabasesResultAsMarkdown(result, config) }],
|
|
117
178
|
};
|
|
118
|
-
|
|
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:
|
|
186
|
+
text: formatListTablesResultAsMarkdown(result),
|
|
124
187
|
},
|
|
125
188
|
],
|
|
126
189
|
};
|
|
127
|
-
|
|
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:
|
|
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:
|
|
219
|
+
text: formatExecuteResultAsMarkdown(result),
|
|
153
220
|
},
|
|
154
221
|
],
|
|
155
222
|
};
|
|
156
|
-
|
|
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:
|
|
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
|
-
|
|
246
|
+
if (error instanceof Error) {
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
throw new Error(String(error));
|
|
177
250
|
}
|
|
178
251
|
});
|
|
179
252
|
return server;
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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;
|
|
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 {
|
|
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
|
-
|
|
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;
|
|
1
|
+
{"version":3,"file":"describe-table.js","sourceRoot":"","sources":["../../src/tools/describe-table.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/execute.d.ts
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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,
|
|
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"}
|
package/dist/tools/execute.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|
|
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"}
|
package/dist/tools/explain.d.ts
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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,
|
|
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"}
|
package/dist/tools/explain.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
34
|
-
//
|
|
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
|
-
|
|
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 {
|