@berthojoris/mcp-mysql-server 1.42.1 → 1.43.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/CHANGELOG.md +17 -0
- package/DOCUMENTATIONS.md +44 -9
- package/README.md +4 -4
- package/dist/config/featureConfig.js +14 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +27 -0
- package/dist/mcp-server.js +133 -5
- package/dist/security/securityLayer.js +9 -0
- package/dist/tools/analysisTools.d.ts +57 -0
- package/dist/tools/analysisTools.js +544 -13
- package/dist/tools/queryTools.d.ts +2 -1
- package/dist/tools/queryTools.js +2 -1
- package/dist/tools/toolArgumentValidation.js +113 -1
- package/dist/tools/utilityTools.js +11 -2
- package/manifest.json +154 -2
- package/package.json +1 -1
|
@@ -95,6 +95,111 @@ const validateSeedFromTemplateArgs = (args) => {
|
|
|
95
95
|
validateRowsPerTable(args.rows_per_table, errors);
|
|
96
96
|
return errors.length ? { valid: false, errors } : { valid: true };
|
|
97
97
|
};
|
|
98
|
+
const validateDiscoveryKeyword = (value, key, errors) => {
|
|
99
|
+
if (!value || typeof value !== "string" || !value.trim()) {
|
|
100
|
+
errors.push(`${key} is required`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (value.length > 255) {
|
|
104
|
+
errors.push(`${key} must be 255 characters or fewer`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const validation = (0, inputValidation_js_1.validateValue)(value);
|
|
108
|
+
if (!validation.valid)
|
|
109
|
+
errors.push(validation.error || `Invalid ${key}`);
|
|
110
|
+
};
|
|
111
|
+
const validatePositiveNumber = (value, key, min, max, errors) => {
|
|
112
|
+
if (value === undefined)
|
|
113
|
+
return;
|
|
114
|
+
const numeric = Number(value);
|
|
115
|
+
if (!Number.isFinite(numeric) || numeric < min || numeric > max) {
|
|
116
|
+
errors.push(`${key} must be a number between ${min} and ${max}`);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const validateColumnList = (value, key, errors) => {
|
|
120
|
+
if (value === undefined)
|
|
121
|
+
return;
|
|
122
|
+
if (!Array.isArray(value)) {
|
|
123
|
+
errors.push(`${key} must be an array`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
for (const column of value) {
|
|
127
|
+
const validation = (0, inputValidation_js_1.validateTableName)(column);
|
|
128
|
+
if (!validation.valid) {
|
|
129
|
+
errors.push(`Invalid ${key} column '${column}': ${validation.error}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const validateFindTablesByKeywordArgs = (args) => {
|
|
134
|
+
const errors = [];
|
|
135
|
+
validateDiscoveryKeyword(args.keyword, "keyword", errors);
|
|
136
|
+
if (args.search_in !== undefined && !["table_names", "column_names", "comments", "all"].includes(args.search_in)) {
|
|
137
|
+
errors.push("search_in must be one of table_names, column_names, comments, all");
|
|
138
|
+
}
|
|
139
|
+
if (args.database !== undefined) {
|
|
140
|
+
const validation = (0, inputValidation_js_1.validateValue)(args.database);
|
|
141
|
+
if (!validation.valid)
|
|
142
|
+
errors.push(validation.error || "Invalid database name");
|
|
143
|
+
}
|
|
144
|
+
validatePositiveNumber(args.limit, "limit", 1, 100, errors);
|
|
145
|
+
return errors.length ? { valid: false, errors } : { valid: true };
|
|
146
|
+
};
|
|
147
|
+
const validateSearchDataAcrossTablesArgs = (args) => {
|
|
148
|
+
const errors = [];
|
|
149
|
+
validateDiscoveryKeyword(args.keyword, "keyword", errors);
|
|
150
|
+
if (args.database !== undefined) {
|
|
151
|
+
const validation = (0, inputValidation_js_1.validateValue)(args.database);
|
|
152
|
+
if (!validation.valid)
|
|
153
|
+
errors.push(validation.error || "Invalid database name");
|
|
154
|
+
}
|
|
155
|
+
validateTableList(args.tables, "tables", errors);
|
|
156
|
+
validateColumnList(args.columns, "columns", errors);
|
|
157
|
+
validatePositiveNumber(args.max_tables, "max_tables", 1, 100, errors);
|
|
158
|
+
validatePositiveNumber(args.limit_per_table, "limit_per_table", 1, 20, errors);
|
|
159
|
+
return errors.length ? { valid: false, errors } : { valid: true };
|
|
160
|
+
};
|
|
161
|
+
const validateSearchSchemaArgs = (args) => {
|
|
162
|
+
const errors = [];
|
|
163
|
+
validateDiscoveryKeyword(args.query, "query", errors);
|
|
164
|
+
if (args.database !== undefined) {
|
|
165
|
+
const validation = (0, inputValidation_js_1.validateValue)(args.database);
|
|
166
|
+
if (!validation.valid)
|
|
167
|
+
errors.push(validation.error || "Invalid database name");
|
|
168
|
+
}
|
|
169
|
+
if (args.modes !== undefined) {
|
|
170
|
+
if (!Array.isArray(args.modes) || args.modes.length === 0) {
|
|
171
|
+
errors.push("modes must be a non-empty array");
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const validModes = ["table_names", "column_names", "comments", "sample_data"];
|
|
175
|
+
for (const mode of args.modes) {
|
|
176
|
+
if (!validModes.includes(mode)) {
|
|
177
|
+
errors.push(`Invalid mode '${mode}'. Must be one of ${validModes.join(", ")}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
validateTableList(args.tables, "tables", errors);
|
|
183
|
+
validateColumnList(args.columns, "columns", errors);
|
|
184
|
+
validatePositiveNumber(args.max_results, "max_results", 1, 100, errors);
|
|
185
|
+
validatePositiveNumber(args.max_tables, "max_tables", 1, 100, errors);
|
|
186
|
+
validatePositiveNumber(args.limit_per_table, "limit_per_table", 1, 20, errors);
|
|
187
|
+
return errors.length ? { valid: false, errors } : { valid: true };
|
|
188
|
+
};
|
|
189
|
+
const validateSchemaRagContextArgs = (args) => {
|
|
190
|
+
const errors = [];
|
|
191
|
+
if (args.database !== undefined) {
|
|
192
|
+
const validation = (0, inputValidation_js_1.validateValue)(args.database);
|
|
193
|
+
if (!validation.valid)
|
|
194
|
+
errors.push(validation.error || "Invalid database name");
|
|
195
|
+
}
|
|
196
|
+
if (args.keyword_filter !== undefined) {
|
|
197
|
+
validateDiscoveryKeyword(args.keyword_filter, "keyword_filter", errors);
|
|
198
|
+
}
|
|
199
|
+
validatePositiveNumber(args.max_tables, "max_tables", 1, 200, errors);
|
|
200
|
+
validatePositiveNumber(args.max_columns, "max_columns", 1, 200, errors);
|
|
201
|
+
return errors.length ? { valid: false, errors } : { valid: true };
|
|
202
|
+
};
|
|
98
203
|
const validatePlanIdArgs = (args) => {
|
|
99
204
|
if (!args.plan_id || typeof args.plan_id !== "string") {
|
|
100
205
|
return { valid: false, errors: ["plan_id is required"] };
|
|
@@ -138,9 +243,16 @@ function validateToolArguments(name, args) {
|
|
|
138
243
|
return validateInferSeedRulesArgs(args);
|
|
139
244
|
case "seed_from_template":
|
|
140
245
|
return validateSeedFromTemplateArgs(args);
|
|
246
|
+
case "find_tables_by_keyword":
|
|
247
|
+
return validateFindTablesByKeywordArgs(args);
|
|
248
|
+
case "search_schema":
|
|
249
|
+
return validateSearchSchemaArgs(args);
|
|
250
|
+
case "search_data_across_tables":
|
|
251
|
+
return validateSearchDataAcrossTablesArgs(args);
|
|
252
|
+
case "get_schema_rag_context":
|
|
253
|
+
return validateSchemaRagContextArgs(args);
|
|
141
254
|
case "list_tables":
|
|
142
255
|
case "get_schema_erd":
|
|
143
|
-
case "get_schema_rag_context":
|
|
144
256
|
case "get_database_summary":
|
|
145
257
|
if (args.database !== undefined) {
|
|
146
258
|
const validation = (0, inputValidation_js_1.validateValue)(args.database);
|
|
@@ -347,6 +347,13 @@ class UtilityTools {
|
|
|
347
347
|
"get_database_summary",
|
|
348
348
|
"get_schema_rag_context",
|
|
349
349
|
],
|
|
350
|
+
discover_concept_location: [
|
|
351
|
+
"find_tables_by_keyword",
|
|
352
|
+
"search_schema",
|
|
353
|
+
"read_table_schema on likely matches",
|
|
354
|
+
"read_records on likely matches for verification",
|
|
355
|
+
"search_data_across_tables only when the keyword may exist only in row data",
|
|
356
|
+
],
|
|
350
357
|
inspect_table: [
|
|
351
358
|
"read_table_schema",
|
|
352
359
|
"get_column_statistics",
|
|
@@ -375,9 +382,11 @@ class UtilityTools {
|
|
|
375
382
|
],
|
|
376
383
|
},
|
|
377
384
|
selection_rules: [
|
|
378
|
-
"Use get_schema_rag_context before generating SQL to reduce token usage.",
|
|
385
|
+
"Use get_schema_rag_context before generating SQL to reduce token usage; add keyword_filter for concept-focused context.",
|
|
386
|
+
"Use find_tables_by_keyword or search_schema when users ask which table stores a concept.",
|
|
387
|
+
"Use search_data_across_tables only as a bounded fallback after schema metadata is inconclusive.",
|
|
379
388
|
"Use run_select_query only for SELECT statements.",
|
|
380
|
-
"Use execute_write_query for INSERT
|
|
389
|
+
"Use execute_write_query for INSERT and UPDATE. DELETE requires the delete permission.",
|
|
381
390
|
"Use execute_ddl only for CREATE, ALTER, DROP, TRUNCATE, and RENAME.",
|
|
382
391
|
"Use seed_operations for relational dummy data instead of manually chaining bulk_insert across foreign keys.",
|
|
383
392
|
"Prefer structured tools over raw SQL when possible.",
|
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mysql-mcp",
|
|
3
3
|
"description": "A Model Context Protocol for MySQL database interaction",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.43.0",
|
|
5
5
|
"tools": [
|
|
6
6
|
{
|
|
7
7
|
"name": "list_databases",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
"name": "get_schema_rag_context",
|
|
75
|
-
"description": "🎯 AI-OPTIMIZED: Returns ultra-compact schema information (tables, columns, keys, relationships, row estimates) designed specifically for LLM context windows. Use
|
|
75
|
+
"description": "🎯 AI-OPTIMIZED: Returns ultra-compact schema information (tables, columns, keys, relationships, comments, row estimates) designed specifically for LLM context windows. Use keyword_filter for concept-focused schema discovery.",
|
|
76
76
|
"input_schema": {
|
|
77
77
|
"type": "object",
|
|
78
78
|
"properties": {
|
|
@@ -91,6 +91,14 @@
|
|
|
91
91
|
"include_relationships": {
|
|
92
92
|
"type": "boolean",
|
|
93
93
|
"description": "Whether to include FK relationships section (default: true)"
|
|
94
|
+
},
|
|
95
|
+
"include_comments": {
|
|
96
|
+
"type": "boolean",
|
|
97
|
+
"description": "Optional: include TABLE_COMMENT and COLUMN_COMMENT metadata (default: false)"
|
|
98
|
+
},
|
|
99
|
+
"keyword_filter": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"description": "Optional: only include tables relevant to this keyword, ranked by table names, column names, and comments"
|
|
94
102
|
}
|
|
95
103
|
}
|
|
96
104
|
},
|
|
@@ -98,6 +106,150 @@
|
|
|
98
106
|
"type": "object"
|
|
99
107
|
}
|
|
100
108
|
},
|
|
109
|
+
{
|
|
110
|
+
"name": "find_tables_by_keyword",
|
|
111
|
+
"description": "🔎 Schema discovery: finds candidate tables for a concept or keyword by searching table names, column names, TABLE_COMMENT, and COLUMN_COMMENT metadata. Use when users ask 'which table stores X?' before reading sample data.",
|
|
112
|
+
"input_schema": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"properties": {
|
|
115
|
+
"keyword": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "Concept or keyword to find, such as survey, invoice, feedback, or customer"
|
|
118
|
+
},
|
|
119
|
+
"search_in": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"enum": [
|
|
122
|
+
"table_names",
|
|
123
|
+
"column_names",
|
|
124
|
+
"comments",
|
|
125
|
+
"all"
|
|
126
|
+
],
|
|
127
|
+
"description": "Where to search (default: all)"
|
|
128
|
+
},
|
|
129
|
+
"database": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"description": "Optional: specific database name"
|
|
132
|
+
},
|
|
133
|
+
"limit": {
|
|
134
|
+
"type": "number",
|
|
135
|
+
"description": "Optional: maximum number of ranked tables to return (default 20, max 100)"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"required": [
|
|
139
|
+
"keyword"
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
"output_schema": {
|
|
143
|
+
"type": "object"
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "search_schema",
|
|
148
|
+
"description": "🧭 Unified schema discovery for natural-language 'where is X?' questions. Searches schema metadata by default and can optionally perform a guarded sample-data scan when mode sample_data is requested.",
|
|
149
|
+
"input_schema": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"properties": {
|
|
152
|
+
"query": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"description": "Concept or keyword to discover"
|
|
155
|
+
},
|
|
156
|
+
"modes": {
|
|
157
|
+
"type": "array",
|
|
158
|
+
"items": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"enum": [
|
|
161
|
+
"table_names",
|
|
162
|
+
"column_names",
|
|
163
|
+
"comments",
|
|
164
|
+
"sample_data"
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
"description": "Discovery modes (default: table_names, column_names, comments). sample_data requires read permission."
|
|
168
|
+
},
|
|
169
|
+
"max_results": {
|
|
170
|
+
"type": "number",
|
|
171
|
+
"description": "Optional: maximum combined results to return (default 20, max 100)"
|
|
172
|
+
},
|
|
173
|
+
"database": {
|
|
174
|
+
"type": "string",
|
|
175
|
+
"description": "Optional: specific database name"
|
|
176
|
+
},
|
|
177
|
+
"tables": {
|
|
178
|
+
"type": "array",
|
|
179
|
+
"items": {
|
|
180
|
+
"type": "string"
|
|
181
|
+
},
|
|
182
|
+
"description": "Optional: restrict sample_data mode to these tables"
|
|
183
|
+
},
|
|
184
|
+
"columns": {
|
|
185
|
+
"type": "array",
|
|
186
|
+
"items": {
|
|
187
|
+
"type": "string"
|
|
188
|
+
},
|
|
189
|
+
"description": "Optional: restrict sample_data mode to these columns"
|
|
190
|
+
},
|
|
191
|
+
"max_tables": {
|
|
192
|
+
"type": "number",
|
|
193
|
+
"description": "Optional: max tables scanned in sample_data mode (default 20, max 100)"
|
|
194
|
+
},
|
|
195
|
+
"limit_per_table": {
|
|
196
|
+
"type": "number",
|
|
197
|
+
"description": "Optional: max matching rows returned per table in sample_data mode (default 5, max 20)"
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
"required": [
|
|
201
|
+
"query"
|
|
202
|
+
]
|
|
203
|
+
},
|
|
204
|
+
"output_schema": {
|
|
205
|
+
"type": "object"
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"name": "search_data_across_tables",
|
|
210
|
+
"description": "🔍 Guarded read-only keyword scan across text-like table data. Use only when schema metadata does not reveal where a concept is stored; enforces max table and per-table result limits.",
|
|
211
|
+
"input_schema": {
|
|
212
|
+
"type": "object",
|
|
213
|
+
"properties": {
|
|
214
|
+
"keyword": {
|
|
215
|
+
"type": "string",
|
|
216
|
+
"description": "Keyword to search inside text-like column values"
|
|
217
|
+
},
|
|
218
|
+
"tables": {
|
|
219
|
+
"type": "array",
|
|
220
|
+
"items": {
|
|
221
|
+
"type": "string"
|
|
222
|
+
},
|
|
223
|
+
"description": "Optional: restrict scan to these tables"
|
|
224
|
+
},
|
|
225
|
+
"columns": {
|
|
226
|
+
"type": "array",
|
|
227
|
+
"items": {
|
|
228
|
+
"type": "string"
|
|
229
|
+
},
|
|
230
|
+
"description": "Optional: restrict scan to these column names"
|
|
231
|
+
},
|
|
232
|
+
"database": {
|
|
233
|
+
"type": "string",
|
|
234
|
+
"description": "Optional: specific database name"
|
|
235
|
+
},
|
|
236
|
+
"max_tables": {
|
|
237
|
+
"type": "number",
|
|
238
|
+
"description": "Optional: maximum tables to scan (default 20, max 100)"
|
|
239
|
+
},
|
|
240
|
+
"limit_per_table": {
|
|
241
|
+
"type": "number",
|
|
242
|
+
"description": "Optional: maximum rows returned per table (default 5, max 20)"
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"required": [
|
|
246
|
+
"keyword"
|
|
247
|
+
]
|
|
248
|
+
},
|
|
249
|
+
"output_schema": {
|
|
250
|
+
"type": "object"
|
|
251
|
+
}
|
|
252
|
+
},
|
|
101
253
|
{
|
|
102
254
|
"name": "get_column_statistics",
|
|
103
255
|
"description": "Returns detailed statistics for a specific column: min/max values, average, distinct count, null percentage, and value distribution. Use to understand data quality and ranges in a column before writing queries.",
|
package/package.json
CHANGED