@cainli/mcp-server-mysql 2.0.16 → 2.0.17
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 +1 -0
- package/dist/index.js +1 -0
- package/dist/src/config/index.js +1 -0
- package/dist/src/db/index.js +19 -2
- package/dist/src/db/utils.js +28 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -617,6 +617,7 @@ When `MYSQL_CONNECTION_STRING` is provided, it takes precedence over individual
|
|
|
617
617
|
- `ALLOW_UPDATE_OPERATION`: Enable UPDATE operations (default: "false")
|
|
618
618
|
- `ALLOW_DELETE_OPERATION`: Enable DELETE operations (default: "false")
|
|
619
619
|
- `ALLOW_DDL_OPERATION`: Enable DDL operations (default: "false")
|
|
620
|
+
- `REQUIRE_DB_FILTER_FOR_INFO_SCHEMA`: **[NEW]** Require database filter for `information_schema.tables` queries (default: "false"). When enabled in single-DB mode, queries against `information_schema.tables` must include a `WHERE table_schema = '<database_name>'` clause. This prevents full table scans on databases with many tables (4500+) which can cause high CPU usage.
|
|
620
621
|
- `MYSQL_DISABLE_READ_ONLY_TRANSACTIONS`: **[NEW]** Disable read-only transaction enforcement (default: "false") ⚠️ **Security Warning:** Only enable this if you need full write capabilities and trust the LLM with your database
|
|
621
622
|
- `SCHEMA_INSERT_PERMISSIONS`: Schema-specific INSERT permissions
|
|
622
623
|
- `SCHEMA_UPDATE_PERMISSIONS`: Schema-specific UPDATE permissions
|
package/dist/index.js
CHANGED
|
@@ -113,6 +113,7 @@ export default function createMcpServer({ sessionId, config, }) {
|
|
|
113
113
|
? `socket: ${process.env.MYSQL_SOCKET_PATH}`
|
|
114
114
|
: `host: ${process.env.MYSQL_HOST || "localhost"}, port: ${process.env.MYSQL_PORT || 3306}`;
|
|
115
115
|
log("info", `Connection info: ${connectionInfo}`);
|
|
116
|
+
log("info", `isMultiDbMode: ${isMultiDbMode}, configured database: ${mcpConfig.mysql.database || 'none'}`);
|
|
116
117
|
const tablesQuery = `
|
|
117
118
|
SELECT
|
|
118
119
|
table_name as name,
|
package/dist/src/config/index.js
CHANGED
|
@@ -47,6 +47,7 @@ export const REMOTE_SECRET_KEY = process.env.REMOTE_SECRET_KEY || "";
|
|
|
47
47
|
export const PORT = process.env.PORT || 3000;
|
|
48
48
|
const dbFromEnvOrConnString = connectionStringConfig.database || process.env.MYSQL_DB;
|
|
49
49
|
export const isMultiDbMode = !dbFromEnvOrConnString || dbFromEnvOrConnString.trim() === "";
|
|
50
|
+
export const REQUIRE_DB_FILTER_FOR_INFO_SCHEMA = process.env.REQUIRE_DB_FILTER_FOR_INFO_SCHEMA === "true";
|
|
50
51
|
export const mcpConfig = {
|
|
51
52
|
server: {
|
|
52
53
|
name: "@benborla29/mcp-server-mysql",
|
package/dist/src/db/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { performance } from "perf_hooks";
|
|
2
|
-
import { isMultiDbMode } from "./../config/index.js";
|
|
2
|
+
import { isMultiDbMode, REQUIRE_DB_FILTER_FOR_INFO_SCHEMA, } from "./../config/index.js";
|
|
3
3
|
import { isDDLAllowedForSchema, isInsertAllowedForSchema, isUpdateAllowedForSchema, isDeleteAllowedForSchema, } from "./permissions.js";
|
|
4
|
-
import { extractSchemaFromQuery, getQueryTypes } from "./utils.js";
|
|
4
|
+
import { extractSchemaFromQuery, getQueryTypes, validateInfoSchemaQuery, } from "./utils.js";
|
|
5
5
|
import * as mysql2 from "mysql2/promise";
|
|
6
6
|
import { log } from "./../utils/index.js";
|
|
7
7
|
import { mcpConfig as config, MYSQL_DISABLE_READ_ONLY_TRANSACTIONS } from "./../config/index.js";
|
|
@@ -142,6 +142,23 @@ async function executeWriteQuery(sql) {
|
|
|
142
142
|
async function executeReadOnlyQuery(sql) {
|
|
143
143
|
let connection;
|
|
144
144
|
try {
|
|
145
|
+
if (REQUIRE_DB_FILTER_FOR_INFO_SCHEMA &&
|
|
146
|
+
!isMultiDbMode &&
|
|
147
|
+
config.mysql.database) {
|
|
148
|
+
const validation = validateInfoSchemaQuery(sql, config.mysql.database);
|
|
149
|
+
if (!validation.valid) {
|
|
150
|
+
log("error", `information_schema.tables query validation failed: ${validation.reason}`);
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: `Error: ${validation.reason}`,
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
isError: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
145
162
|
const queryTypes = await getQueryTypes(sql);
|
|
146
163
|
const schema = extractSchemaFromQuery(sql);
|
|
147
164
|
const isUpdateOperation = queryTypes.some((type) => ["update"].includes(type));
|
package/dist/src/db/utils.js
CHANGED
|
@@ -31,4 +31,31 @@ async function getQueryTypes(query) {
|
|
|
31
31
|
throw new Error(`Parsing failed: ${err.message}`);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
function validateInfoSchemaQuery(sql, requiredDb) {
|
|
35
|
+
if (!requiredDb) {
|
|
36
|
+
return { valid: true };
|
|
37
|
+
}
|
|
38
|
+
const normalizedSql = sql.replace(/\s+/g, " ").trim().toLowerCase();
|
|
39
|
+
const infoSchemaPatterns = [
|
|
40
|
+
/from\s+information_schema\.tables\b/,
|
|
41
|
+
/from\s+information_schema\.`tables`\b/,
|
|
42
|
+
/from\s+`information_schema`\.tables\b/,
|
|
43
|
+
/from\s+`information_schema`\.`tables`\b/,
|
|
44
|
+
];
|
|
45
|
+
const targetsInfoSchemaTables = infoSchemaPatterns.some((pattern) => pattern.test(normalizedSql));
|
|
46
|
+
if (!targetsInfoSchemaTables) {
|
|
47
|
+
return { valid: true };
|
|
48
|
+
}
|
|
49
|
+
const hasTableSchemaFilter = normalizedSql.includes("table_schema") &&
|
|
50
|
+
(normalizedSql.includes("=") ||
|
|
51
|
+
normalizedSql.includes("in") ||
|
|
52
|
+
normalizedSql.includes("?"));
|
|
53
|
+
if (hasTableSchemaFilter) {
|
|
54
|
+
return { valid: true };
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
valid: false,
|
|
58
|
+
reason: `Querying information_schema.tables requires a database filter. Please add 'WHERE table_schema = '${requiredDb}'' to your query. Required database: ${requiredDb}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export { extractSchemaFromQuery, getQueryTypes, validateInfoSchemaQuery };
|
package/package.json
CHANGED