@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 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,
@@ -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",
@@ -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));
@@ -31,4 +31,31 @@ async function getQueryTypes(query) {
31
31
  throw new Error(`Parsing failed: ${err.message}`);
32
32
  }
33
33
  }
34
- export { extractSchemaFromQuery, getQueryTypes };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cainli/mcp-server-mysql",
3
- "version": "2.0.16",
3
+ "version": "2.0.17",
4
4
  "description": "MCP server for interacting with MySQL databases with write operations support",
5
5
  "license": "MIT",
6
6
  "author": "cainli (https://github.com/cainli)",