@neverinfamous/mysql-mcp 2.2.0 → 2.3.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 (213) hide show
  1. package/.github/workflows/docker-publish.yml +1 -2
  2. package/CHANGELOG.md +85 -0
  3. package/CODE_MODE.md +245 -0
  4. package/DOCKER_README.md +59 -36
  5. package/README.md +65 -42
  6. package/VERSION +1 -1
  7. package/dist/adapters/mysql/MySQLAdapter.d.ts +4 -0
  8. package/dist/adapters/mysql/MySQLAdapter.d.ts.map +1 -1
  9. package/dist/adapters/mysql/MySQLAdapter.js +9 -0
  10. package/dist/adapters/mysql/MySQLAdapter.js.map +1 -1
  11. package/dist/adapters/mysql/prompts/index.d.ts +8 -1
  12. package/dist/adapters/mysql/prompts/index.d.ts.map +1 -1
  13. package/dist/adapters/mysql/prompts/index.js +8 -1
  14. package/dist/adapters/mysql/prompts/index.js.map +1 -1
  15. package/dist/adapters/mysql/prompts/routerSetup.d.ts.map +1 -1
  16. package/dist/adapters/mysql/prompts/routerSetup.js +5 -0
  17. package/dist/adapters/mysql/prompts/routerSetup.js.map +1 -1
  18. package/dist/adapters/mysql/resources/capabilities.d.ts.map +1 -1
  19. package/dist/adapters/mysql/resources/capabilities.js +6 -5
  20. package/dist/adapters/mysql/resources/capabilities.js.map +1 -1
  21. package/dist/adapters/mysql/resources/index.d.ts +9 -1
  22. package/dist/adapters/mysql/resources/index.d.ts.map +1 -1
  23. package/dist/adapters/mysql/resources/index.js +9 -1
  24. package/dist/adapters/mysql/resources/index.js.map +1 -1
  25. package/dist/adapters/mysql/tools/admin/backup.d.ts.map +1 -1
  26. package/dist/adapters/mysql/tools/admin/backup.js +3 -3
  27. package/dist/adapters/mysql/tools/admin/backup.js.map +1 -1
  28. package/dist/adapters/mysql/tools/admin/maintenance.d.ts.map +1 -1
  29. package/dist/adapters/mysql/tools/admin/maintenance.js +5 -5
  30. package/dist/adapters/mysql/tools/admin/maintenance.js.map +1 -1
  31. package/dist/adapters/mysql/tools/cluster/innodb-cluster.d.ts.map +1 -1
  32. package/dist/adapters/mysql/tools/cluster/innodb-cluster.js +26 -5
  33. package/dist/adapters/mysql/tools/cluster/innodb-cluster.js.map +1 -1
  34. package/dist/adapters/mysql/tools/codemode/index.d.ts +38 -0
  35. package/dist/adapters/mysql/tools/codemode/index.d.ts.map +1 -0
  36. package/dist/adapters/mysql/tools/codemode/index.js +203 -0
  37. package/dist/adapters/mysql/tools/codemode/index.js.map +1 -0
  38. package/dist/adapters/mysql/tools/core.d.ts.map +1 -1
  39. package/dist/adapters/mysql/tools/core.js +32 -20
  40. package/dist/adapters/mysql/tools/core.js.map +1 -1
  41. package/dist/adapters/mysql/tools/events.js +18 -6
  42. package/dist/adapters/mysql/tools/events.js.map +1 -1
  43. package/dist/adapters/mysql/tools/json/core.d.ts.map +1 -1
  44. package/dist/adapters/mysql/tools/json/core.js +5 -5
  45. package/dist/adapters/mysql/tools/json/core.js.map +1 -1
  46. package/dist/adapters/mysql/tools/json/helpers.d.ts.map +1 -1
  47. package/dist/adapters/mysql/tools/json/helpers.js +9 -3
  48. package/dist/adapters/mysql/tools/json/helpers.js.map +1 -1
  49. package/dist/adapters/mysql/tools/partitioning.d.ts.map +1 -1
  50. package/dist/adapters/mysql/tools/partitioning.js +38 -6
  51. package/dist/adapters/mysql/tools/partitioning.js.map +1 -1
  52. package/dist/adapters/mysql/tools/performance/analysis.d.ts.map +1 -1
  53. package/dist/adapters/mysql/tools/performance/analysis.js +67 -20
  54. package/dist/adapters/mysql/tools/performance/analysis.js.map +1 -1
  55. package/dist/adapters/mysql/tools/performance/optimization.d.ts.map +1 -1
  56. package/dist/adapters/mysql/tools/performance/optimization.js +36 -6
  57. package/dist/adapters/mysql/tools/performance/optimization.js.map +1 -1
  58. package/dist/adapters/mysql/tools/security/data-protection.d.ts.map +1 -1
  59. package/dist/adapters/mysql/tools/security/data-protection.js +9 -4
  60. package/dist/adapters/mysql/tools/security/data-protection.js.map +1 -1
  61. package/dist/adapters/mysql/tools/shell/common.d.ts.map +1 -1
  62. package/dist/adapters/mysql/tools/shell/common.js +28 -2
  63. package/dist/adapters/mysql/tools/shell/common.js.map +1 -1
  64. package/dist/adapters/mysql/tools/shell/restore.d.ts.map +1 -1
  65. package/dist/adapters/mysql/tools/shell/restore.js +54 -4
  66. package/dist/adapters/mysql/tools/shell/restore.js.map +1 -1
  67. package/dist/adapters/mysql/tools/spatial/operations.d.ts.map +1 -1
  68. package/dist/adapters/mysql/tools/spatial/operations.js +10 -2
  69. package/dist/adapters/mysql/tools/spatial/operations.js.map +1 -1
  70. package/dist/adapters/mysql/tools/spatial/setup.d.ts.map +1 -1
  71. package/dist/adapters/mysql/tools/spatial/setup.js +18 -0
  72. package/dist/adapters/mysql/tools/spatial/setup.js.map +1 -1
  73. package/dist/adapters/mysql/tools/sysschema/resources.d.ts.map +1 -1
  74. package/dist/adapters/mysql/tools/sysschema/resources.js +5 -0
  75. package/dist/adapters/mysql/tools/sysschema/resources.js.map +1 -1
  76. package/dist/adapters/mysql/tools/text/fulltext.d.ts.map +1 -1
  77. package/dist/adapters/mysql/tools/text/fulltext.js +6 -4
  78. package/dist/adapters/mysql/tools/text/fulltext.js.map +1 -1
  79. package/dist/adapters/mysql/tools/text/processing.d.ts.map +1 -1
  80. package/dist/adapters/mysql/tools/text/processing.js +10 -45
  81. package/dist/adapters/mysql/tools/text/processing.js.map +1 -1
  82. package/dist/adapters/mysql/tools/transactions.d.ts.map +1 -1
  83. package/dist/adapters/mysql/tools/transactions.js +8 -8
  84. package/dist/adapters/mysql/tools/transactions.js.map +1 -1
  85. package/dist/adapters/mysql/types.d.ts +968 -78
  86. package/dist/adapters/mysql/types.d.ts.map +1 -1
  87. package/dist/adapters/mysql/types.js +1084 -78
  88. package/dist/adapters/mysql/types.js.map +1 -1
  89. package/dist/auth/scopes.d.ts.map +1 -1
  90. package/dist/auth/scopes.js +1 -0
  91. package/dist/auth/scopes.js.map +1 -1
  92. package/dist/cli/args.d.ts.map +1 -1
  93. package/dist/cli/args.js +12 -0
  94. package/dist/cli/args.js.map +1 -1
  95. package/dist/codemode/api.d.ts +69 -0
  96. package/dist/codemode/api.d.ts.map +1 -0
  97. package/dist/codemode/api.js +1035 -0
  98. package/dist/codemode/api.js.map +1 -0
  99. package/dist/codemode/index.d.ts +13 -0
  100. package/dist/codemode/index.d.ts.map +1 -0
  101. package/dist/codemode/index.js +17 -0
  102. package/dist/codemode/index.js.map +1 -0
  103. package/dist/codemode/sandbox-factory.d.ts +72 -0
  104. package/dist/codemode/sandbox-factory.d.ts.map +1 -0
  105. package/dist/codemode/sandbox-factory.js +88 -0
  106. package/dist/codemode/sandbox-factory.js.map +1 -0
  107. package/dist/codemode/sandbox.d.ts +96 -0
  108. package/dist/codemode/sandbox.d.ts.map +1 -0
  109. package/dist/codemode/sandbox.js +345 -0
  110. package/dist/codemode/sandbox.js.map +1 -0
  111. package/dist/codemode/security.d.ts +44 -0
  112. package/dist/codemode/security.d.ts.map +1 -0
  113. package/dist/codemode/security.js +149 -0
  114. package/dist/codemode/security.js.map +1 -0
  115. package/dist/codemode/types.d.ts +137 -0
  116. package/dist/codemode/types.d.ts.map +1 -0
  117. package/dist/codemode/types.js +46 -0
  118. package/dist/codemode/types.js.map +1 -0
  119. package/dist/codemode/worker-sandbox.d.ts +82 -0
  120. package/dist/codemode/worker-sandbox.d.ts.map +1 -0
  121. package/dist/codemode/worker-sandbox.js +244 -0
  122. package/dist/codemode/worker-sandbox.js.map +1 -0
  123. package/dist/codemode/worker-script.d.ts +8 -0
  124. package/dist/codemode/worker-script.d.ts.map +1 -0
  125. package/dist/codemode/worker-script.js +113 -0
  126. package/dist/codemode/worker-script.js.map +1 -0
  127. package/dist/constants/ServerInstructions.d.ts +1 -1
  128. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  129. package/dist/constants/ServerInstructions.js +33 -9
  130. package/dist/constants/ServerInstructions.js.map +1 -1
  131. package/dist/filtering/ToolConstants.d.ts +11 -11
  132. package/dist/filtering/ToolConstants.d.ts.map +1 -1
  133. package/dist/filtering/ToolConstants.js +37 -19
  134. package/dist/filtering/ToolConstants.js.map +1 -1
  135. package/dist/filtering/ToolFilter.d.ts.map +1 -1
  136. package/dist/filtering/ToolFilter.js +12 -0
  137. package/dist/filtering/ToolFilter.js.map +1 -1
  138. package/dist/server/McpServer.js +1 -1
  139. package/dist/server/McpServer.js.map +1 -1
  140. package/dist/types/modules/server.d.ts +2 -0
  141. package/dist/types/modules/server.d.ts.map +1 -1
  142. package/dist/types/modules/tools.d.ts +1 -1
  143. package/dist/types/modules/tools.d.ts.map +1 -1
  144. package/dist/utils/logger.d.ts +1 -1
  145. package/dist/utils/logger.d.ts.map +1 -1
  146. package/dist/utils/logger.js.map +1 -1
  147. package/package.json +12 -7
  148. package/releases/v2.2.0-release-notes.md +18 -18
  149. package/releases/v2.3.0-release-notes.md +191 -0
  150. package/src/__tests__/perf.test.ts +12 -12
  151. package/src/adapters/mysql/MySQLAdapter.ts +10 -0
  152. package/src/adapters/mysql/__tests__/MySQLAdapter.test.ts +1 -1
  153. package/src/adapters/mysql/prompts/index.ts +8 -1
  154. package/src/adapters/mysql/prompts/routerSetup.ts +5 -0
  155. package/src/adapters/mysql/resources/__tests__/capabilities.test.ts +50 -1
  156. package/src/adapters/mysql/resources/capabilities.ts +6 -4
  157. package/src/adapters/mysql/resources/index.ts +9 -1
  158. package/src/adapters/mysql/tools/__tests__/core.test.ts +68 -0
  159. package/src/adapters/mysql/tools/__tests__/events.test.ts +56 -2
  160. package/src/adapters/mysql/tools/__tests__/json_core.test.ts +1 -1
  161. package/src/adapters/mysql/tools/__tests__/json_helpers.test.ts +46 -4
  162. package/src/adapters/mysql/tools/__tests__/replication.test.ts +144 -42
  163. package/src/adapters/mysql/tools/__tests__/security.test.ts +39 -0
  164. package/src/adapters/mysql/tools/__tests__/spatial.test.ts +39 -7
  165. package/src/adapters/mysql/tools/__tests__/spatial_handler.test.ts +35 -3
  166. package/src/adapters/mysql/tools/__tests__/transactions.test.ts +3 -5
  167. package/src/adapters/mysql/tools/admin/backup.ts +8 -3
  168. package/src/adapters/mysql/tools/admin/maintenance.ts +8 -4
  169. package/src/adapters/mysql/tools/cluster/__tests__/innodb-cluster.test.ts +35 -0
  170. package/src/adapters/mysql/tools/cluster/innodb-cluster.ts +26 -5
  171. package/src/adapters/mysql/tools/codemode/index.ts +249 -0
  172. package/src/adapters/mysql/tools/core.ts +44 -27
  173. package/src/adapters/mysql/tools/events.ts +23 -7
  174. package/src/adapters/mysql/tools/json/__tests__/helpers.test.ts +59 -14
  175. package/src/adapters/mysql/tools/json/core.ts +8 -4
  176. package/src/adapters/mysql/tools/json/helpers.ts +13 -3
  177. package/src/adapters/mysql/tools/partitioning.ts +53 -6
  178. package/src/adapters/mysql/tools/performance/__tests__/analysis.test.ts +227 -4
  179. package/src/adapters/mysql/tools/performance/__tests__/optimization.test.ts +35 -0
  180. package/src/adapters/mysql/tools/performance/analysis.ts +75 -21
  181. package/src/adapters/mysql/tools/performance/optimization.ts +44 -6
  182. package/src/adapters/mysql/tools/security/data-protection.ts +10 -4
  183. package/src/adapters/mysql/tools/shell/__tests__/common.test.ts +46 -0
  184. package/src/adapters/mysql/tools/shell/__tests__/restore.test.ts +28 -1
  185. package/src/adapters/mysql/tools/shell/common.ts +34 -2
  186. package/src/adapters/mysql/tools/shell/restore.ts +70 -7
  187. package/src/adapters/mysql/tools/spatial/__tests__/operations.test.ts +29 -0
  188. package/src/adapters/mysql/tools/spatial/operations.ts +13 -2
  189. package/src/adapters/mysql/tools/spatial/setup.ts +23 -0
  190. package/src/adapters/mysql/tools/sysschema/__tests__/resources.test.ts +21 -0
  191. package/src/adapters/mysql/tools/sysschema/resources.ts +5 -0
  192. package/src/adapters/mysql/tools/text/fulltext.ts +13 -5
  193. package/src/adapters/mysql/tools/text/processing.ts +20 -49
  194. package/src/adapters/mysql/tools/transactions.ts +11 -7
  195. package/src/adapters/mysql/types.ts +1241 -87
  196. package/src/auth/scopes.ts +1 -0
  197. package/src/cli/args.ts +14 -0
  198. package/src/codemode/api.ts +1224 -0
  199. package/src/codemode/index.ts +51 -0
  200. package/src/codemode/sandbox-factory.ts +146 -0
  201. package/src/codemode/sandbox.ts +450 -0
  202. package/src/codemode/security.ts +188 -0
  203. package/src/codemode/types.ts +194 -0
  204. package/src/codemode/worker-sandbox.ts +326 -0
  205. package/src/codemode/worker-script.ts +144 -0
  206. package/src/constants/ServerInstructions.ts +33 -9
  207. package/src/filtering/ToolConstants.ts +37 -19
  208. package/src/filtering/ToolFilter.ts +15 -0
  209. package/src/filtering/__tests__/ToolFilter.test.ts +65 -38
  210. package/src/server/McpServer.ts +1 -1
  211. package/src/types/modules/server.ts +3 -0
  212. package/src/types/modules/tools.ts +2 -1
  213. package/src/utils/logger.ts +2 -1
