@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.
- package/.dockerignore +1 -0
- package/.gitattributes +18 -0
- package/.github/workflows/codeql.yml +2 -10
- package/.github/workflows/docker-publish.yml +15 -13
- package/CHANGELOG.md +287 -1
- package/DOCKER_README.md +100 -265
- package/Dockerfile +5 -0
- package/README.md +124 -59
- package/VERSION +1 -1
- package/dist/__tests__/mocks/adapter.d.ts.map +1 -1
- package/dist/__tests__/mocks/adapter.js +2 -0
- package/dist/__tests__/mocks/adapter.js.map +1 -1
- package/dist/adapters/DatabaseAdapter.d.ts.map +1 -1
- package/dist/adapters/DatabaseAdapter.js +50 -9
- package/dist/adapters/DatabaseAdapter.js.map +1 -1
- package/dist/adapters/mysql/MySQLAdapter.d.ts +6 -0
- package/dist/adapters/mysql/MySQLAdapter.d.ts.map +1 -1
- package/dist/adapters/mysql/MySQLAdapter.js +8 -0
- package/dist/adapters/mysql/MySQLAdapter.js.map +1 -1
- package/dist/adapters/mysql/SchemaManager.js +16 -15
- package/dist/adapters/mysql/SchemaManager.js.map +1 -1
- package/dist/adapters/mysql/prompts/index.js +10 -20
- package/dist/adapters/mysql/prompts/index.js.map +1 -1
- package/dist/adapters/mysql/prompts/proxysqlSetup.js +1 -1
- package/dist/adapters/mysql/resources/docstore.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/docstore.js +10 -7
- package/dist/adapters/mysql/resources/docstore.js.map +1 -1
- package/dist/adapters/mysql/resources/events.js +11 -8
- package/dist/adapters/mysql/resources/events.js.map +1 -1
- package/dist/adapters/mysql/resources/indexes.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/indexes.js +12 -15
- package/dist/adapters/mysql/resources/indexes.js.map +1 -1
- package/dist/adapters/mysql/resources/innodb.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/innodb.js +20 -17
- package/dist/adapters/mysql/resources/innodb.js.map +1 -1
- package/dist/adapters/mysql/resources/locks.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/locks.js +9 -6
- package/dist/adapters/mysql/resources/locks.js.map +1 -1
- package/dist/adapters/mysql/resources/performance.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/performance.js +15 -15
- package/dist/adapters/mysql/resources/performance.js.map +1 -1
- package/dist/adapters/mysql/resources/spatial.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/spatial.js +9 -6
- package/dist/adapters/mysql/resources/spatial.js.map +1 -1
- package/dist/adapters/mysql/resources/sysschema.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/sysschema.js +12 -9
- package/dist/adapters/mysql/resources/sysschema.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/backup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/backup.js +170 -121
- package/dist/adapters/mysql/tools/admin/backup.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/maintenance.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/maintenance.js +106 -57
- package/dist/adapters/mysql/tools/admin/maintenance.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/monitoring.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/monitoring.js +183 -101
- package/dist/adapters/mysql/tools/admin/monitoring.js.map +1 -1
- package/dist/adapters/mysql/tools/cluster/group-replication.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/cluster/group-replication.js +164 -120
- package/dist/adapters/mysql/tools/cluster/group-replication.js.map +1 -1
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.js +212 -145
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.js.map +1 -1
- package/dist/adapters/mysql/tools/codemode/index.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/codemode/index.js +6 -4
- package/dist/adapters/mysql/tools/codemode/index.js.map +1 -1
- package/dist/adapters/mysql/tools/core.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/core.js +152 -29
- package/dist/adapters/mysql/tools/core.js.map +1 -1
- package/dist/adapters/mysql/tools/docstore.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/docstore.js +340 -163
- package/dist/adapters/mysql/tools/docstore.js.map +1 -1
- package/dist/adapters/mysql/tools/events.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/events.js +284 -198
- package/dist/adapters/mysql/tools/events.js.map +1 -1
- package/dist/adapters/mysql/tools/json/core.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/core.js +11 -39
- package/dist/adapters/mysql/tools/json/core.js.map +1 -1
- package/dist/adapters/mysql/tools/json/enhanced.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/enhanced.js +15 -33
- package/dist/adapters/mysql/tools/json/enhanced.js.map +1 -1
- package/dist/adapters/mysql/tools/json/helpers.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/helpers.js +13 -24
- package/dist/adapters/mysql/tools/json/helpers.js.map +1 -1
- package/dist/adapters/mysql/tools/partitioning.js +3 -0
- package/dist/adapters/mysql/tools/partitioning.js.map +1 -1
- package/dist/adapters/mysql/tools/performance/analysis.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/performance/analysis.js +89 -60
- package/dist/adapters/mysql/tools/performance/analysis.js.map +1 -1
- package/dist/adapters/mysql/tools/performance/optimization.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/performance/optimization.js +151 -127
- package/dist/adapters/mysql/tools/performance/optimization.js.map +1 -1
- package/dist/adapters/mysql/tools/proxysql.d.ts +1 -1
- package/dist/adapters/mysql/tools/proxysql.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/proxysql.js +289 -176
- package/dist/adapters/mysql/tools/proxysql.js.map +1 -1
- package/dist/adapters/mysql/tools/replication.js +75 -49
- package/dist/adapters/mysql/tools/replication.js.map +1 -1
- package/dist/adapters/mysql/tools/roles.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/roles.js +224 -182
- package/dist/adapters/mysql/tools/roles.js.map +1 -1
- package/dist/adapters/mysql/tools/router.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/router.js +168 -67
- package/dist/adapters/mysql/tools/router.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/constraints.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/constraints.js +21 -3
- package/dist/adapters/mysql/tools/schema/constraints.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/management.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/management.js +61 -14
- package/dist/adapters/mysql/tools/schema/management.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/routines.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/routines.js +27 -4
- package/dist/adapters/mysql/tools/schema/routines.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/scheduled_events.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/scheduled_events.js +24 -3
- package/dist/adapters/mysql/tools/schema/scheduled_events.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/triggers.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/triggers.js +23 -2
- package/dist/adapters/mysql/tools/schema/triggers.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/views.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/views.js +47 -7
- package/dist/adapters/mysql/tools/schema/views.js.map +1 -1
- package/dist/adapters/mysql/tools/security/audit.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/security/audit.js +102 -34
- package/dist/adapters/mysql/tools/security/audit.js.map +1 -1
- package/dist/adapters/mysql/tools/security/data-protection.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/security/data-protection.js +264 -205
- package/dist/adapters/mysql/tools/security/data-protection.js.map +1 -1
- package/dist/adapters/mysql/tools/security/encryption.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/security/encryption.js +137 -104
- package/dist/adapters/mysql/tools/security/encryption.js.map +1 -1
- package/dist/adapters/mysql/tools/shell/backup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/shell/backup.js +71 -59
- package/dist/adapters/mysql/tools/shell/backup.js.map +1 -1
- package/dist/adapters/mysql/tools/shell/restore.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/shell/restore.js +61 -47
- package/dist/adapters/mysql/tools/shell/restore.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/geometry.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/geometry.js +19 -5
- package/dist/adapters/mysql/tools/spatial/geometry.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/operations.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/operations.js +42 -17
- package/dist/adapters/mysql/tools/spatial/operations.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/queries.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/queries.js +109 -57
- package/dist/adapters/mysql/tools/spatial/queries.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/setup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/setup.js +103 -50
- package/dist/adapters/mysql/tools/spatial/setup.js.map +1 -1
- package/dist/adapters/mysql/tools/stats/comparative.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/stats/comparative.js +128 -79
- package/dist/adapters/mysql/tools/stats/comparative.js.map +1 -1
- package/dist/adapters/mysql/tools/stats/descriptive.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/stats/descriptive.js +174 -102
- package/dist/adapters/mysql/tools/stats/descriptive.js.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/activity.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/activity.js +50 -25
- package/dist/adapters/mysql/tools/sysschema/activity.js.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/performance.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/performance.js +121 -66
- package/dist/adapters/mysql/tools/sysschema/performance.js.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/resources.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/resources.js +101 -64
- package/dist/adapters/mysql/tools/sysschema/resources.js.map +1 -1
- package/dist/adapters/mysql/tools/text/fulltext.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/text/fulltext.js +18 -32
- package/dist/adapters/mysql/tools/text/fulltext.js.map +1 -1
- package/dist/adapters/mysql/tools/transactions.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/transactions.js +48 -23
- package/dist/adapters/mysql/tools/transactions.js.map +1 -1
- package/dist/adapters/mysql/types/proxysql-types.d.ts +15 -0
- package/dist/adapters/mysql/types/proxysql-types.d.ts.map +1 -1
- package/dist/adapters/mysql/types/proxysql-types.js +33 -1
- package/dist/adapters/mysql/types/proxysql-types.js.map +1 -1
- package/dist/adapters/mysql/types/router-types.d.ts +1 -1
- package/dist/adapters/mysql/types/router-types.js +1 -1
- package/dist/adapters/mysql/types/router-types.js.map +1 -1
- package/dist/adapters/mysql/types/shell-types.js +2 -2
- package/dist/adapters/mysql/types/shell-types.js.map +1 -1
- package/dist/adapters/mysql/types.d.ts +485 -21
- package/dist/adapters/mysql/types.d.ts.map +1 -1
- package/dist/adapters/mysql/types.js +546 -19
- package/dist/adapters/mysql/types.js.map +1 -1
- package/dist/auth/scopes.js +1 -1
- package/dist/auth/scopes.js.map +1 -1
- package/dist/codemode/api.d.ts +3 -2
- package/dist/codemode/api.d.ts.map +1 -1
- package/dist/codemode/api.js +80 -5
- package/dist/codemode/api.js.map +1 -1
- package/dist/codemode/sandbox-factory.js +1 -1
- package/dist/codemode/sandbox-factory.js.map +1 -1
- package/dist/codemode/types.d.ts +26 -0
- package/dist/codemode/types.d.ts.map +1 -1
- package/dist/codemode/types.js +2 -0
- package/dist/codemode/types.js.map +1 -1
- package/dist/codemode/worker-sandbox.d.ts +4 -2
- package/dist/codemode/worker-sandbox.d.ts.map +1 -1
- package/dist/codemode/worker-sandbox.js +66 -7
- package/dist/codemode/worker-sandbox.js.map +1 -1
- package/dist/codemode/worker-script.d.ts +3 -0
- package/dist/codemode/worker-script.d.ts.map +1 -1
- package/dist/codemode/worker-script.js +128 -75
- package/dist/codemode/worker-script.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +37 -31
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/filtering/ToolConstants.d.ts +1 -1
- package/dist/filtering/ToolConstants.d.ts.map +1 -1
- package/dist/filtering/ToolConstants.js +1 -2
- package/dist/filtering/ToolConstants.js.map +1 -1
- package/dist/pool/ConnectionPool.d.ts.map +1 -1
- package/dist/pool/ConnectionPool.js.map +1 -1
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +6 -0
- package/dist/transports/http.js.map +1 -1
- package/dist/utils/validators.d.ts +1 -1
- package/dist/utils/validators.d.ts.map +1 -1
- package/dist/utils/validators.js.map +1 -1
- package/package.json +4 -4
- package/releases/v2.3.0-release-notes.md +20 -20
- package/releases/v2.3.1-release-notes.md +34 -0
- package/releases/v3.0.0-release-notes.md +81 -0
- package/src/__tests__/mocks/adapter.ts +3 -0
- package/src/__tests__/perf.test.ts +6 -6
- package/src/adapters/DatabaseAdapter.ts +58 -9
- package/src/adapters/__tests__/DatabaseAdapter.test.ts +89 -8
- package/src/adapters/mysql/MySQLAdapter.ts +17 -2
- package/src/adapters/mysql/SchemaManager.ts +21 -21
- package/src/adapters/mysql/__tests__/MySQLAdapter.test.ts +1 -1
- package/src/adapters/mysql/prompts/index.ts +12 -22
- package/src/adapters/mysql/prompts/proxysqlSetup.ts +1 -1
- package/src/adapters/mysql/resources/docstore.ts +13 -10
- package/src/adapters/mysql/resources/events.ts +12 -12
- package/src/adapters/mysql/resources/indexes.ts +17 -19
- package/src/adapters/mysql/resources/innodb.ts +23 -22
- package/src/adapters/mysql/resources/locks.ts +9 -7
- package/src/adapters/mysql/resources/performance.ts +23 -18
- package/src/adapters/mysql/resources/spatial.ts +9 -7
- package/src/adapters/mysql/resources/sysschema.ts +12 -11
- package/src/adapters/mysql/tools/__tests__/core.test.ts +126 -55
- package/src/adapters/mysql/tools/__tests__/docstore.test.ts +459 -88
- package/src/adapters/mysql/tools/__tests__/events.test.ts +281 -103
- package/src/adapters/mysql/tools/__tests__/proxysql.test.ts +128 -28
- package/src/adapters/mysql/tools/__tests__/replication.test.ts +48 -2
- package/src/adapters/mysql/tools/__tests__/roles.test.ts +15 -18
- package/src/adapters/mysql/tools/__tests__/router.test.ts +32 -5
- package/src/adapters/mysql/tools/__tests__/security.test.ts +126 -2
- package/src/adapters/mysql/tools/__tests__/security_injection.test.ts +84 -76
- package/src/adapters/mysql/tools/__tests__/security_integration.test.ts +47 -50
- package/src/adapters/mysql/tools/__tests__/spatial.test.ts +11 -10
- package/src/adapters/mysql/tools/__tests__/spatial_handler.test.ts +54 -38
- package/src/adapters/mysql/tools/__tests__/stats.test.ts +285 -152
- package/src/adapters/mysql/tools/__tests__/transactions.test.ts +13 -13
- package/src/adapters/mysql/tools/admin/__tests__/backup.test.ts +171 -25
- package/src/adapters/mysql/tools/admin/__tests__/maintenance.test.ts +240 -4
- package/src/adapters/mysql/tools/admin/__tests__/monitoring-summary.test.ts +274 -0
- package/src/adapters/mysql/tools/admin/__tests__/monitoring.test.ts +94 -5
- package/src/adapters/mysql/tools/admin/backup.ts +193 -143
- package/src/adapters/mysql/tools/admin/maintenance.ts +118 -69
- package/src/adapters/mysql/tools/admin/monitoring.ts +201 -125
- package/src/adapters/mysql/tools/cluster/__tests__/group-replication.test.ts +69 -0
- package/src/adapters/mysql/tools/cluster/__tests__/innodb-cluster.test.ts +141 -0
- package/src/adapters/mysql/tools/cluster/group-replication.ts +172 -132
- package/src/adapters/mysql/tools/cluster/innodb-cluster.ts +231 -157
- package/src/adapters/mysql/tools/codemode/__tests__/codemode-tool.test.ts +227 -0
- package/src/adapters/mysql/tools/codemode/index.ts +5 -3
- package/src/adapters/mysql/tools/core.ts +152 -38
- package/src/adapters/mysql/tools/docstore.ts +422 -205
- package/src/adapters/mysql/tools/events.ts +334 -233
- package/src/adapters/mysql/tools/json/__tests__/core.test.ts +20 -0
- package/src/adapters/mysql/tools/json/__tests__/enhanced.test.ts +82 -50
- package/src/adapters/mysql/tools/json/__tests__/helpers.test.ts +42 -3
- package/src/adapters/mysql/tools/json/core.ts +21 -42
- package/src/adapters/mysql/tools/json/enhanced.ts +22 -37
- package/src/adapters/mysql/tools/json/helpers.ts +21 -25
- package/src/adapters/mysql/tools/partitioning.ts +3 -0
- package/src/adapters/mysql/tools/performance/__tests__/analysis.test.ts +98 -5
- package/src/adapters/mysql/tools/performance/__tests__/optimization-coverage.test.ts +515 -0
- package/src/adapters/mysql/tools/performance/__tests__/optimization.test.ts +187 -0
- package/src/adapters/mysql/tools/performance/analysis.ts +95 -69
- package/src/adapters/mysql/tools/performance/optimization.ts +182 -153
- package/src/adapters/mysql/tools/proxysql.ts +314 -209
- package/src/adapters/mysql/tools/replication.ts +84 -57
- package/src/adapters/mysql/tools/roles.ts +274 -226
- package/src/adapters/mysql/tools/router.ts +181 -85
- package/src/adapters/mysql/tools/schema/__tests__/constraints.test.ts +13 -0
- package/src/adapters/mysql/tools/schema/__tests__/management.test.ts +60 -25
- package/src/adapters/mysql/tools/schema/__tests__/scheduled_events.test.ts +11 -0
- package/src/adapters/mysql/tools/schema/__tests__/triggers.test.ts +25 -4
- package/src/adapters/mysql/tools/schema/__tests__/views.test.ts +46 -14
- package/src/adapters/mysql/tools/schema/constraints.ts +22 -3
- package/src/adapters/mysql/tools/schema/management.ts +60 -15
- package/src/adapters/mysql/tools/schema/routines.ts +26 -4
- package/src/adapters/mysql/tools/schema/scheduled_events.ts +25 -3
- package/src/adapters/mysql/tools/schema/triggers.ts +27 -2
- package/src/adapters/mysql/tools/schema/views.ts +46 -8
- package/src/adapters/mysql/tools/security/__tests__/audit.test.ts +90 -4
- package/src/adapters/mysql/tools/security/audit.ts +113 -39
- package/src/adapters/mysql/tools/security/data-protection.ts +293 -233
- package/src/adapters/mysql/tools/security/encryption.ts +172 -139
- package/src/adapters/mysql/tools/shell/__tests__/backup.test.ts +29 -0
- package/src/adapters/mysql/tools/shell/backup.ts +90 -73
- package/src/adapters/mysql/tools/shell/restore.ts +62 -48
- package/src/adapters/mysql/tools/spatial/__tests__/operations.test.ts +22 -14
- package/src/adapters/mysql/tools/spatial/__tests__/queries.test.ts +65 -51
- package/src/adapters/mysql/tools/spatial/geometry.ts +23 -7
- package/src/adapters/mysql/tools/spatial/operations.ts +60 -31
- package/src/adapters/mysql/tools/spatial/queries.ts +142 -65
- package/src/adapters/mysql/tools/spatial/setup.ts +121 -55
- package/src/adapters/mysql/tools/stats/__tests__/comparative.test.ts +12 -10
- package/src/adapters/mysql/tools/stats/comparative.ts +150 -98
- package/src/adapters/mysql/tools/stats/descriptive.ts +204 -127
- package/src/adapters/mysql/tools/sysschema/__tests__/error-paths.test.ts +222 -0
- package/src/adapters/mysql/tools/sysschema/__tests__/performance.test.ts +45 -0
- package/src/adapters/mysql/tools/sysschema/__tests__/resources.test.ts +6 -3
- package/src/adapters/mysql/tools/sysschema/activity.ts +52 -27
- package/src/adapters/mysql/tools/sysschema/performance.ts +132 -68
- package/src/adapters/mysql/tools/sysschema/resources.ts +105 -67
- package/src/adapters/mysql/tools/text/__tests__/fulltext.test.ts +45 -17
- package/src/adapters/mysql/tools/text/fulltext.ts +27 -38
- package/src/adapters/mysql/tools/transactions.ts +49 -24
- package/src/adapters/mysql/types/proxysql-types.ts +38 -1
- package/src/adapters/mysql/types/router-types.ts +1 -1
- package/src/adapters/mysql/types/shell-types.ts +2 -2
- package/src/adapters/mysql/types.ts +632 -19
- package/src/auth/__tests__/scopes.test.ts +2 -2
- package/src/auth/scopes.ts +1 -1
- package/src/codemode/__tests__/api.test.ts +417 -0
- package/src/codemode/__tests__/sandbox-factory.test.ts +158 -0
- package/src/codemode/__tests__/sandbox.test.ts +301 -0
- package/src/codemode/__tests__/security.test.ts +368 -0
- package/src/codemode/__tests__/worker-sandbox.test.ts +179 -0
- package/src/codemode/__tests__/worker-script.test.ts +226 -0
- package/src/codemode/api.ts +89 -5
- package/src/codemode/sandbox-factory.ts +1 -1
- package/src/codemode/types.ts +34 -0
- package/src/codemode/worker-sandbox.ts +74 -7
- package/src/codemode/worker-script.ts +157 -86
- package/src/constants/ServerInstructions.ts +37 -31
- package/src/filtering/ToolConstants.ts +1 -2
- package/src/filtering/__tests__/ToolFilter.test.ts +9 -9
- package/src/pool/ConnectionPool.ts +4 -1
- package/src/transports/__tests__/http.test.ts +15 -3
- package/src/transports/http.ts +12 -0
- package/src/utils/validators.ts +2 -1
- package/vitest.config.ts +3 -1
- 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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
138
|
+
error: `Role '${roleName}' already exists`,
|
|
116
139
|
};
|
|
117
140
|
}
|
|
118
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
195
|
+
error: `Role '${roleName}' does not exist`,
|
|
158
196
|
};
|
|
159
197
|
}
|
|
160
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
238
|
-
|
|
273
|
+
let targetDb = database;
|
|
274
|
+
let targetTable = table;
|
|
239
275
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
285
|
+
// Validate database and table identifiers when not wildcards
|
|
286
|
+
if (targetDb !== "*") validateIdentifier(targetDb, "database");
|
|
287
|
+
if (targetTable !== "*") validateIdentifier(targetTable, "table");
|
|
252
288
|
|
|
253
|
-
|
|
254
|
-
|
|
289
|
+
const db = targetDb === "*" ? "*" : `\`${targetDb}\``;
|
|
290
|
+
const tbl = targetTable === "*" ? "*" : `\`${targetTable}\``;
|
|
255
291
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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, "
|
|
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
|
-
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
];
|