@neverinfamous/mysql-mcp 2.3.0 → 3.0.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 (347) hide show
  1. package/.dockerignore +1 -0
  2. package/.gitattributes +18 -0
  3. package/.github/workflows/codeql.yml +2 -10
  4. package/.github/workflows/docker-publish.yml +15 -13
  5. package/CHANGELOG.md +287 -1
  6. package/DOCKER_README.md +100 -265
  7. package/Dockerfile +5 -0
  8. package/README.md +124 -59
  9. package/VERSION +1 -1
  10. package/dist/__tests__/mocks/adapter.d.ts.map +1 -1
  11. package/dist/__tests__/mocks/adapter.js +2 -0
  12. package/dist/__tests__/mocks/adapter.js.map +1 -1
  13. package/dist/adapters/DatabaseAdapter.d.ts.map +1 -1
  14. package/dist/adapters/DatabaseAdapter.js +50 -9
  15. package/dist/adapters/DatabaseAdapter.js.map +1 -1
  16. package/dist/adapters/mysql/MySQLAdapter.d.ts +6 -0
  17. package/dist/adapters/mysql/MySQLAdapter.d.ts.map +1 -1
  18. package/dist/adapters/mysql/MySQLAdapter.js +8 -0
  19. package/dist/adapters/mysql/MySQLAdapter.js.map +1 -1
  20. package/dist/adapters/mysql/SchemaManager.js +16 -15
  21. package/dist/adapters/mysql/SchemaManager.js.map +1 -1
  22. package/dist/adapters/mysql/prompts/index.js +10 -20
  23. package/dist/adapters/mysql/prompts/index.js.map +1 -1
  24. package/dist/adapters/mysql/prompts/proxysqlSetup.js +1 -1
  25. package/dist/adapters/mysql/resources/docstore.d.ts.map +1 -1
  26. package/dist/adapters/mysql/resources/docstore.js +10 -7
  27. package/dist/adapters/mysql/resources/docstore.js.map +1 -1
  28. package/dist/adapters/mysql/resources/events.js +11 -8
  29. package/dist/adapters/mysql/resources/events.js.map +1 -1
  30. package/dist/adapters/mysql/resources/indexes.d.ts.map +1 -1
  31. package/dist/adapters/mysql/resources/indexes.js +12 -15
  32. package/dist/adapters/mysql/resources/indexes.js.map +1 -1
  33. package/dist/adapters/mysql/resources/innodb.d.ts.map +1 -1
  34. package/dist/adapters/mysql/resources/innodb.js +20 -17
  35. package/dist/adapters/mysql/resources/innodb.js.map +1 -1
  36. package/dist/adapters/mysql/resources/locks.d.ts.map +1 -1
  37. package/dist/adapters/mysql/resources/locks.js +9 -6
  38. package/dist/adapters/mysql/resources/locks.js.map +1 -1
  39. package/dist/adapters/mysql/resources/performance.d.ts.map +1 -1
  40. package/dist/adapters/mysql/resources/performance.js +15 -15
  41. package/dist/adapters/mysql/resources/performance.js.map +1 -1
  42. package/dist/adapters/mysql/resources/spatial.d.ts.map +1 -1
  43. package/dist/adapters/mysql/resources/spatial.js +9 -6
  44. package/dist/adapters/mysql/resources/spatial.js.map +1 -1
  45. package/dist/adapters/mysql/resources/sysschema.d.ts.map +1 -1
  46. package/dist/adapters/mysql/resources/sysschema.js +12 -9
  47. package/dist/adapters/mysql/resources/sysschema.js.map +1 -1
  48. package/dist/adapters/mysql/tools/admin/backup.d.ts.map +1 -1
  49. package/dist/adapters/mysql/tools/admin/backup.js +170 -121
  50. package/dist/adapters/mysql/tools/admin/backup.js.map +1 -1
  51. package/dist/adapters/mysql/tools/admin/maintenance.d.ts.map +1 -1
  52. package/dist/adapters/mysql/tools/admin/maintenance.js +106 -57
  53. package/dist/adapters/mysql/tools/admin/maintenance.js.map +1 -1
  54. package/dist/adapters/mysql/tools/admin/monitoring.d.ts.map +1 -1
  55. package/dist/adapters/mysql/tools/admin/monitoring.js +183 -101
  56. package/dist/adapters/mysql/tools/admin/monitoring.js.map +1 -1
  57. package/dist/adapters/mysql/tools/cluster/group-replication.d.ts.map +1 -1
  58. package/dist/adapters/mysql/tools/cluster/group-replication.js +164 -120
  59. package/dist/adapters/mysql/tools/cluster/group-replication.js.map +1 -1
  60. package/dist/adapters/mysql/tools/cluster/innodb-cluster.d.ts.map +1 -1
  61. package/dist/adapters/mysql/tools/cluster/innodb-cluster.js +212 -145
  62. package/dist/adapters/mysql/tools/cluster/innodb-cluster.js.map +1 -1
  63. package/dist/adapters/mysql/tools/codemode/index.d.ts.map +1 -1
  64. package/dist/adapters/mysql/tools/codemode/index.js +6 -4
  65. package/dist/adapters/mysql/tools/codemode/index.js.map +1 -1
  66. package/dist/adapters/mysql/tools/core.d.ts.map +1 -1
  67. package/dist/adapters/mysql/tools/core.js +152 -29
  68. package/dist/adapters/mysql/tools/core.js.map +1 -1
  69. package/dist/adapters/mysql/tools/docstore.d.ts.map +1 -1
  70. package/dist/adapters/mysql/tools/docstore.js +340 -163
  71. package/dist/adapters/mysql/tools/docstore.js.map +1 -1
  72. package/dist/adapters/mysql/tools/events.d.ts.map +1 -1
  73. package/dist/adapters/mysql/tools/events.js +284 -198
  74. package/dist/adapters/mysql/tools/events.js.map +1 -1
  75. package/dist/adapters/mysql/tools/json/core.d.ts.map +1 -1
  76. package/dist/adapters/mysql/tools/json/core.js +11 -39
  77. package/dist/adapters/mysql/tools/json/core.js.map +1 -1
  78. package/dist/adapters/mysql/tools/json/enhanced.d.ts.map +1 -1
  79. package/dist/adapters/mysql/tools/json/enhanced.js +15 -33
  80. package/dist/adapters/mysql/tools/json/enhanced.js.map +1 -1
  81. package/dist/adapters/mysql/tools/json/helpers.d.ts.map +1 -1
  82. package/dist/adapters/mysql/tools/json/helpers.js +13 -24
  83. package/dist/adapters/mysql/tools/json/helpers.js.map +1 -1
  84. package/dist/adapters/mysql/tools/partitioning.js +3 -0
  85. package/dist/adapters/mysql/tools/partitioning.js.map +1 -1
  86. package/dist/adapters/mysql/tools/performance/analysis.d.ts.map +1 -1
  87. package/dist/adapters/mysql/tools/performance/analysis.js +89 -60
  88. package/dist/adapters/mysql/tools/performance/analysis.js.map +1 -1
  89. package/dist/adapters/mysql/tools/performance/optimization.d.ts.map +1 -1
  90. package/dist/adapters/mysql/tools/performance/optimization.js +151 -127
  91. package/dist/adapters/mysql/tools/performance/optimization.js.map +1 -1
  92. package/dist/adapters/mysql/tools/proxysql.d.ts +1 -1
  93. package/dist/adapters/mysql/tools/proxysql.d.ts.map +1 -1
  94. package/dist/adapters/mysql/tools/proxysql.js +289 -176
  95. package/dist/adapters/mysql/tools/proxysql.js.map +1 -1
  96. package/dist/adapters/mysql/tools/replication.js +75 -49
  97. package/dist/adapters/mysql/tools/replication.js.map +1 -1
  98. package/dist/adapters/mysql/tools/roles.d.ts.map +1 -1
  99. package/dist/adapters/mysql/tools/roles.js +224 -182
  100. package/dist/adapters/mysql/tools/roles.js.map +1 -1
  101. package/dist/adapters/mysql/tools/router.d.ts.map +1 -1
  102. package/dist/adapters/mysql/tools/router.js +168 -67
  103. package/dist/adapters/mysql/tools/router.js.map +1 -1
  104. package/dist/adapters/mysql/tools/schema/constraints.d.ts.map +1 -1
  105. package/dist/adapters/mysql/tools/schema/constraints.js +21 -3
  106. package/dist/adapters/mysql/tools/schema/constraints.js.map +1 -1
  107. package/dist/adapters/mysql/tools/schema/management.d.ts.map +1 -1
  108. package/dist/adapters/mysql/tools/schema/management.js +61 -14
  109. package/dist/adapters/mysql/tools/schema/management.js.map +1 -1
  110. package/dist/adapters/mysql/tools/schema/routines.d.ts.map +1 -1
  111. package/dist/adapters/mysql/tools/schema/routines.js +27 -4
  112. package/dist/adapters/mysql/tools/schema/routines.js.map +1 -1
  113. package/dist/adapters/mysql/tools/schema/scheduled_events.d.ts.map +1 -1
  114. package/dist/adapters/mysql/tools/schema/scheduled_events.js +24 -3
  115. package/dist/adapters/mysql/tools/schema/scheduled_events.js.map +1 -1
  116. package/dist/adapters/mysql/tools/schema/triggers.d.ts.map +1 -1
  117. package/dist/adapters/mysql/tools/schema/triggers.js +23 -2
  118. package/dist/adapters/mysql/tools/schema/triggers.js.map +1 -1
  119. package/dist/adapters/mysql/tools/schema/views.d.ts.map +1 -1
  120. package/dist/adapters/mysql/tools/schema/views.js +47 -7
  121. package/dist/adapters/mysql/tools/schema/views.js.map +1 -1
  122. package/dist/adapters/mysql/tools/security/audit.d.ts.map +1 -1
  123. package/dist/adapters/mysql/tools/security/audit.js +102 -34
  124. package/dist/adapters/mysql/tools/security/audit.js.map +1 -1
  125. package/dist/adapters/mysql/tools/security/data-protection.d.ts.map +1 -1
  126. package/dist/adapters/mysql/tools/security/data-protection.js +264 -205
  127. package/dist/adapters/mysql/tools/security/data-protection.js.map +1 -1
  128. package/dist/adapters/mysql/tools/security/encryption.d.ts.map +1 -1
  129. package/dist/adapters/mysql/tools/security/encryption.js +137 -104
  130. package/dist/adapters/mysql/tools/security/encryption.js.map +1 -1
  131. package/dist/adapters/mysql/tools/shell/backup.d.ts.map +1 -1
  132. package/dist/adapters/mysql/tools/shell/backup.js +71 -59
  133. package/dist/adapters/mysql/tools/shell/backup.js.map +1 -1
  134. package/dist/adapters/mysql/tools/shell/restore.d.ts.map +1 -1
  135. package/dist/adapters/mysql/tools/shell/restore.js +61 -47
  136. package/dist/adapters/mysql/tools/shell/restore.js.map +1 -1
  137. package/dist/adapters/mysql/tools/spatial/geometry.d.ts.map +1 -1
  138. package/dist/adapters/mysql/tools/spatial/geometry.js +19 -5
  139. package/dist/adapters/mysql/tools/spatial/geometry.js.map +1 -1
  140. package/dist/adapters/mysql/tools/spatial/operations.d.ts.map +1 -1
  141. package/dist/adapters/mysql/tools/spatial/operations.js +42 -17
  142. package/dist/adapters/mysql/tools/spatial/operations.js.map +1 -1
  143. package/dist/adapters/mysql/tools/spatial/queries.d.ts.map +1 -1
  144. package/dist/adapters/mysql/tools/spatial/queries.js +109 -57
  145. package/dist/adapters/mysql/tools/spatial/queries.js.map +1 -1
  146. package/dist/adapters/mysql/tools/spatial/setup.d.ts.map +1 -1
  147. package/dist/adapters/mysql/tools/spatial/setup.js +103 -50
  148. package/dist/adapters/mysql/tools/spatial/setup.js.map +1 -1
  149. package/dist/adapters/mysql/tools/stats/comparative.d.ts.map +1 -1
  150. package/dist/adapters/mysql/tools/stats/comparative.js +128 -79
  151. package/dist/adapters/mysql/tools/stats/comparative.js.map +1 -1
  152. package/dist/adapters/mysql/tools/stats/descriptive.d.ts.map +1 -1
  153. package/dist/adapters/mysql/tools/stats/descriptive.js +174 -102
  154. package/dist/adapters/mysql/tools/stats/descriptive.js.map +1 -1
  155. package/dist/adapters/mysql/tools/sysschema/activity.d.ts.map +1 -1
  156. package/dist/adapters/mysql/tools/sysschema/activity.js +50 -25
  157. package/dist/adapters/mysql/tools/sysschema/activity.js.map +1 -1
  158. package/dist/adapters/mysql/tools/sysschema/performance.d.ts.map +1 -1
  159. package/dist/adapters/mysql/tools/sysschema/performance.js +121 -66
  160. package/dist/adapters/mysql/tools/sysschema/performance.js.map +1 -1
  161. package/dist/adapters/mysql/tools/sysschema/resources.d.ts.map +1 -1
  162. package/dist/adapters/mysql/tools/sysschema/resources.js +101 -64
  163. package/dist/adapters/mysql/tools/sysschema/resources.js.map +1 -1
  164. package/dist/adapters/mysql/tools/text/fulltext.d.ts.map +1 -1
  165. package/dist/adapters/mysql/tools/text/fulltext.js +18 -32
  166. package/dist/adapters/mysql/tools/text/fulltext.js.map +1 -1
  167. package/dist/adapters/mysql/tools/transactions.d.ts.map +1 -1
  168. package/dist/adapters/mysql/tools/transactions.js +48 -23
  169. package/dist/adapters/mysql/tools/transactions.js.map +1 -1
  170. package/dist/adapters/mysql/types/proxysql-types.d.ts +15 -0
  171. package/dist/adapters/mysql/types/proxysql-types.d.ts.map +1 -1
  172. package/dist/adapters/mysql/types/proxysql-types.js +33 -1
  173. package/dist/adapters/mysql/types/proxysql-types.js.map +1 -1
  174. package/dist/adapters/mysql/types/router-types.d.ts +1 -1
  175. package/dist/adapters/mysql/types/router-types.js +1 -1
  176. package/dist/adapters/mysql/types/router-types.js.map +1 -1
  177. package/dist/adapters/mysql/types/shell-types.js +2 -2
  178. package/dist/adapters/mysql/types/shell-types.js.map +1 -1
  179. package/dist/adapters/mysql/types.d.ts +485 -21
  180. package/dist/adapters/mysql/types.d.ts.map +1 -1
  181. package/dist/adapters/mysql/types.js +546 -19
  182. package/dist/adapters/mysql/types.js.map +1 -1
  183. package/dist/auth/scopes.js +1 -1
  184. package/dist/auth/scopes.js.map +1 -1
  185. package/dist/codemode/api.d.ts +3 -2
  186. package/dist/codemode/api.d.ts.map +1 -1
  187. package/dist/codemode/api.js +80 -5
  188. package/dist/codemode/api.js.map +1 -1
  189. package/dist/codemode/sandbox-factory.js +1 -1
  190. package/dist/codemode/sandbox-factory.js.map +1 -1
  191. package/dist/codemode/types.d.ts +26 -0
  192. package/dist/codemode/types.d.ts.map +1 -1
  193. package/dist/codemode/types.js +2 -0
  194. package/dist/codemode/types.js.map +1 -1
  195. package/dist/codemode/worker-sandbox.d.ts +4 -2
  196. package/dist/codemode/worker-sandbox.d.ts.map +1 -1
  197. package/dist/codemode/worker-sandbox.js +66 -7
  198. package/dist/codemode/worker-sandbox.js.map +1 -1
  199. package/dist/codemode/worker-script.d.ts +3 -0
  200. package/dist/codemode/worker-script.d.ts.map +1 -1
  201. package/dist/codemode/worker-script.js +128 -75
  202. package/dist/codemode/worker-script.js.map +1 -1
  203. package/dist/constants/ServerInstructions.d.ts +1 -1
  204. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  205. package/dist/constants/ServerInstructions.js +37 -31
  206. package/dist/constants/ServerInstructions.js.map +1 -1
  207. package/dist/filtering/ToolConstants.d.ts +1 -1
  208. package/dist/filtering/ToolConstants.d.ts.map +1 -1
  209. package/dist/filtering/ToolConstants.js +1 -2
  210. package/dist/filtering/ToolConstants.js.map +1 -1
  211. package/dist/pool/ConnectionPool.d.ts.map +1 -1
  212. package/dist/pool/ConnectionPool.js.map +1 -1
  213. package/dist/transports/http.d.ts.map +1 -1
  214. package/dist/transports/http.js +6 -0
  215. package/dist/transports/http.js.map +1 -1
  216. package/dist/utils/validators.d.ts +1 -1
  217. package/dist/utils/validators.d.ts.map +1 -1
  218. package/dist/utils/validators.js.map +1 -1
  219. package/package.json +4 -4
  220. package/releases/v2.3.0-release-notes.md +20 -20
  221. package/releases/v2.3.1-release-notes.md +34 -0
  222. package/releases/v3.0.0-release-notes.md +81 -0
  223. package/src/__tests__/mocks/adapter.ts +3 -0
  224. package/src/__tests__/perf.test.ts +6 -6
  225. package/src/adapters/DatabaseAdapter.ts +58 -9
  226. package/src/adapters/__tests__/DatabaseAdapter.test.ts +89 -8
  227. package/src/adapters/mysql/MySQLAdapter.ts +17 -2
  228. package/src/adapters/mysql/SchemaManager.ts +21 -21
  229. package/src/adapters/mysql/__tests__/MySQLAdapter.test.ts +1 -1
  230. package/src/adapters/mysql/prompts/index.ts +12 -22
  231. package/src/adapters/mysql/prompts/proxysqlSetup.ts +1 -1
  232. package/src/adapters/mysql/resources/docstore.ts +13 -10
  233. package/src/adapters/mysql/resources/events.ts +12 -12
  234. package/src/adapters/mysql/resources/indexes.ts +17 -19
  235. package/src/adapters/mysql/resources/innodb.ts +23 -22
  236. package/src/adapters/mysql/resources/locks.ts +9 -7
  237. package/src/adapters/mysql/resources/performance.ts +23 -18
  238. package/src/adapters/mysql/resources/spatial.ts +9 -7
  239. package/src/adapters/mysql/resources/sysschema.ts +12 -11
  240. package/src/adapters/mysql/tools/__tests__/core.test.ts +126 -55
  241. package/src/adapters/mysql/tools/__tests__/docstore.test.ts +459 -88
  242. package/src/adapters/mysql/tools/__tests__/events.test.ts +281 -103
  243. package/src/adapters/mysql/tools/__tests__/proxysql.test.ts +128 -28
  244. package/src/adapters/mysql/tools/__tests__/replication.test.ts +48 -2
  245. package/src/adapters/mysql/tools/__tests__/roles.test.ts +15 -18
  246. package/src/adapters/mysql/tools/__tests__/router.test.ts +32 -5
  247. package/src/adapters/mysql/tools/__tests__/security.test.ts +126 -2
  248. package/src/adapters/mysql/tools/__tests__/security_injection.test.ts +84 -76
  249. package/src/adapters/mysql/tools/__tests__/security_integration.test.ts +47 -50
  250. package/src/adapters/mysql/tools/__tests__/spatial.test.ts +11 -10
  251. package/src/adapters/mysql/tools/__tests__/spatial_handler.test.ts +54 -38
  252. package/src/adapters/mysql/tools/__tests__/stats.test.ts +285 -152
  253. package/src/adapters/mysql/tools/__tests__/transactions.test.ts +13 -13
  254. package/src/adapters/mysql/tools/admin/__tests__/backup.test.ts +171 -25
  255. package/src/adapters/mysql/tools/admin/__tests__/maintenance.test.ts +240 -4
  256. package/src/adapters/mysql/tools/admin/__tests__/monitoring-summary.test.ts +274 -0
  257. package/src/adapters/mysql/tools/admin/__tests__/monitoring.test.ts +94 -5
  258. package/src/adapters/mysql/tools/admin/backup.ts +193 -143
  259. package/src/adapters/mysql/tools/admin/maintenance.ts +118 -69
  260. package/src/adapters/mysql/tools/admin/monitoring.ts +201 -125
  261. package/src/adapters/mysql/tools/cluster/__tests__/group-replication.test.ts +69 -0
  262. package/src/adapters/mysql/tools/cluster/__tests__/innodb-cluster.test.ts +141 -0
  263. package/src/adapters/mysql/tools/cluster/group-replication.ts +172 -132
  264. package/src/adapters/mysql/tools/cluster/innodb-cluster.ts +231 -157
  265. package/src/adapters/mysql/tools/codemode/__tests__/codemode-tool.test.ts +227 -0
  266. package/src/adapters/mysql/tools/codemode/index.ts +5 -3
  267. package/src/adapters/mysql/tools/core.ts +152 -38
  268. package/src/adapters/mysql/tools/docstore.ts +422 -205
  269. package/src/adapters/mysql/tools/events.ts +334 -233
  270. package/src/adapters/mysql/tools/json/__tests__/core.test.ts +20 -0
  271. package/src/adapters/mysql/tools/json/__tests__/enhanced.test.ts +82 -50
  272. package/src/adapters/mysql/tools/json/__tests__/helpers.test.ts +42 -3
  273. package/src/adapters/mysql/tools/json/core.ts +21 -42
  274. package/src/adapters/mysql/tools/json/enhanced.ts +22 -37
  275. package/src/adapters/mysql/tools/json/helpers.ts +21 -25
  276. package/src/adapters/mysql/tools/partitioning.ts +3 -0
  277. package/src/adapters/mysql/tools/performance/__tests__/analysis.test.ts +98 -5
  278. package/src/adapters/mysql/tools/performance/__tests__/optimization-coverage.test.ts +515 -0
  279. package/src/adapters/mysql/tools/performance/__tests__/optimization.test.ts +187 -0
  280. package/src/adapters/mysql/tools/performance/analysis.ts +95 -69
  281. package/src/adapters/mysql/tools/performance/optimization.ts +182 -153
  282. package/src/adapters/mysql/tools/proxysql.ts +314 -209
  283. package/src/adapters/mysql/tools/replication.ts +84 -57
  284. package/src/adapters/mysql/tools/roles.ts +274 -226
  285. package/src/adapters/mysql/tools/router.ts +181 -85
  286. package/src/adapters/mysql/tools/schema/__tests__/constraints.test.ts +13 -0
  287. package/src/adapters/mysql/tools/schema/__tests__/management.test.ts +60 -25
  288. package/src/adapters/mysql/tools/schema/__tests__/scheduled_events.test.ts +11 -0
  289. package/src/adapters/mysql/tools/schema/__tests__/triggers.test.ts +25 -4
  290. package/src/adapters/mysql/tools/schema/__tests__/views.test.ts +46 -14
  291. package/src/adapters/mysql/tools/schema/constraints.ts +22 -3
  292. package/src/adapters/mysql/tools/schema/management.ts +60 -15
  293. package/src/adapters/mysql/tools/schema/routines.ts +26 -4
  294. package/src/adapters/mysql/tools/schema/scheduled_events.ts +25 -3
  295. package/src/adapters/mysql/tools/schema/triggers.ts +27 -2
  296. package/src/adapters/mysql/tools/schema/views.ts +46 -8
  297. package/src/adapters/mysql/tools/security/__tests__/audit.test.ts +90 -4
  298. package/src/adapters/mysql/tools/security/audit.ts +113 -39
  299. package/src/adapters/mysql/tools/security/data-protection.ts +293 -233
  300. package/src/adapters/mysql/tools/security/encryption.ts +172 -139
  301. package/src/adapters/mysql/tools/shell/__tests__/backup.test.ts +29 -0
  302. package/src/adapters/mysql/tools/shell/backup.ts +90 -73
  303. package/src/adapters/mysql/tools/shell/restore.ts +62 -48
  304. package/src/adapters/mysql/tools/spatial/__tests__/operations.test.ts +22 -14
  305. package/src/adapters/mysql/tools/spatial/__tests__/queries.test.ts +65 -51
  306. package/src/adapters/mysql/tools/spatial/geometry.ts +23 -7
  307. package/src/adapters/mysql/tools/spatial/operations.ts +60 -31
  308. package/src/adapters/mysql/tools/spatial/queries.ts +142 -65
  309. package/src/adapters/mysql/tools/spatial/setup.ts +121 -55
  310. package/src/adapters/mysql/tools/stats/__tests__/comparative.test.ts +12 -10
  311. package/src/adapters/mysql/tools/stats/comparative.ts +150 -98
  312. package/src/adapters/mysql/tools/stats/descriptive.ts +204 -127
  313. package/src/adapters/mysql/tools/sysschema/__tests__/error-paths.test.ts +222 -0
  314. package/src/adapters/mysql/tools/sysschema/__tests__/performance.test.ts +45 -0
  315. package/src/adapters/mysql/tools/sysschema/__tests__/resources.test.ts +6 -3
  316. package/src/adapters/mysql/tools/sysschema/activity.ts +52 -27
  317. package/src/adapters/mysql/tools/sysschema/performance.ts +132 -68
  318. package/src/adapters/mysql/tools/sysschema/resources.ts +105 -67
  319. package/src/adapters/mysql/tools/text/__tests__/fulltext.test.ts +45 -17
  320. package/src/adapters/mysql/tools/text/fulltext.ts +27 -38
  321. package/src/adapters/mysql/tools/transactions.ts +49 -24
  322. package/src/adapters/mysql/types/proxysql-types.ts +38 -1
  323. package/src/adapters/mysql/types/router-types.ts +1 -1
  324. package/src/adapters/mysql/types/shell-types.ts +2 -2
  325. package/src/adapters/mysql/types.ts +632 -19
  326. package/src/auth/__tests__/scopes.test.ts +2 -2
  327. package/src/auth/scopes.ts +1 -1
  328. package/src/codemode/__tests__/api.test.ts +417 -0
  329. package/src/codemode/__tests__/sandbox-factory.test.ts +158 -0
  330. package/src/codemode/__tests__/sandbox.test.ts +301 -0
  331. package/src/codemode/__tests__/security.test.ts +368 -0
  332. package/src/codemode/__tests__/worker-sandbox.test.ts +179 -0
  333. package/src/codemode/__tests__/worker-script.test.ts +226 -0
  334. package/src/codemode/api.ts +89 -5
  335. package/src/codemode/sandbox-factory.ts +1 -1
  336. package/src/codemode/types.ts +34 -0
  337. package/src/codemode/worker-sandbox.ts +74 -7
  338. package/src/codemode/worker-script.ts +157 -86
  339. package/src/constants/ServerInstructions.ts +37 -31
  340. package/src/filtering/ToolConstants.ts +1 -2
  341. package/src/filtering/__tests__/ToolFilter.test.ts +9 -9
  342. package/src/pool/ConnectionPool.ts +4 -1
  343. package/src/transports/__tests__/http.test.ts +15 -3
  344. package/src/transports/http.ts +12 -0
  345. package/src/utils/validators.ts +2 -1
  346. package/vitest.config.ts +3 -1
  347. package/CODE_MODE.md +0 -245