@@ -9,12 +9,19 @@ import type { MySQLAdapter } from "../MySQLAdapter.js";
9
9
  import type { ToolDefinition, RequestContext } from "../../../types/index.js";
10
10
  import {
11
11
  ReadQuerySchema,
12
+ ReadQuerySchemaBase,
12
13
  WriteQuerySchema,
14
+ WriteQuerySchemaBase,
13
15
  CreateTableSchema,
16
+ CreateTableSchemaBase,
14
17
  DescribeTableSchema,
18
+ DescribeTableSchemaBase,
15
19
  DropTableSchema,
20
+ DropTableSchemaBase,
16
21
  CreateIndexSchema,
22
+ CreateIndexSchemaBase,
17
23
  GetIndexesSchema,
24
+ GetIndexesSchemaBase,
18
25
  ListTablesSchema,
19
26
  } from "../types.js";
20
27
 
@@ -69,7 +76,7 @@ function createReadQueryTool(adapter: MySQLAdapter): ToolDefinition {
69
76
  description:
70
77
  "Execute a read-only SQL query (SELECT). Uses prepared statements for safety.",
71
78
  group: "core",
72
- inputSchema: ReadQuerySchema,
79
+ inputSchema: ReadQuerySchemaBase,
73
80
  requiredScopes: ["read"],
74
81
  annotations: {
75
82
  readOnlyHint: true,
@@ -81,16 +88,21 @@ function createReadQueryTool(adapter: MySQLAdapter): ToolDefinition {
81
88
  params: queryParams,
82
89
  transactionId,
83
90
  } = ReadQuerySchema.parse(params);
84
- const result = await adapter.executeReadQuery(
85
- query,
86
- queryParams,
87
- transactionId,
88
- );
89
- return {
90
- rows: result.rows,
91
- rowCount: result.rows?.length ?? 0,
92
- executionTimeMs: result.executionTimeMs,
93
- };
91
+ try {
92
+ const result = await adapter.executeReadQuery(
93
+ query,
94
+ queryParams,
95
+ transactionId,
96
+ );
97
+ return {
98
+ rows: result.rows,
99
+ rowCount: result.rows?.length ?? 0,
100
+ executionTimeMs: result.executionTimeMs,
101
+ };
102
+ } catch (err: unknown) {
103
+ const message = err instanceof Error ? err.message : String(err);
104
+ return { success: false, error: message.replace(/^.*?:\s*/, "") };
105
+ }
94
106
  },
95
107
  };
96
108
  }
@@ -105,7 +117,7 @@ function createWriteQueryTool(adapter: MySQLAdapter): ToolDefinition {
105
117
  description:
106
118
  "Execute a write SQL query (INSERT, UPDATE, DELETE). Uses prepared statements for safety.",
107
119
  group: "core",
108
- inputSchema: WriteQuerySchema,
120
+ inputSchema: WriteQuerySchemaBase,
109
121
  requiredScopes: ["write"],
110
122
  annotations: {
111
123
  readOnlyHint: false,
@@ -116,16 +128,21 @@ function createWriteQueryTool(adapter: MySQLAdapter): ToolDefinition {
116
128
  params: queryParams,
117
129
  transactionId,
118
130
  } = WriteQuerySchema.parse(params);
119
- const result = await adapter.executeWriteQuery(
120
- query,
121
- queryParams,
122
- transactionId,
123
- );
124
- return {
125
- rowsAffected: result.rowsAffected,
126
- lastInsertId: result.lastInsertId?.toString(),
127
- executionTimeMs: result.executionTimeMs,
128
- };
131
+ try {
132
+ const result = await adapter.executeWriteQuery(
133
+ query,
134
+ queryParams,
135
+ transactionId,
136
+ );
137
+ return {
138
+ rowsAffected: result.rowsAffected,
139
+ lastInsertId: result.lastInsertId?.toString(),
140
+ executionTimeMs: result.executionTimeMs,
141
+ };
142
+ } catch (err: unknown) {
143
+ const message = err instanceof Error ? err.message : String(err);
144
+ return { success: false, error: message.replace(/^.*?:\s*/, "") };
145
+ }
129
146
  },
130
147
  };