@@ -2,7 +2,7 @@
2
2
  * MySQL Roles Tools - 8 tools for role management
3
3
  */
4
4
 
5
- import { z } from "zod";
5
+ import { z, ZodError } from "zod";
6
6
  import type { MySQLAdapter } from "../MySQLAdapter.js";
7
7
  import type { ToolDefinition, RequestContext } from "../../../types/index.js";
8
8
  import {
@@ -12,6 +12,17 @@ import {
12
12
  escapeLikePattern,
13
13
  } from "../../../utils/validators.js";
14
14
 
15
+ function formatZodError(error: ZodError): string {
16
+ return error.issues.map((i) => i.message).join("; ");
17
+ }
18
+
19
+ function stripErrorPrefix(msg: string): string {
20
+ return msg
21
+ .replace(/^Raw query failed:\s*/i, "")
22
+ .replace(/^Query failed:\s*/i, "")
23
+ .replace(/^Execute failed:\s*/i, "");
24
+ }
25
+
15
26
  const RoleListSchema = z.object({
16
27
  pattern: z.string().optional().describe("Filter pattern (LIKE syntax)"),
17
28
  });
@@ -64,13 +75,21 @@ export function getRoleTools(adapter: MySQLAdapter): ToolDefinition[] {
64
75
  requiredScopes: ["read"],
65
76
  annotations: { readOnlyHint: true, idempotentHint: true },
66
77
  handler: async (params: unknown, _context: RequestContext) => {
67
- const { pattern } = RoleListSchema.parse(params);
68
- let query = `SELECT u.User as roleName, u.Host FROM mysql.user u
69
- WHERE u.account_locked='Y' AND u.password_expired='Y' AND u.authentication_string=''`;
70
- if (pattern)
71
- query += ` AND u.User LIKE '${escapeLikePattern(pattern)}'`;
72
- const result = await adapter.executeQuery(query);
73
- return { roles: result.rows ?? [], count: result.rows?.length ?? 0 };
78
+ try {
79
+ const { pattern } = RoleListSchema.parse(params);
80
+ let query = `SELECT u.User as roleName, u.Host FROM mysql.user u
81
+ WHERE u.account_locked='Y' AND u.password_expired='Y' AND u.authentication_string=''`;
82
+ if (pattern)
83
+ query += ` AND u.User LIKE '${escapeLikePattern(pattern)}'`;
84
+ const result = await adapter.executeQuery(query);
85
+ return { roles: result.rows ?? [], count: result.rows?.length ?? 0 };
86
+ } catch (error: unknown) {
87
+ if (error instanceof ZodError)
88
+ return { success: false, error: formatZodError(error) };
89
+ const message =
90
+ error instanceof Error ? error.message : String(error);
91
+ return { success: false, error: stripErrorPrefix(message) };
92
+ }
74
93
  },
75
94
  },
76
95
  {
@@ -82,40 +101,44 @@ export function getRoleTools(adapter: MySQLAdapter): ToolDefinition[] {
82
101
  requiredScopes: ["admin"],
83
102
  annotations: { readOnlyHint: false },
84
103
  handler: async (params: unknown, _context: RequestContext) => {
85
- const { name, ifNotExists } = RoleCreateSchema.parse(params);
86
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name))
87
- throw new Error("Invalid role name");
88
-
89
- // Pre-check existence for skipped indicator when ifNotExists is true
90
- if (ifNotExists) {
91
- const checkResult = await adapter.executeQuery(
92
- `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
93
- [name],
94
- );
95
- if (checkResult.rows && checkResult.rows.length > 0) {
96
- return {
97
- success: true,
98
- skipped: true,
99
- roleName: name,
100
- reason: "Role already exists",
101
- };
104
+ try {
105
+ const { name, ifNotExists } = RoleCreateSchema.parse(params);
106
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name))
107
+ return { success: false, error: "Invalid role name" };
108
+
109
+ // Pre-check existence for skipped indicator when ifNotExists is true
110
+ if (ifNotExists) {
111
+ const checkResult = await adapter.executeQuery(
112
+ `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
113
+ [name],
114
+ );
115
+ if (checkResult.rows && checkResult.rows.length > 0) {
116
+ return {
117
+ success: true,
118
+ skipped: true,
119
+ roleName: name,
120
+ reason: "Role already exists",
121
+ };
122
+ }
102
123
  }
103
- }
104
124
 
105
- try {
106
125
  const clause = ifNotExists ? "IF NOT EXISTS " : "";
107
126
  await adapter.executeQuery(`CREATE ROLE ${clause}'${name}'`);
108
127
  return { success: true, roleName: name };
109
128
  } catch (error: unknown) {
129
+ if (error instanceof ZodError)
130
+ return { success: false, error: formatZodError(error) };
110
131
  const message =
111
132
  error instanceof Error ? error.message : String(error);
112
133
  if (message.includes("Operation CREATE ROLE failed")) {
134
+ const rawName = (params as Record<string, string>)["name"];
135
+ const roleName = rawName ?? "unknown";
113
136
  return {
114
137
  success: false,
115
- reason: `Role '${name}' already exists`,
138
+ error: `Role '${roleName}' already exists`,
116
139
  };
117
140
  }
118
- throw error;
141
+ return { success: false, error: stripErrorPrefix(message) };
119
142
  }
120
143
  },
121
144
  },
@@ -128,48 +151,52 @@ export function getRoleTools(adapter: MySQLAdapter): ToolDefinition[] {
128
151
  requiredScopes: ["admin"],
129
152
  annotations: { readOnlyHint: false, destructiveHint: true },
130
153
  handler: async (params: unknown, _context: RequestContext) => {
131
- const { name, ifExists } = RoleDropSchema.parse(params);
132
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name))
133
- throw new Error("Invalid role name");
134
-
135
- // Pre-check existence for skipped indicator when ifExists is true
136
- let roleAbsent = false;
137
- if (ifExists) {
138
- const checkResult = await adapter.executeQuery(
139
- `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
140
- [name],
141
- );
142
- if (!checkResult.rows || checkResult.rows.length === 0) {
143
- roleAbsent = true;
154
+ try {
155
+ const { name, ifExists } = RoleDropSchema.parse(params);
156
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name))
157
+ return { success: false, error: "Invalid role name" };
158
+
159
+ // Pre-check existence for skipped indicator when ifExists is true
160
+ let roleAbsent = false;
161
+ if (ifExists) {
162
+ const checkResult = await adapter.executeQuery(
163
+ `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
164
+ [name],
165
+ );
166
+ if (!checkResult.rows || checkResult.rows.length === 0) {
167
+ roleAbsent = true;
168
+ }
144
169
  }