131
148
  }
@@ -172,7 +189,7 @@ function createDescribeTableTool(adapter: MySQLAdapter): ToolDefinition {
172
189
  description:
173
190
  "Get detailed information about a table's structure including columns, types, and constraints.",
174
191
  group: "core",
175
- inputSchema: DescribeTableSchema,
192
+ inputSchema: DescribeTableSchemaBase,
176
193
  requiredScopes: ["read"],
177
194
  annotations: {
178
195
  readOnlyHint: true,
@@ -204,7 +221,7 @@ function createCreateTableTool(adapter: MySQLAdapter): ToolDefinition {
204
221
  description:
205
222
  "Create a new table with specified columns, engine, and charset.",
206
223
  group: "core",
207
- inputSchema: CreateTableSchema,
224
+ inputSchema: CreateTableSchemaBase,
208
225
  requiredScopes: ["write"],
209
226
  annotations: {
210
227
  readOnlyHint: false,
@@ -313,7 +330,7 @@ function createDropTableTool(adapter: MySQLAdapter): ToolDefinition {
313
330
  title: "MySQL Drop Table",
314
331
  description: "Drop (delete) a table from the database.",
315
332
  group: "core",
316
- inputSchema: DropTableSchema,
333
+ inputSchema: DropTableSchemaBase,
317
334
  requiredScopes: ["admin"],
318
335
  annotations: {
319
336
  readOnlyHint: false,
@@ -376,7 +393,7 @@ function createGetIndexesTool(adapter: MySQLAdapter): ToolDefinition {
376
393
  description:
377
394
  "Get all indexes for a table including type, columns, and cardinality.",
378
395
  group: "core",
379
- inputSchema: GetIndexesSchema,
396
+ inputSchema: GetIndexesSchemaBase,
380
397
  requiredScopes: ["read"],
381
398
  annotations: {
382
399
  readOnlyHint: true,
@@ -410,7 +427,7 @@ function createCreateIndexTool(adapter: MySQLAdapter): ToolDefinition {
410
427
  description:
411
428
  "Create an index on a table. Supports BTREE, HASH, FULLTEXT, and SPATIAL index types.",
412
429
  group: "core",
413
- inputSchema: CreateIndexSchema,
430
+ inputSchema: CreateIndexSchemaBase,
414
431
  requiredScopes: ["write"],
415
432
  annotations: {
416
433
  readOnlyHint: false,
@@ -152,6 +152,22 @@ function createEventCreateTool(adapter: MySQLAdapter): ToolDefinition {
152
152
  throw new Error("Invalid event name");
153
153
  }
154
154
 
155
+ // Pre-check event existence for informative messaging
156
+ if (ifNotExists) {
157
+ const existsCheck = await adapter.executeQuery(
158
+ "SELECT EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA = DATABASE() AND EVENT_NAME = ?",
159
+ [name],
160
+ );
161
+ if (existsCheck.rows && existsCheck.rows.length > 0) {
162
+ return {
163
+ success: true,
164
+ skipped: true,
165
+ reason: "Event already exists",
166
+ eventName: name,
167
+ };
168
+ }
169
+ }
170
+
155
171
  const ifNotExistsClause = ifNotExists ? "IF NOT EXISTS " : "";
156
172
  let sql = `CREATE EVENT ${ifNotExistsClause}\`${name}\`\nON SCHEDULE `;
157
173
 
@@ -273,6 +289,13 @@ function createEventAlterTool(adapter: MySQLAdapter): ToolDefinition {
273
289
  clauses.push(`ON COMPLETION ${onCompletion}`);
274
290
  }
275
291
 
292
+ if (newName) {
293
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(newName)) {
294
+ throw new Error("Invalid new event name");
295
+ }
296
+ clauses.push(`RENAME TO \`${newName}\``);
297
+ }
298
+
276
299
  if (enabled !== undefined) {
277
300
  clauses.push(enabled ? "ENABLE" : "DISABLE");
278
301
  }
@@ -285,13 +308,6 @@ function createEventAlterTool(adapter: MySQLAdapter): ToolDefinition {
285
308
  clauses.push(`DO ${body}`);
286
309
  }
287
310
 
288
- if (newName) {
289
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(newName)) {
290
- throw new Error("Invalid new event name");
291
- }
292
- clauses.push(`RENAME TO \`${newName}\``);
293
- }
294
-
295
311
  if (clauses.length === 0) {
296
312
  throw new Error("No modifications specified");
297
313
  }
@@ -54,6 +54,29 @@ describe("JSON Helper Tools", () => {
54
54
  });
55
55
  });
56
56
 
57
+ describe("createJsonSearchTool", () => {
58
+ it("should search JSON by value", async () => {
59
+ mockAdapter.executeReadQuery.mockResolvedValue(
60
+ createMockQueryResult([{ id: 1, match_path: "$[0]" }]),
61
+ );
62
+
63
+ const tool = createJsonSearchTool(mockAdapter as unknown as MySQLAdapter);
64
+ await tool.handler(
65
+ {
66
+ table: "data",
67
+ column: "json_col",
68
+ searchValue: "test",
69
+ },
70
+ mockContext,
71
+ );
72
+
73
+ const call = mockAdapter.executeReadQuery.mock.calls[0][0] as string;
74
+ expect(call).toContain("JSON_SEARCH");
75
+ expect(call).toContain("SELECT id, `json_col`");
76
+ expect(call).not.toContain("SELECT *");
77
+ });
78
+ });
79
+
57
80
  describe("createJsonUpdateTool", () => {
58
81
  it("should update JSON value by ID", async () => {
59
82
  mockAdapter.executeWriteQuery.mockResolvedValue({
@@ -76,28 +99,27 @@ describe("JSON Helper Tools", () => {
76
99
  expect(mockAdapter.executeWriteQuery).toHaveBeenCalled();
77
100
  expect(result.success).toBe(true);
78
101
  });
79
- });
80
102
 
81
- describe("createJsonSearchTool", () => {
82
- it("should search JSON by value", async () => {
83
- mockAdapter.executeReadQuery.mockResolvedValue(
84
- createMockQueryResult([{ id: 1, match_path: "$[0]" }]),
85
- );
103
+ it("should return reason when no row matches the ID", async () => {
104
+ mockAdapter.executeWriteQuery.mockResolvedValue({
105
+ rowsAffected: 0,
106
+ insertId: 0,
107
+ });
86
108
 
87
- const tool = createJsonSearchTool(mockAdapter as unknown as MySQLAdapter);
88
- await tool.handler(
109
+ const tool = createJsonUpdateTool(mockAdapter as unknown as MySQLAdapter);
110
+ const result = (await tool.handler(
89
111
  {
90
112
  table: "data",
91
113
  column: "json_col",
92
- searchValue: "test",
114
+ path: "$.a",
115
+ value: 2,
116
+ id: 999,
93
117
  },
94
118
  mockContext,
95
- );
119
+ )) as { success: boolean; reason: string };
96
120
 
97
- const call = mockAdapter.executeReadQuery.mock.calls[0][0] as string;
98
- expect(call).toContain("JSON_SEARCH");
99
- expect(call).toContain("SELECT id, `json_col`");
100
- expect(call).not.toContain("SELECT *");
121
+ expect(result.success).toBe(false);
122
+ expect(result.reason).toContain("999");
101
123
  });
102
124
  });
103
125
 
@@ -120,6 +142,29 @@ describe("JSON Helper Tools", () => {
120
142
  expect(mockAdapter.executeReadQuery).toHaveBeenCalled();
121
143
  expect(result.valid).toBe(true);
122
144
  });
145
+
146
+ it("should pass bare strings directly without auto-conversion", async () => {
147
+ mockAdapter.executeReadQuery.mockResolvedValue(
148
+ createMockQueryResult([{ is_valid: 0 }]),
149
+ );
150
+
151
+ const tool = createJsonValidateTool(
152
+ mockAdapter as unknown as MySQLAdapter,
153
+ );
154
+ const result = (await tool.handler(
155
+ {
156
+ value: "hello",
157
+ },
158
+ mockContext,
159
+ )) as { valid: boolean };
160
+
161
+ expect(mockAdapter.executeReadQuery).toHaveBeenCalled();
162
+ // The bare string "hello" should be passed directly, not wrapped
163
+ const sqlParam = mockAdapter.executeReadQuery.mock
164
+ .calls[0][1] as string[];
165
+ expect(sqlParam[0]).toBe("hello");
166
+ expect(result.valid).toBe(false);
167
+ });
123
168
  });
124
169
 
125
170
  describe("P154 Graceful Error Handling", () => {
@@ -12,9 +12,13 @@ import type {
12
12
  } from "../../../../types/index.js";
13
13
  import {
14
14
  JsonExtractSchema,
15
+ JsonExtractSchemaBase,
15
16
  JsonSetSchema,
17
+ JsonSetSchemaBase,
16
18
  JsonContainsSchema,
19
+ JsonContainsSchemaBase,
17
20
  JsonKeysSchema,
21
+ JsonKeysSchemaBase,
18
22
  } from "../../types.js";
19
23
  import { z } from "zod";
20
24
  import {
@@ -57,7 +61,7 @@ export function createJsonExtractTool(adapter: MySQLAdapter): ToolDefinition {
57
61
  description:
58
62
  "Extract values from JSON columns using JSON path expressions.",
59
63
  group: "json",
60
- inputSchema: JsonExtractSchema,
64
+ inputSchema: JsonExtractSchemaBase,
61
65
  requiredScopes: ["read"],
62
66
  annotations: {
63
67
  readOnlyHint: true,
@@ -100,7 +104,7 @@ export function createJsonSetTool(adapter: MySQLAdapter): ToolDefinition {
100
104
  title: "MySQL JSON Set",
101
105
  description: "Set or update values in JSON columns at specified paths.",
102
106
  group: "json",
103
- inputSchema: JsonSetSchema,
107
+ inputSchema: JsonSetSchemaBase,
104
108
  requiredScopes: ["write"],
105
109
  annotations: {
106
110
  readOnlyHint: false,
@@ -286,7 +290,7 @@ export function createJsonContainsTool(adapter: MySQLAdapter): ToolDefinition {
286
290
  title: "MySQL JSON Contains",
287
291
  description: "Find rows where JSON column contains a specified value.",
288
292
  group: "json",
289
- inputSchema: JsonContainsSchema,
293
+ inputSchema: JsonContainsSchemaBase,
290
294
  requiredScopes: ["read"],
291
295
  annotations: {
292
296
  readOnlyHint: true,
@@ -332,7 +336,7 @@ export function createJsonKeysTool(adapter: MySQLAdapter): ToolDefinition {
332
336
  title: "MySQL JSON Keys",
333
337
  description: "Get the keys of a JSON object at the specified path.",
334
338
  group: "json",
335
- inputSchema: JsonKeysSchema,
339
+ inputSchema: JsonKeysSchemaBase,
336
340
  requiredScopes: ["read"],
337
341
  annotations: {
338
342
  readOnlyHint: true,
@@ -10,7 +10,11 @@ import type {
10
10
  ToolDefinition,
11
11
  RequestContext,
12
12
  } from "../../../../types/index.js";
13
- import { JsonSearchSchema, JsonValidateSchema } from "../../types.js";
13
+ import {
14
+ JsonSearchSchema,
15
+ JsonSearchSchemaBase,
16
+ JsonValidateSchema,
17
+ } from "../../types.js";
14
18
  import { z } from "zod";
15
19
  import {
16
20
  validateQualifiedIdentifier,
@@ -133,7 +137,13 @@ export function createJsonUpdateTool(adapter: MySQLAdapter): ToolDefinition {
133
137
  jsonValue,
134
138
  id,
135
139
  ]);
136
- return { success: result.rowsAffected === 1 };
140
+ if (result.rowsAffected === 0) {
141
+ return {
142
+ success: false,
143
+ reason: `No row found with ${idColumn} = ${id}`,
144
+ };
145
+ }
146
+ return { success: true };
137
147
  } catch (error) {
138
148
  const msg = error instanceof Error ? error.message : String(error);
139
149
  if (msg.includes("doesn't exist")) {
@@ -152,7 +162,7 @@ export function createJsonSearchTool(adapter: MySQLAdapter): ToolDefinition {
152
162
  description:
153
163
  "Search for a string value in JSON columns and return matching paths.",
154
164
  group: "json",
155
- inputSchema: JsonSearchSchema,
165
+ inputSchema: JsonSearchSchemaBase,
156
166
  requiredScopes: ["read"],
157
167
  annotations: {
158
168
  readOnlyHint: true,
@@ -5,13 +5,18 @@
5
5
  * 4 tools: partition_info, add_partition, drop_partition, reorganize_partition.
6
6
  */
7
7
 
8
+ import { ZodError } from "zod";
8
9
  import type { MySQLAdapter } from "../MySQLAdapter.js";
9
10
  import type { ToolDefinition, RequestContext } from "../../../types/index.js";
10
11
  import {
11
12
  PartitionInfoSchema,
13
+ PartitionInfoSchemaBase,
12
14
  AddPartitionSchema,
15
+ AddPartitionSchemaBase,
13
16
  DropPartitionSchema,
17
+ DropPartitionSchemaBase,
14
18
  ReorganizePartitionSchema,
19
+ ReorganizePartitionSchemaBase,
15
20
  } from "../types.js";
16
21
 
17
22
  /**
@@ -32,7 +37,7 @@ function createPartitionInfoTool(adapter: MySQLAdapter): ToolDefinition {
32
37
  title: "MySQL Partition Info",
33
38
  description: "Get partition information for a table.",
34
39
  group: "partitioning",
35
- inputSchema: PartitionInfoSchema,
40
+ inputSchema: PartitionInfoSchemaBase,
36
41
  requiredScopes: ["read"],
37
42
  annotations: {
38
43
  readOnlyHint: true,
@@ -99,7 +104,7 @@ function createAddPartitionTool(adapter: MySQLAdapter): ToolDefinition {
99
104
  title: "MySQL Add Partition",
100
105
  description: "Add a new partition to a partitioned table.",
101
106
  group: "partitioning",
102
- inputSchema: AddPartitionSchema,
107
+ inputSchema: AddPartitionSchemaBase,
103
108
  requiredScopes: ["admin"],
104
109
  annotations: {
105
110
  readOnlyHint: false,
@@ -108,6 +113,16 @@ function createAddPartitionTool(adapter: MySQLAdapter): ToolDefinition {
108
113
  const { table, partitionName, partitionType, value } =
109
114
  AddPartitionSchema.parse(params);
110
115
 
116
+ // P154: Check if table exists
117
+ const tableCheck = await adapter.executeQuery(
118
+ `SELECT TABLE_NAME FROM information_schema.TABLES
119
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?`,
120
+ [table],
121
+ );
122
+ if (!tableCheck.rows || tableCheck.rows.length === 0) {
123
+ return { exists: false, table };
124
+ }
125
+
111
126
  let sql: string;
112
127
 
113
128
  switch (partitionType) {
@@ -171,7 +186,7 @@ function createDropPartitionTool(adapter: MySQLAdapter): ToolDefinition {
171
186
  description:
172
187
  "Drop a partition from a partitioned table. Warning: This deletes all data in the partition!",
173
188
  group: "partitioning",
174
- inputSchema: DropPartitionSchema,
189
+ inputSchema: DropPartitionSchemaBase,
175
190
  requiredScopes: ["admin"],
176
191
  annotations: {
177
192
  readOnlyHint: false,
@@ -180,6 +195,16 @@ function createDropPartitionTool(adapter: MySQLAdapter): ToolDefinition {
180
195
  handler: async (params: unknown, _context: RequestContext) => {
181
196
  const { table, partitionName } = DropPartitionSchema.parse(params);
182
197
 
198
+ // P154: Check if table exists
199
+ const tableCheck = await adapter.executeQuery(
200
+ `SELECT TABLE_NAME FROM information_schema.TABLES
201
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?`,
202
+ [table],
203
+ );
204
+ if (!tableCheck.rows || tableCheck.rows.length === 0) {
205
+ return { exists: false, table };
206
+ }
207
+
183
208
  try {
184
209
  await adapter.executeQuery(
185
210
  `ALTER TABLE \`${table}\` DROP PARTITION \`${partitionName}\``,
@@ -225,14 +250,36 @@ function createReorganizePartitionTool(adapter: MySQLAdapter): ToolDefinition {
225
250
  title: "MySQL Reorganize Partition",
226
251
  description: "Reorganize partitions by splitting or merging them.",
227
252
  group: "partitioning",
228
- inputSchema: ReorganizePartitionSchema,
253
+ inputSchema: ReorganizePartitionSchemaBase,
229
254
  requiredScopes: ["admin"],
230
255
  annotations: {
231
256
  readOnlyHint: false,
232
257
  },
233
258
  handler: async (params: unknown, _context: RequestContext) => {
234
- const { table, fromPartitions, partitionType, toPartitions } =
235
- ReorganizePartitionSchema.parse(params);
259
+ let parsed;
260
+ try {
261
+ parsed = ReorganizePartitionSchema.parse(params);
262
+ } catch (error) {
263
+ if (error instanceof ZodError) {
264
+ return {
265
+ success: false,
266
+ error:
267
+ "HASH/KEY partitions cannot be reorganized. Only RANGE and LIST partition types support REORGANIZE PARTITION.",
268
+ };
269
+ }
270
+ throw error;
271
+ }
272
+ const { table, fromPartitions, partitionType, toPartitions } = parsed;
273
+
274
+ // P154: Check if table exists
275
+ const tableCheck = await adapter.executeQuery(
276
+ `SELECT TABLE_NAME FROM information_schema.TABLES
277
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?`,
278
+ [table],
279
+ );
280
+ if (!tableCheck.rows || tableCheck.rows.length === 0) {
281
+ return { exists: false, table };
282
+ }
236
283
 
237
284
  const fromList = fromPartitions.map((p) => `\`${p}\``).join(", ");
238
285
  const toList = toPartitions