145
- }
146
170
 
147
- try {
148
171
  await adapter.executeQuery(
149
172
  `DROP ROLE ${ifExists ? "IF EXISTS " : ""}'${name}'`,
150
173
  );
174
+
175
+ if (roleAbsent) {
176
+ return {
177
+ success: true,
178
+ skipped: true,
179
+ roleName: name,
180
+ reason: "Role did not exist",
181
+ };
182
+ }
183
+
184
+ return { success: true, roleName: name };
151
185
  } catch (error: unknown) {
186
+ if (error instanceof ZodError)
187
+ return { success: false, error: formatZodError(error) };
152
188
  const message =
153
189
  error instanceof Error ? error.message : String(error);
154
190
  if (message.includes("Operation DROP ROLE failed")) {
191
+ const rawName = (params as Record<string, string>)["name"];
192
+ const roleName = rawName ?? "unknown";
155
193
  return {
156
194
  success: false,
157
- reason: `Role '${name}' does not exist`,
195
+ error: `Role '${roleName}' does not exist`,
158
196
  };
159
197
  }
160
- throw error;
198
+ return { success: false, error: stripErrorPrefix(message) };
161
199
  }
162
-
163
- if (roleAbsent) {
164
- return {
165
- success: true,
166
- skipped: true,
167
- roleName: name,
168
- reason: "Role did not exist",
169
- };
170
- }
171
-
172
- return { success: true, roleName: name };
173
200
  },
174
201
  },
175
202
  {
@@ -181,24 +208,32 @@ export function getRoleTools(adapter: MySQLAdapter): ToolDefinition[] {
181
208
  requiredScopes: ["read"],
182
209
  annotations: { readOnlyHint: true, idempotentHint: true },
183
210
  handler: async (params: unknown, _context: RequestContext) => {
184
- const { role } = RoleGrantsSchema.parse(params);
185
-
186
- // Validate role identifier before interpolation
187
- validateIdentifier(role, "column");
188
-
189
- // Check if role exists first (roles are locked accounts with empty auth string)
190
- const checkResult = await adapter.executeQuery(
191
- `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
192
- [role],
193
- );
194
- if (!checkResult.rows || checkResult.rows.length === 0) {
195
- return { role, grants: [], exists: false };
196
- }
211
+ try {
212
+ const { role } = RoleGrantsSchema.parse(params);
213
+
214
+ // Validate role identifier before interpolation
215
+ validateIdentifier(role, "role");
216
+
217
+ // Check if role exists first (roles are locked accounts with empty auth string)
218
+ const checkResult = await adapter.executeQuery(
219
+ `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
220
+ [role],
221
+ );
222
+ if (!checkResult.rows || checkResult.rows.length === 0) {
223
+ return { role, grants: [], exists: false };
224
+ }
197
225
 
198
- // SHOW GRANTS cannot be always prepared
199
- const result = await adapter.rawQuery(`SHOW GRANTS FOR '${role}'`);
200
- const grants = (result.rows ?? []).map((r) => Object.values(r)[0]);
201
- return { role, grants, exists: true };
226
+ // SHOW GRANTS cannot be always prepared
227
+ const result = await adapter.rawQuery(`SHOW GRANTS FOR '${role}'`);
228
+ const grants = (result.rows ?? []).map((r) => Object.values(r)[0]);
229
+ return { role, grants, exists: true };
230
+ } catch (error: unknown) {
231
+ if (error instanceof ZodError)
232
+ return { success: false, error: formatZodError(error) };
233
+ const message =
234
+ error instanceof Error ? error.message : String(error);
235
+ return { success: false, error: stripErrorPrefix(message) };
236
+ }
202
237
  },
203
238
  },
204
239
  {
@@ -210,57 +245,57 @@ export function getRoleTools(adapter: MySQLAdapter): ToolDefinition[] {
210
245
  requiredScopes: ["admin"],
211
246
  annotations: { readOnlyHint: false },
212
247
  handler: async (params: unknown, _context: RequestContext) => {
213
- const { role, privileges, database, table } =
214
- RoleGrantPrivilegeSchema.parse(params);
215
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(role))
216
- throw new Error("Invalid role name");
217
-
218
- // Validate each privilege against allowlist
219
- for (const priv of privileges) {
220
- validateMySQLPrivilege(priv);
221
- }
248
+ try {
249
+ const { role, privileges, database, table } =
250
+ RoleGrantPrivilegeSchema.parse(params);
251
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(role))
252
+ return { success: false, error: "Invalid role name" };
253
+
254
+ // Validate each privilege against allowlist
255
+ for (const priv of privileges) {
256
+ validateMySQLPrivilege(priv);
257
+ }
222
258
 
223
- // Check if role exists first
224
- const checkResult = await adapter.executeQuery(
225
- `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
226
- [role],
227
- );
228
- if (!checkResult.rows || checkResult.rows.length === 0) {
229
- return {
230
- success: false,
231
- role,
232
- exists: false,
233
- error: "Role does not exist",
234
- };
235
- }
259
+ // Check if role exists first
260
+ const checkResult = await adapter.executeQuery(
261
+ `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
262
+ [role],
263
+ );
264
+ if (!checkResult.rows || checkResult.rows.length === 0) {
265
+ return {
266
+ success: false,
267
+ role,
268
+ exists: false,
269
+ error: "Role does not exist",
270
+ };
271
+ }
236
272
 
237
- let targetDb = database;
238
- let targetTable = table;
273
+ let targetDb = database;
274
+ let targetTable = table;
239
275
 
240
- // Handle schema-qualified table names (e.g. "db.table")
241
- if (targetTable.includes(".") && targetTable !== "*") {
242
- const [dbPart, tablePart] = targetTable.split(".");
243
- if (dbPart && tablePart) {
244
- targetDb = dbPart;
245
- targetTable = tablePart;
276
+ // Handle schema-qualified table names (e.g. "db.table")
277
+ if (targetTable.includes(".") && targetTable !== "*") {
278
+ const [dbPart, tablePart] = targetTable.split(".");
279
+ if (dbPart && tablePart) {
280
+ targetDb = dbPart;
281
+ targetTable = tablePart;
282
+ }
246
283
  }
247
- }
248
284
 
249
- // Validate database and table identifiers when not wildcards
250
- if (targetDb !== "*") validateIdentifier(targetDb, "database");
251
- if (targetTable !== "*") validateIdentifier(targetTable, "table");
285
+ // Validate database and table identifiers when not wildcards
286
+ if (targetDb !== "*") validateIdentifier(targetDb, "database");
287
+ if (targetTable !== "*") validateIdentifier(targetTable, "table");
252
288
 
253
- const db = targetDb === "*" ? "*" : `\`${targetDb}\``;
254
- const tbl = targetTable === "*" ? "*" : `\`${targetTable}\``;
289
+ const db = targetDb === "*" ? "*" : `\`${targetDb}\``;
290
+ const tbl = targetTable === "*" ? "*" : `\`${targetTable}\``;
255
291
 
256
- let onClause = `${db}.${tbl}`;
257
- if (targetDb === "*" && targetTable !== "*") {
258
- // Cannot use * for db with specific table (MySQL syntax error)
259
- // Assume current database if table is specified but db is wildcard
260
- onClause = tbl;
261
- }
292
+ let onClause = `${db}.${tbl}`;
293
+ if (targetDb === "*" && targetTable !== "*") {
294
+ // Cannot use * for db with specific table (MySQL syntax error)
295
+ // Assume current database if table is specified but db is wildcard
296
+ onClause = tbl;
297
+ }
262
298
 
263
- try {
264
299
  await adapter.rawQuery(
265
300
  `GRANT ${privileges.join(", ")} ON ${onClause} TO '${role}'`,
266
301
  );
@@ -272,20 +307,19 @@ export function getRoleTools(adapter: MySQLAdapter): ToolDefinition[] {
272
307
  table: targetTable,
273
308
  };
274
309
  } catch (error: unknown) {
275
- const rawMessage =
310
+ if (error instanceof ZodError)
311
+ return { success: false, error: formatZodError(error) };
312
+ const message =
276
313
  error instanceof Error ? error.message : String(error);
277
- if (rawMessage.includes("doesn't exist")) {
278
- // Strip adapter prefix (e.g. "Raw query failed: Query failed: ") for clean output
279
- const cleanMessage = rawMessage
280
- .replace(/^Raw query failed:\s*/i, "")
281
- .replace(/^Query failed:\s*/i, "");
282
- return {
283
- success: false,
284
- role,
285
- error: cleanMessage,
286
- };
314
+ const cleanMsg = stripErrorPrefix(message);
315
+ // Include role context for table/db-level errors when role was parsed
316
+ const roleName = (params as Record<string, unknown>)["role"] as
317
+ | string
318
+ | undefined;
319
+ if (roleName) {
320
+ return { success: false, role: roleName, error: cleanMsg };
287
321
  }
288
- throw error;
322
+ return { success: false, error: cleanMsg };
289
323
  }
290
324
  },
291
325
  },
@@ -298,51 +332,54 @@ export function getRoleTools(adapter: MySQLAdapter): ToolDefinition[] {
298
332
  requiredScopes: ["admin"],
299
333
  annotations: { readOnlyHint: false },
300
334
  handler: async (params: unknown, _context: RequestContext) => {
301
- const { role, user, host, withAdminOption } =
302
- RoleAssignSchema.parse(params);
303
-
304
- // Validate all interpolated identifiers
305
- validateIdentifier(role, "column");
306
- validateMySQLUserHost(user, "user");
307
- validateMySQLUserHost(host, "host");
308
-
309
- // Check if role exists first
310
- const checkResult = await adapter.executeQuery(
311
- `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
312
- [role],
313
- );
314
- if (!checkResult.rows || checkResult.rows.length === 0) {
315
- return {
316
- success: false,
317
- role,
318
- user,
319
- host,
320
- exists: false,
321
- error: "Role does not exist",
322
- };
323
- }
324
-
325
- let sql = `GRANT '${role}' TO '${user}'@'${host}'`;
326
- if (withAdminOption) sql += " WITH ADMIN OPTION";
327
335
  try {
336
+ const { role, user, host, withAdminOption } =
337
+ RoleAssignSchema.parse(params);
338
+
339
+ // Validate all interpolated identifiers
340
+ validateIdentifier(role, "role");
341
+ validateMySQLUserHost(user, "user");
342
+ validateMySQLUserHost(host, "host");
343
+
344
+ // Check if role exists first
345
+ const checkResult = await adapter.executeQuery(
346
+ `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
347
+ [role],
348
+ );
349
+ if (!checkResult.rows || checkResult.rows.length === 0) {
350
+ return {
351
+ success: false,
352
+ role,
353
+ user,
354
+ host,
355
+ exists: false,
356
+ error: "Role does not exist",
357
+ };
358
+ }
359
+
360
+ let sql = `GRANT '${role}' TO '${user}'@'${host}'`;
361
+ if (withAdminOption) sql += " WITH ADMIN OPTION";
328
362
  await adapter.rawQuery(sql);
329
363
  await adapter.rawQuery(
330
364
  `SET DEFAULT ROLE '${role}' TO '${user}'@'${host}'`,
331
365
  );
332
366
  return { success: true, role, user, host };
333
367
  } catch (error: unknown) {
368
+ if (error instanceof ZodError)
369
+ return { success: false, error: formatZodError(error) };
334
370
  const message =
335
371
  error instanceof Error ? error.message : String(error);
336
372
  if (message.includes("Unknown authorization ID")) {
337
373
  return {
338
374
  success: false,
339
- role,
340
- user,
341
- host,
375
+ role: (params as Record<string, unknown>)["role"] as string,
376
+ user: (params as Record<string, unknown>)["user"] as string,
377
+ host:
378
+ ((params as Record<string, unknown>)["host"] as string) ?? "%",
342
379
  error: "User does not exist",
343
380
  };
344
381
  }
345
- throw error;
382
+ return { success: false, error: stripErrorPrefix(message) };
346
383
  }
347
384
  },
348
385
  },
@@ -355,76 +392,79 @@ export function getRoleTools(adapter: MySQLAdapter): ToolDefinition[] {
355
392
  requiredScopes: ["admin"],
356
393
  annotations: { readOnlyHint: false },
357
394
  handler: async (params: unknown, _context: RequestContext) => {
358
- const { role, user, host } = RoleRevokeSchema.parse(params);
359
-
360
- // Check if role exists first
361
- const checkResult = await adapter.executeQuery(
362
- `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
363
- [role],
364
- );
365
- if (!checkResult.rows || checkResult.rows.length === 0) {
366
- return {
367
- success: false,
368
- role,
369
- user,
370
- host,
371
- exists: false,
372
- error: "Role does not exist",
373
- };
374
- }
395
+ try {
396
+ const { role, user, host } = RoleRevokeSchema.parse(params);
375
397
 
376
- // P154: Check if user exists before checking assignment
377
- const userCheck = await adapter.executeQuery(
378
- `SELECT 1 FROM mysql.user WHERE User = ? AND Host = ?`,
379
- [user, host],
380
- );
381
- if (!userCheck.rows || userCheck.rows.length === 0) {
382
- return {
383
- success: false,
384
- role,
385
- user,
386
- host,
387
- error: "User does not exist",
388
- };
389
- }
398
+ // Check if role exists first
399
+ const checkResult = await adapter.executeQuery(
400
+ `SELECT 1 FROM mysql.user WHERE User = ? AND account_locked = 'Y' AND password_expired = 'Y' AND authentication_string = ''`,
401
+ [role],
402
+ );
403
+ if (!checkResult.rows || checkResult.rows.length === 0) {
404
+ return {
405
+ success: false,
406
+ role,
407
+ user,
408
+ host,
409
+ exists: false,
410
+ error: "Role does not exist",
411
+ };
412
+ }
390
413
 
391
- // Check if the role is actually assigned to this user
392
- const assignCheck = await adapter.executeQuery(
393
- `SELECT 1 FROM mysql.role_edges WHERE FROM_USER = ? AND FROM_HOST = '%' AND TO_USER = ? AND TO_HOST = ?`,
394
- [role, user, host],
395
- );
396
- if (!assignCheck.rows || assignCheck.rows.length === 0) {
397
- return {
398
- success: false,
399
- role,
400
- user,
401
- host,
402
- reason: `Role '${role}' is not assigned to user '${user}'@'${host}'`,
403
- };
404
- }
414
+ // P154: Check if user exists before checking assignment
415
+ const userCheck = await adapter.executeQuery(
416
+ `SELECT 1 FROM mysql.user WHERE User = ? AND Host = ?`,
417
+ [user, host],
418
+ );
419
+ if (!userCheck.rows || userCheck.rows.length === 0) {
420
+ return {
421
+ success: false,
422
+ role,
423
+ user,
424
+ host,
425
+ error: "User does not exist",
426
+ };
427
+ }
428
+
429
+ // Check if the role is actually assigned to this user
430
+ const assignCheck = await adapter.executeQuery(
431
+ `SELECT 1 FROM mysql.role_edges WHERE FROM_USER = ? AND FROM_HOST = '%' AND TO_USER = ? AND TO_HOST = ?`,
432
+ [role, user, host],
433
+ );
434
+ if (!assignCheck.rows || assignCheck.rows.length === 0) {
435
+ return {
436
+ success: false,
437
+ role,
438
+ user,
439
+ host,
440
+ error: `Role '${role}' is not assigned to user '${user}'@'${host}'`,
441
+ };
442
+ }
405
443
 
406
- try {
407
444
  // Validate before interpolation (role/user/host already validated by earlier checks
408
445
  // but validate user/host explicitly for this rawQuery)
409
- validateIdentifier(role, "column");
446
+ validateIdentifier(role, "role");
410
447
  validateMySQLUserHost(user, "user");
411
448
  validateMySQLUserHost(host, "host");
412
449
 
413
450
  await adapter.rawQuery(`REVOKE '${role}' FROM '${user}'@'${host}'`);
414
451
  return { success: true, role, user, host };
415
452
  } catch (error: unknown) {
453
+ if (error instanceof ZodError)
454
+ return { success: false, error: formatZodError(error) };
416
455
  const message =
417
456
  error instanceof Error ? error.message : String(error);
418
457
  if (message.includes("Unknown authorization ID")) {
419
458
  return {
420
459
  success: false,
421
- role,
422
- user,
423
- host,
460
+ role: (params as Record<string, unknown>)["role"] as string,
461
+ user: (params as Record<string, unknown>)["user"] as string,
462
+ host:
463
+ ((params as Record<string, unknown>)["host"] as string) ?? "%",
424
464
  error: "User does not exist",
425
465
  };
426
466
  }
427
- throw error;
467
+ return { success: false, error: stripErrorPrefix(message) };
428
468
  }
429
469
  },
430
470
  },
@@ -437,23 +477,31 @@ export function getRoleTools(adapter: MySQLAdapter): ToolDefinition[] {
437
477
  requiredScopes: ["read"],
438
478
  annotations: { readOnlyHint: true, idempotentHint: true },
439
479
  handler: async (params: unknown, _context: RequestContext) => {
440
- const { user, host } = UserRolesSchema.parse(params);
441
-
442
- // P154: Check if user exists
443
- const userCheck = await adapter.executeQuery(
444
- `SELECT 1 FROM mysql.user WHERE User = ? AND Host = ?`,
445
- [user, host],
446
- );
447
- if (!userCheck.rows || userCheck.rows.length === 0) {
448
- return { user, host, exists: false };
449
- }
480
+ try {
481
+ const { user, host } = UserRolesSchema.parse(params);
482
+
483
+ // P154: Check if user exists
484
+ const userCheck = await adapter.executeQuery(
485
+ `SELECT 1 FROM mysql.user WHERE User = ? AND Host = ?`,
486
+ [user, host],
487
+ );
488
+ if (!userCheck.rows || userCheck.rows.length === 0) {
489
+ return { user, host, exists: false };
490
+ }
450
491
 
451
- const result = await adapter.executeQuery(
452
- `SELECT FROM_USER as roleName, FROM_HOST as roleHost, WITH_ADMIN_OPTION as admin
453
- FROM mysql.role_edges WHERE TO_USER=? AND TO_HOST=?`,
454
- [user, host],
455
- );
456
- return { user, host, roles: result.rows ?? [] };
492
+ const result = await adapter.executeQuery(
493
+ `SELECT FROM_USER as roleName, FROM_HOST as roleHost, WITH_ADMIN_OPTION as admin
494
+ FROM mysql.role_edges WHERE TO_USER=? AND TO_HOST=?`,
495
+ [user, host],
496
+ );
497
+ return { user, host, roles: result.rows ?? [] };
498
+ } catch (error: unknown) {
499
+ if (error instanceof ZodError)
500
+ return { success: false, error: formatZodError(error) };
501
+ const message =
502
+ error instanceof Error ? error.message : String(error);
503
+ return { success: false, error: stripErrorPrefix(message) };
504
+ }
457
505
  },
458
506
  },
459
507
  ];