@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.
- package/.github/workflows/docker-publish.yml +1 -2
- package/CHANGELOG.md +85 -0
- package/CODE_MODE.md +245 -0
- package/DOCKER_README.md +59 -36
- package/README.md +65 -42
- package/VERSION +1 -1
- package/dist/adapters/mysql/MySQLAdapter.d.ts +4 -0
- package/dist/adapters/mysql/MySQLAdapter.d.ts.map +1 -1
- package/dist/adapters/mysql/MySQLAdapter.js +9 -0
- package/dist/adapters/mysql/MySQLAdapter.js.map +1 -1
- package/dist/adapters/mysql/prompts/index.d.ts +8 -1
- package/dist/adapters/mysql/prompts/index.d.ts.map +1 -1
- package/dist/adapters/mysql/prompts/index.js +8 -1
- package/dist/adapters/mysql/prompts/index.js.map +1 -1
- package/dist/adapters/mysql/prompts/routerSetup.d.ts.map +1 -1
- package/dist/adapters/mysql/prompts/routerSetup.js +5 -0
- package/dist/adapters/mysql/prompts/routerSetup.js.map +1 -1
- package/dist/adapters/mysql/resources/capabilities.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/capabilities.js +6 -5
- package/dist/adapters/mysql/resources/capabilities.js.map +1 -1
- package/dist/adapters/mysql/resources/index.d.ts +9 -1
- package/dist/adapters/mysql/resources/index.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/index.js +9 -1
- package/dist/adapters/mysql/resources/index.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/backup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/backup.js +3 -3
- 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 +5 -5
- package/dist/adapters/mysql/tools/admin/maintenance.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 +26 -5
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.js.map +1 -1
- package/dist/adapters/mysql/tools/codemode/index.d.ts +38 -0
- package/dist/adapters/mysql/tools/codemode/index.d.ts.map +1 -0
- package/dist/adapters/mysql/tools/codemode/index.js +203 -0
- package/dist/adapters/mysql/tools/codemode/index.js.map +1 -0
- package/dist/adapters/mysql/tools/core.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/core.js +32 -20
- package/dist/adapters/mysql/tools/core.js.map +1 -1
- package/dist/adapters/mysql/tools/events.js +18 -6
- 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 +5 -5
- package/dist/adapters/mysql/tools/json/core.js.map +1 -1
- package/dist/adapters/mysql/tools/json/helpers.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/helpers.js +9 -3
- package/dist/adapters/mysql/tools/json/helpers.js.map +1 -1
- package/dist/adapters/mysql/tools/partitioning.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/partitioning.js +38 -6
- 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 +67 -20
- 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 +36 -6
- package/dist/adapters/mysql/tools/performance/optimization.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 +9 -4
- package/dist/adapters/mysql/tools/security/data-protection.js.map +1 -1
- package/dist/adapters/mysql/tools/shell/common.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/shell/common.js +28 -2
- package/dist/adapters/mysql/tools/shell/common.js.map +1 -1
- package/dist/adapters/mysql/tools/shell/restore.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/shell/restore.js +54 -4
- package/dist/adapters/mysql/tools/shell/restore.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/operations.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/operations.js +10 -2
- package/dist/adapters/mysql/tools/spatial/operations.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/setup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/setup.js +18 -0
- package/dist/adapters/mysql/tools/spatial/setup.js.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/resources.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/resources.js +5 -0
- 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 +6 -4
- package/dist/adapters/mysql/tools/text/fulltext.js.map +1 -1
- package/dist/adapters/mysql/tools/text/processing.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/text/processing.js +10 -45
- package/dist/adapters/mysql/tools/text/processing.js.map +1 -1
- package/dist/adapters/mysql/tools/transactions.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/transactions.js +8 -8
- package/dist/adapters/mysql/tools/transactions.js.map +1 -1
- package/dist/adapters/mysql/types.d.ts +968 -78
- package/dist/adapters/mysql/types.d.ts.map +1 -1
- package/dist/adapters/mysql/types.js +1084 -78
- package/dist/adapters/mysql/types.js.map +1 -1
- package/dist/auth/scopes.d.ts.map +1 -1
- package/dist/auth/scopes.js +1 -0
- package/dist/auth/scopes.js.map +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +12 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/codemode/api.d.ts +69 -0
- package/dist/codemode/api.d.ts.map +1 -0
- package/dist/codemode/api.js +1035 -0
- package/dist/codemode/api.js.map +1 -0
- package/dist/codemode/index.d.ts +13 -0
- package/dist/codemode/index.d.ts.map +1 -0
- package/dist/codemode/index.js +17 -0
- package/dist/codemode/index.js.map +1 -0
- package/dist/codemode/sandbox-factory.d.ts +72 -0
- package/dist/codemode/sandbox-factory.d.ts.map +1 -0
- package/dist/codemode/sandbox-factory.js +88 -0
- package/dist/codemode/sandbox-factory.js.map +1 -0
- package/dist/codemode/sandbox.d.ts +96 -0
- package/dist/codemode/sandbox.d.ts.map +1 -0
- package/dist/codemode/sandbox.js +345 -0
- package/dist/codemode/sandbox.js.map +1 -0
- package/dist/codemode/security.d.ts +44 -0
- package/dist/codemode/security.d.ts.map +1 -0
- package/dist/codemode/security.js +149 -0
- package/dist/codemode/security.js.map +1 -0
- package/dist/codemode/types.d.ts +137 -0
- package/dist/codemode/types.d.ts.map +1 -0
- package/dist/codemode/types.js +46 -0
- package/dist/codemode/types.js.map +1 -0
- package/dist/codemode/worker-sandbox.d.ts +82 -0
- package/dist/codemode/worker-sandbox.d.ts.map +1 -0
- package/dist/codemode/worker-sandbox.js +244 -0
- package/dist/codemode/worker-sandbox.js.map +1 -0
- package/dist/codemode/worker-script.d.ts +8 -0
- package/dist/codemode/worker-script.d.ts.map +1 -0
- package/dist/codemode/worker-script.js +113 -0
- package/dist/codemode/worker-script.js.map +1 -0
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +33 -9
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/filtering/ToolConstants.d.ts +11 -11
- package/dist/filtering/ToolConstants.d.ts.map +1 -1
- package/dist/filtering/ToolConstants.js +37 -19
- package/dist/filtering/ToolConstants.js.map +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +12 -0
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/server/McpServer.js +1 -1
- package/dist/server/McpServer.js.map +1 -1
- package/dist/types/modules/server.d.ts +2 -0
- package/dist/types/modules/server.d.ts.map +1 -1
- package/dist/types/modules/tools.d.ts +1 -1
- package/dist/types/modules/tools.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/package.json +12 -7
- package/releases/v2.2.0-release-notes.md +18 -18
- package/releases/v2.3.0-release-notes.md +191 -0
- package/src/__tests__/perf.test.ts +12 -12
- package/src/adapters/mysql/MySQLAdapter.ts +10 -0
- package/src/adapters/mysql/__tests__/MySQLAdapter.test.ts +1 -1
- package/src/adapters/mysql/prompts/index.ts +8 -1
- package/src/adapters/mysql/prompts/routerSetup.ts +5 -0
- package/src/adapters/mysql/resources/__tests__/capabilities.test.ts +50 -1
- package/src/adapters/mysql/resources/capabilities.ts +6 -4
- package/src/adapters/mysql/resources/index.ts +9 -1
- package/src/adapters/mysql/tools/__tests__/core.test.ts +68 -0
- package/src/adapters/mysql/tools/__tests__/events.test.ts +56 -2
- package/src/adapters/mysql/tools/__tests__/json_core.test.ts +1 -1
- package/src/adapters/mysql/tools/__tests__/json_helpers.test.ts +46 -4
- package/src/adapters/mysql/tools/__tests__/replication.test.ts +144 -42
- package/src/adapters/mysql/tools/__tests__/security.test.ts +39 -0
- package/src/adapters/mysql/tools/__tests__/spatial.test.ts +39 -7
- package/src/adapters/mysql/tools/__tests__/spatial_handler.test.ts +35 -3
- package/src/adapters/mysql/tools/__tests__/transactions.test.ts +3 -5
- package/src/adapters/mysql/tools/admin/backup.ts +8 -3
- package/src/adapters/mysql/tools/admin/maintenance.ts +8 -4
- package/src/adapters/mysql/tools/cluster/__tests__/innodb-cluster.test.ts +35 -0
- package/src/adapters/mysql/tools/cluster/innodb-cluster.ts +26 -5
- package/src/adapters/mysql/tools/codemode/index.ts +249 -0
- package/src/adapters/mysql/tools/core.ts +44 -27
- package/src/adapters/mysql/tools/events.ts +23 -7
- package/src/adapters/mysql/tools/json/__tests__/helpers.test.ts +59 -14
- package/src/adapters/mysql/tools/json/core.ts +8 -4
- package/src/adapters/mysql/tools/json/helpers.ts +13 -3
- package/src/adapters/mysql/tools/partitioning.ts +53 -6
- package/src/adapters/mysql/tools/performance/__tests__/analysis.test.ts +227 -4
- package/src/adapters/mysql/tools/performance/__tests__/optimization.test.ts +35 -0
- package/src/adapters/mysql/tools/performance/analysis.ts +75 -21
- package/src/adapters/mysql/tools/performance/optimization.ts +44 -6
- package/src/adapters/mysql/tools/security/data-protection.ts +10 -4
- package/src/adapters/mysql/tools/shell/__tests__/common.test.ts +46 -0
- package/src/adapters/mysql/tools/shell/__tests__/restore.test.ts +28 -1
- package/src/adapters/mysql/tools/shell/common.ts +34 -2
- package/src/adapters/mysql/tools/shell/restore.ts +70 -7
- package/src/adapters/mysql/tools/spatial/__tests__/operations.test.ts +29 -0
- package/src/adapters/mysql/tools/spatial/operations.ts +13 -2
- package/src/adapters/mysql/tools/spatial/setup.ts +23 -0
- package/src/adapters/mysql/tools/sysschema/__tests__/resources.test.ts +21 -0
- package/src/adapters/mysql/tools/sysschema/resources.ts +5 -0
- package/src/adapters/mysql/tools/text/fulltext.ts +13 -5
- package/src/adapters/mysql/tools/text/processing.ts +20 -49
- package/src/adapters/mysql/tools/transactions.ts +11 -7
- package/src/adapters/mysql/types.ts +1241 -87
- package/src/auth/scopes.ts +1 -0
- package/src/cli/args.ts +14 -0
- package/src/codemode/api.ts +1224 -0
- package/src/codemode/index.ts +51 -0
- package/src/codemode/sandbox-factory.ts +146 -0
- package/src/codemode/sandbox.ts +450 -0
- package/src/codemode/security.ts +188 -0
- package/src/codemode/types.ts +194 -0
- package/src/codemode/worker-sandbox.ts +326 -0
- package/src/codemode/worker-script.ts +144 -0
- package/src/constants/ServerInstructions.ts +33 -9
- package/src/filtering/ToolConstants.ts +37 -19
- package/src/filtering/ToolFilter.ts +15 -0
- package/src/filtering/__tests__/ToolFilter.test.ts +65 -38
- package/src/server/McpServer.ts +1 -1
- package/src/types/modules/server.ts +3 -0
- package/src/types/modules/tools.ts +2 -1
- package/src/utils/logger.ts +2 -1
|
@@ -289,6 +289,45 @@ describe("Security Tools", () => {
|
|
|
289
289
|
expect(result.type).toBe("credit_card");
|
|
290
290
|
expect(result.warning).toContain("too short");
|
|
291
291
|
});
|
|
292
|
+
|
|
293
|
+
it("should fully mask 8-digit credit card values with warning", async () => {
|
|
294
|
+
const tool = tools.find((t) => t.name === "mysql_security_mask_data");
|
|
295
|
+
const result = (await tool?.handler(
|
|
296
|
+
{ value: "12345678", type: "credit_card" },
|
|
297
|
+
mockContext,
|
|
298
|
+
)) as any;
|
|
299
|
+
|
|
300
|
+
expect(result.original).toBe("12345678");
|
|
301
|
+
expect(result.masked).toBe("********");
|
|
302
|
+
expect(result.type).toBe("credit_card");
|
|
303
|
+
expect(result.warning).toContain("too short");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("should return warning when partial masking is ineffective", async () => {
|
|
307
|
+
const tool = tools.find((t) => t.name === "mysql_security_mask_data");
|
|
308
|
+
const result = (await tool?.handler(
|
|
309
|
+
{ value: "AB", type: "partial", keepFirst: 5, keepLast: 5 },
|
|
310
|
+
mockContext,
|
|
311
|
+
)) as any;
|
|
312
|
+
|
|
313
|
+
expect(result.original).toBe("AB");
|
|
314
|
+
expect(result.masked).toBe("AB");
|
|
315
|
+
expect(result.type).toBe("partial");
|
|
316
|
+
expect(result.warning).toContain("Masking ineffective");
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("should return warning for empty string partial masking", async () => {
|
|
320
|
+
const tool = tools.find((t) => t.name === "mysql_security_mask_data");
|
|
321
|
+
const result = (await tool?.handler(
|
|
322
|
+
{ value: "", type: "partial", keepFirst: 0, keepLast: 0 },
|
|
323
|
+
mockContext,
|
|
324
|
+
)) as any;
|
|
325
|
+
|
|
326
|
+
expect(result.original).toBe("");
|
|
327
|
+
expect(result.masked).toBe("");
|
|
328
|
+
expect(result.type).toBe("partial");
|
|
329
|
+
expect(result.warning).toContain("Masking ineffective");
|
|
330
|
+
});
|
|
292
331
|
});
|
|
293
332
|
|
|
294
333
|
describe("mysql_security_password_validate", () => {
|
|
@@ -115,11 +115,12 @@ describe("Handler Execution", () => {
|
|
|
115
115
|
|
|
116
116
|
describe("mysql_spatial_create_index", () => {
|
|
117
117
|
it("should create a spatial index", async () => {
|
|
118
|
-
// First call
|
|
118
|
+
// First call: column info (NOT NULL), second: no existing index, third: CREATE
|
|
119
119
|
mockAdapter.executeQuery
|
|
120
120
|
.mockResolvedValueOnce(
|
|
121
121
|
createMockQueryResult([{ IS_NULLABLE: "NO", DATA_TYPE: "point" }]),
|
|
122
122
|
)
|
|
123
|
+
.mockResolvedValueOnce(createMockQueryResult([]))
|
|
123
124
|
.mockResolvedValueOnce(createMockQueryResult([]));
|
|
124
125
|
|
|
125
126
|
const tool = tools.find((t) => t.name === "mysql_spatial_create_index")!;
|
|
@@ -132,8 +133,8 @@ describe("Handler Execution", () => {
|
|
|
132
133
|
mockContext,
|
|
133
134
|
);
|
|
134
135
|
|
|
135
|
-
expect(mockAdapter.executeQuery).toHaveBeenCalledTimes(
|
|
136
|
-
const call = mockAdapter.executeQuery.mock.calls[
|
|
136
|
+
expect(mockAdapter.executeQuery).toHaveBeenCalledTimes(3);
|
|
137
|
+
const call = mockAdapter.executeQuery.mock.calls[2][0] as string;
|
|
137
138
|
expect(call).toContain("SPATIAL INDEX");
|
|
138
139
|
expect(result).toHaveProperty("success", true);
|
|
139
140
|
});
|
|
@@ -162,13 +163,18 @@ describe("Handler Execution", () => {
|
|
|
162
163
|
});
|
|
163
164
|
});
|
|
164
165
|
|
|
165
|
-
it("should
|
|
166
|
-
// First call
|
|
166
|
+
it("should return structured reason for duplicate index", async () => {
|
|
167
|
+
// First call: column info, second: no existing index, third: fails with duplicate key
|
|
167
168
|
mockAdapter.executeQuery
|
|
168
169
|
.mockResolvedValueOnce(
|
|
169
170
|
createMockQueryResult([{ IS_NULLABLE: "NO", DATA_TYPE: "point" }]),
|
|
170
171
|
)
|
|
171
|
-
.
|
|
172
|
+
.mockResolvedValueOnce(createMockQueryResult([]))
|
|
173
|
+
.mockRejectedValueOnce(
|
|
174
|
+
new Error(
|
|
175
|
+
"Query failed: Execute failed: Duplicate key name 'idx_locations_geom'",
|
|
176
|
+
),
|
|
177
|
+
);
|
|
172
178
|
|
|
173
179
|
const tool = tools.find((t) => t.name === "mysql_spatial_create_index")!;
|
|
174
180
|
const result = await tool.handler(
|
|
@@ -182,7 +188,33 @@ describe("Handler Execution", () => {
|
|
|
182
188
|
|
|
183
189
|
expect(result).toEqual({
|
|
184
190
|
success: false,
|
|
185
|
-
|
|
191
|
+
reason:
|
|
192
|
+
"Index 'idx_locations_geom' already exists on table 'locations'",
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should handle other index creation errors gracefully", async () => {
|
|
197
|
+
// First call: column info, second: no existing index, third: fails with generic error
|
|
198
|
+
mockAdapter.executeQuery
|
|
199
|
+
.mockResolvedValueOnce(
|
|
200
|
+
createMockQueryResult([{ IS_NULLABLE: "NO", DATA_TYPE: "point" }]),
|
|
201
|
+
)
|
|
202
|
+
.mockResolvedValueOnce(createMockQueryResult([]))
|
|
203
|
+
.mockRejectedValueOnce(new Error("Some other MySQL error"));
|
|
204
|
+
|
|
205
|
+
const tool = tools.find((t) => t.name === "mysql_spatial_create_index")!;
|
|
206
|
+
const result = await tool.handler(
|
|
207
|
+
{
|
|
208
|
+
table: "locations",
|
|
209
|
+
column: "geom",
|
|
210
|
+
indexName: "idx_locations_geom",
|
|
211
|
+
},
|
|
212
|
+
mockContext,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(result).toEqual({
|
|
216
|
+
success: false,
|
|
217
|
+
error: "Some other MySQL error",
|
|
186
218
|
});
|
|
187
219
|
});
|
|
188
220
|
});
|
|
@@ -87,11 +87,12 @@ describe("Spatial Tools Handlers", () => {
|
|
|
87
87
|
|
|
88
88
|
it("should generate default index name if not provided", async () => {
|
|
89
89
|
const tool = findTool("mysql_spatial_create_index")!;
|
|
90
|
-
// First call
|
|
90
|
+
// First call: column info (NOT NULL), second: no existing index, third: CREATE
|
|
91
91
|
mockAdapter.executeQuery
|
|
92
92
|
.mockResolvedValueOnce(
|
|
93
93
|
createMockQueryResult([{ IS_NULLABLE: "NO", DATA_TYPE: "point" }]),
|
|
94
94
|
)
|
|
95
|
+
.mockResolvedValueOnce(createMockQueryResult([]))
|
|
95
96
|
.mockResolvedValueOnce(createMockQueryResult([]));
|
|
96
97
|
|
|
97
98
|
const result = await tool.handler(
|
|
@@ -102,15 +103,44 @@ describe("Spatial Tools Handlers", () => {
|
|
|
102
103
|
mockContext,
|
|
103
104
|
);
|
|
104
105
|
|
|
105
|
-
expect(mockAdapter.executeQuery).toHaveBeenCalledTimes(
|
|
106
|
+
expect(mockAdapter.executeQuery).toHaveBeenCalledTimes(3);
|
|
106
107
|
expect(mockAdapter.executeQuery).toHaveBeenNthCalledWith(
|
|
107
|
-
|
|
108
|
+
3,
|
|
108
109
|
expect.stringContaining(
|
|
109
110
|
"CREATE SPATIAL INDEX `idx_spatial_users_location`",
|
|
110
111
|
),
|
|
111
112
|
);
|
|
112
113
|
expect(result).toHaveProperty("indexName", "idx_spatial_users_location");
|
|
113
114
|
});
|
|
115
|
+
|
|
116
|
+
it("should detect existing spatial index on same column", async () => {
|
|
117
|
+
const tool = findTool("mysql_spatial_create_index")!;
|
|
118
|
+
// First call: column info (NOT NULL)
|
|
119
|
+
// Second call: existing spatial index found
|
|
120
|
+
mockAdapter.executeQuery
|
|
121
|
+
.mockResolvedValueOnce(
|
|
122
|
+
createMockQueryResult([{ IS_NULLABLE: "NO", DATA_TYPE: "point" }]),
|
|
123
|
+
)
|
|
124
|
+
.mockResolvedValueOnce(
|
|
125
|
+
createMockQueryResult([{ INDEX_NAME: "idx_existing_geom" }]),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const result = await tool.handler(
|
|
129
|
+
{
|
|
130
|
+
table: "locations",
|
|
131
|
+
column: "geom",
|
|
132
|
+
},
|
|
133
|
+
mockContext,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(result).toEqual({
|
|
137
|
+
success: false,
|
|
138
|
+
reason:
|
|
139
|
+
"Spatial index 'idx_existing_geom' already exists on column 'geom' of table 'locations'",
|
|
140
|
+
});
|
|
141
|
+
// Should have called: 1) column check, 2) existing index check — NOT the CREATE
|
|
142
|
+
expect(mockAdapter.executeQuery).toHaveBeenCalledTimes(2);
|
|
143
|
+
});
|
|
114
144
|
});
|
|
115
145
|
|
|
116
146
|
describe("mysql_spatial_distance", () => {
|
|
@@ -475,6 +505,7 @@ describe("Spatial Tools Handlers", () => {
|
|
|
475
505
|
|
|
476
506
|
expect((result as any).segmentsApplied).toBe(false);
|
|
477
507
|
expect((result as any).segments).toBe(4);
|
|
508
|
+
expect((result as any).precision).toBe(6);
|
|
478
509
|
});
|
|
479
510
|
|
|
480
511
|
it("should include segmentsApplied: true for Cartesian SRID (buffer)", async () => {
|
|
@@ -501,6 +532,7 @@ describe("Spatial Tools Handlers", () => {
|
|
|
501
532
|
|
|
502
533
|
expect((result as any).segmentsApplied).toBe(true);
|
|
503
534
|
expect((result as any).segments).toBe(4);
|
|
535
|
+
expect((result as any).precision).toBe(6);
|
|
504
536
|
});
|
|
505
537
|
});
|
|
506
538
|
});
|
|
@@ -382,11 +382,9 @@ describe("Handler Execution", () => {
|
|
|
382
382
|
it("should reject empty statements array", async () => {
|
|
383
383
|
const tool = tools.find((t) => t.name === "mysql_transaction_execute")!;
|
|
384
384
|
const result = await tool.handler({ statements: [] }, mockContext);
|
|
385
|
-
|
|
386
|
-
expect(result).
|
|
387
|
-
|
|
388
|
-
reason: "No statements provided. Pass at least one SQL statement.",
|
|
389
|
-
});
|
|
385
|
+
expect(result).toHaveProperty("success", false);
|
|
386
|
+
expect(result).toHaveProperty("reason");
|
|
387
|
+
expect((result as { reason: string }).reason).toContain("No statements");
|
|
390
388
|
expect(mockAdapter.beginTransaction).not.toHaveBeenCalled();
|
|
391
389
|
});
|
|
392
390
|
|
|
@@ -10,7 +10,12 @@ import type {
|
|
|
10
10
|
ToolDefinition,
|
|
11
11
|
RequestContext,
|
|
12
12
|
} from "../../../../types/index.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ExportTableSchema,
|
|
15
|
+
ExportTableSchemaBase,
|
|
16
|
+
ImportDataSchema,
|
|
17
|
+
ImportDataSchemaBase,
|
|
18
|
+
} from "../../types.js";
|
|
14
19
|
import { z } from "zod";
|
|
15
20
|
import {
|
|
16
21
|
validateIdentifier,
|
|
@@ -94,7 +99,7 @@ export function createExportTableTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
94
99
|
title: "MySQL Export Table",
|
|
95
100
|
description: "Export table data as SQL INSERT statements or CSV format.",
|
|
96
101
|
group: "backup",
|
|
97
|
-
inputSchema:
|
|
102
|
+
inputSchema: ExportTableSchemaBase,
|
|
98
103
|
requiredScopes: ["read"],
|
|
99
104
|
annotations: {
|
|
100
105
|
readOnlyHint: true,
|
|
@@ -175,7 +180,7 @@ export function createImportDataTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
175
180
|
title: "MySQL Import Data",
|
|
176
181
|
description: "Import data into a table from an array of row objects.",
|
|
177
182
|
group: "backup",
|
|
178
|
-
inputSchema:
|
|
183
|
+
inputSchema: ImportDataSchemaBase,
|
|
179
184
|
requiredScopes: ["write"],
|
|
180
185
|
annotations: {
|
|
181
186
|
readOnlyHint: false,
|
|
@@ -12,9 +12,13 @@ import type {
|
|
|
12
12
|
} from "../../../../types/index.js";
|
|
13
13
|
import {
|
|
14
14
|
OptimizeTableSchema,
|
|
15
|
+
OptimizeTableSchemaBase,
|
|
15
16
|
AnalyzeTableSchema,
|
|
17
|
+
AnalyzeTableSchemaBase,
|
|
16
18
|
CheckTableSchema,
|
|
19
|
+
CheckTableSchemaBase,
|
|
17
20
|
FlushTablesSchema,
|
|
21
|
+
FlushTablesSchemaBase,
|
|
18
22
|
KillQuerySchema,
|
|
19
23
|
} from "../../types.js";
|
|
20
24
|
import { z } from "zod";
|
|
@@ -25,7 +29,7 @@ export function createOptimizeTableTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
25
29
|
title: "MySQL Optimize Table",
|
|
26
30
|
description: "Optimize tables to reclaim unused space and defragment data.",
|
|
27
31
|
group: "admin",
|
|
28
|
-
inputSchema:
|
|
32
|
+
inputSchema: OptimizeTableSchemaBase,
|
|
29
33
|
requiredScopes: ["admin"],
|
|
30
34
|
annotations: {
|
|
31
35
|
readOnlyHint: false,
|
|
@@ -47,7 +51,7 @@ export function createAnalyzeTableTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
47
51
|
description:
|
|
48
52
|
"Analyze tables to update index statistics for the query optimizer.",
|
|
49
53
|
group: "admin",
|
|
50
|
-
inputSchema:
|
|
54
|
+
inputSchema: AnalyzeTableSchemaBase,
|
|
51
55
|
requiredScopes: ["admin"],
|
|
52
56
|
annotations: {
|
|
53
57
|
readOnlyHint: false,
|
|
@@ -68,7 +72,7 @@ export function createCheckTableTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
68
72
|
title: "MySQL Check Table",
|
|
69
73
|
description: "Check tables for errors.",
|
|
70
74
|
group: "admin",
|
|
71
|
-
inputSchema:
|
|
75
|
+
inputSchema: CheckTableSchemaBase,
|
|
72
76
|
requiredScopes: ["read"],
|
|
73
77
|
annotations: {
|
|
74
78
|
readOnlyHint: true,
|
|
@@ -125,7 +129,7 @@ export function createFlushTablesTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
125
129
|
title: "MySQL Flush Tables",
|
|
126
130
|
description: "Flush tables to ensure data is written to disk.",
|
|
127
131
|
group: "admin",
|
|
128
|
-
inputSchema:
|
|
132
|
+
inputSchema: FlushTablesSchemaBase,
|
|
129
133
|
requiredScopes: ["admin"],
|
|
130
134
|
annotations: {
|
|
131
135
|
readOnlyHint: false,
|
|
@@ -210,6 +210,41 @@ describe("InnoDB Cluster Tools", () => {
|
|
|
210
210
|
expect(result.routers[0].attributes.ROEndpoint).toBe("6447");
|
|
211
211
|
expect(result.routers[0].attributes.Configuration).toBeUndefined();
|
|
212
212
|
});
|
|
213
|
+
|
|
214
|
+
it("should flag stale routers when lastCheckIn is null or old", async () => {
|
|
215
|
+
const recentTime = new Date().toISOString();
|
|
216
|
+
mockAdapter.executeQuery.mockResolvedValue(
|
|
217
|
+
createMockQueryResult([
|
|
218
|
+
{
|
|
219
|
+
routerId: 1,
|
|
220
|
+
routerName: "active-router",
|
|
221
|
+
address: "192.168.1.1",
|
|
222
|
+
lastCheckIn: recentTime,
|
|
223
|
+
attributes: JSON.stringify({ ROEndpoint: "6447" }),
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
routerId: 2,
|
|
227
|
+
routerName: "stale-router",
|
|
228
|
+
address: "192.168.1.2",
|
|
229
|
+
lastCheckIn: null,
|
|
230
|
+
attributes: JSON.stringify({ ROEndpoint: "6447" }),
|
|
231
|
+
},
|
|
232
|
+
]),
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const tool = createClusterRouterStatusTool(
|
|
236
|
+
mockAdapter as unknown as MySQLAdapter,
|
|
237
|
+
);
|
|
238
|
+
const result = (await tool.handler(
|
|
239
|
+
{ summary: false },
|
|
240
|
+
mockContext,
|
|
241
|
+
)) as any;
|
|
242
|
+
|
|
243
|
+
expect(result.routers).toHaveLength(2);
|
|
244
|
+
expect(result.routers[0].isStale).toBe(false);
|
|
245
|
+
expect(result.routers[1].isStale).toBe(true);
|
|
246
|
+
expect(result.staleCount).toBe(1);
|
|
247
|
+
});
|
|
213
248
|
});
|
|
214
249
|
|
|
215
250
|
describe("createClusterStatusTool - payload optimization", () => {
|
|
@@ -353,6 +353,14 @@ export function createClusterRouterStatusTool(
|
|
|
353
353
|
handler: async (params: unknown, _context: RequestContext) => {
|
|
354
354
|
const { summary } = SummarySchema.parse(params);
|
|
355
355
|
|
|
356
|
+
// Compute staleness: null lastCheckIn or >1 hour old
|
|
357
|
+
const computeStale = (lastCheckIn: unknown): boolean => {
|
|
358
|
+
if (lastCheckIn == null) return true;
|
|
359
|
+
const checkInTime = new Date(lastCheckIn as string).getTime();
|
|
360
|
+
if (isNaN(checkInTime)) return true;
|
|
361
|
+
return Date.now() - checkInTime > 3_600_000; // 1 hour
|
|
362
|
+
};
|
|
363
|
+
|
|
356
364
|
try {
|
|
357
365
|
// Summary mode: return only essential router info
|
|
358
366
|
if (summary) {
|
|
@@ -369,9 +377,16 @@ export function createClusterRouterStatusTool(
|
|
|
369
377
|
FROM mysql_innodb_cluster_metadata.routers
|
|
370
378
|
`);
|
|
371
379
|
|
|
380
|
+
const routers = (result.rows ?? []).map((r) => ({
|
|
381
|
+
...r,
|
|
382
|
+
isStale: computeStale(r["lastCheckIn"]),
|
|
383
|
+
}));
|
|
384
|
+
const staleCount = routers.filter((r) => r.isStale).length;
|
|
385
|
+
|
|
372
386
|
return {
|
|
373
|
-
routers
|
|
374
|
-
count:
|
|
387
|
+
routers,
|
|
388
|
+
count: routers.length,
|
|
389
|
+
staleCount,
|
|
375
390
|
};
|
|
376
391
|
}
|
|
377
392
|
|
|
@@ -388,6 +403,7 @@ export function createClusterRouterStatusTool(
|
|
|
388
403
|
`);
|
|
389
404
|
|
|
390
405
|
const routers = (result.rows ?? []).map((r) => {
|
|
406
|
+
let processed = r;
|
|
391
407
|
if (r["attributes"] != null) {
|
|
392
408
|
try {
|
|
393
409
|
const attrs =
|
|
@@ -395,17 +411,22 @@ export function createClusterRouterStatusTool(
|
|
|
395
411
|
? (JSON.parse(r["attributes"]) as Record<string, unknown>)
|
|
396
412
|
: (r["attributes"] as Record<string, unknown>);
|
|
397
413
|
delete attrs["Configuration"];
|
|
398
|
-
|
|
414
|
+
processed = { ...r, attributes: attrs };
|
|
399
415
|
} catch {
|
|
400
|
-
|
|
416
|
+
// Keep original if parsing fails
|
|
401
417
|
}
|
|
402
418
|
}
|
|
403
|
-
return
|
|
419
|
+
return {
|
|
420
|
+
...processed,
|
|
421
|
+
isStale: computeStale(processed["lastCheckIn"]),
|
|
422
|
+
};
|
|
404
423
|
});
|
|
424
|
+
const staleCount = routers.filter((r) => r.isStale).length;
|
|
405
425
|
|
|
406
426
|
return {
|
|
407
427
|
routers,
|
|
408
428
|
count: routers.length,
|
|
429
|
+
staleCount,
|
|
409
430
|
};
|
|
410
431
|
} catch {
|
|
411
432
|
return {
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mysql-mcp - Code Mode Tool: mysql_execute_code
|
|
3
|
+
*
|
|
4
|
+
* MCP tool that executes LLM-generated code in a sandboxed environment
|
|
5
|
+
* with access to all MySQL tools via the mysql.* API.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import type { MySQLAdapter } from "../../MySQLAdapter.js";
|
|
10
|
+
import type {
|
|
11
|
+
ToolDefinition,
|
|
12
|
+
RequestContext,
|
|
13
|
+
} from "../../../../types/index.js";
|
|
14
|
+
import {
|
|
15
|
+
createSandboxPool,
|
|
16
|
+
type ISandboxPool,
|
|
17
|
+
type SandboxMode,
|
|
18
|
+
} from "../../../../codemode/sandbox-factory.js";
|
|
19
|
+
import { CodeModeSecurityManager } from "../../../../codemode/security.js";
|
|
20
|
+
import { createMysqlApi } from "../../../../codemode/api.js";
|
|
21
|
+
import type { ExecuteCodeOptions } from "../../../../codemode/types.js";
|
|
22
|
+
|
|
23
|
+
// Schema for mysql_execute_code input
|
|
24
|
+
export const ExecuteCodeSchema = z.object({
|
|
25
|
+
code: z
|
|
26
|
+
.string()
|
|
27
|
+
.describe(
|
|
28
|
+
"TypeScript/JavaScript code to execute. Use mysql.{group}.{method}() for database operations.",
|
|
29
|
+
),
|
|
30
|
+
timeout: z
|
|
31
|
+
.number()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Execution timeout in milliseconds (max 30000, default 30000)"),
|
|
34
|
+
readonly: z
|
|
35
|
+
.boolean()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("If true, restricts to read-only operations"),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Schema for mysql_execute_code output
|
|
41
|
+
export const ExecuteCodeOutputSchema = z.object({
|
|
42
|
+
success: z.boolean().describe("Whether the code executed successfully"),
|
|
43
|
+
result: z
|
|
44
|
+
.unknown()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Return value from the executed code"),
|
|
47
|
+
error: z.string().optional().describe("Error message if execution failed"),
|
|
48
|
+
metrics: z
|
|
49
|
+
.object({
|
|
50
|
+
wallTimeMs: z
|
|
51
|
+
.number()
|
|
52
|
+
.describe("Wall clock execution time in milliseconds"),
|
|
53
|
+
cpuTimeMs: z.number().describe("CPU time used in milliseconds"),
|
|
54
|
+
memoryUsedMb: z.number().describe("Memory used in megabytes"),
|
|
55
|
+
})
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("Execution performance metrics"),
|
|
58
|
+
hint: z.string().optional().describe("Helpful tip or additional information"),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Singleton instances (initialized on first use)
|
|
62
|
+
let sandboxPool: ISandboxPool | null = null;
|
|
63
|
+
let securityManager: CodeModeSecurityManager | null = null;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get isolation mode from environment variable
|
|
67
|
+
*/
|
|
68
|
+
function getIsolationMode(): SandboxMode {
|
|
69
|
+
const envMode = process.env["CODEMODE_ISOLATION"];
|
|
70
|
+
if (envMode === "worker") return "worker";
|
|
71
|
+
return "vm"; // Default
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Initialize Code Mode infrastructure
|
|
76
|
+
*/
|
|
77
|
+
function ensureInitialized(): {
|
|
78
|
+
pool: ISandboxPool;
|
|
79
|
+
security: CodeModeSecurityManager;
|
|
80
|
+
} {
|
|
81
|
+
sandboxPool ??= createSandboxPool(getIsolationMode());
|
|
82
|
+
sandboxPool.initialize();
|
|
83
|
+
securityManager ??= new CodeModeSecurityManager();
|
|
84
|
+
return { pool: sandboxPool, security: securityManager };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create the mysql_execute_code tool
|
|
89
|
+
*/
|
|
90
|
+
export function createExecuteCodeTool(adapter: MySQLAdapter): ToolDefinition {
|
|
91
|
+
return {
|
|
92
|
+
name: "mysql_execute_code",
|
|
93
|
+
title: "MySQL Execute Code",
|
|
94
|
+
description: `Execute TypeScript/JavaScript code in a sandboxed environment with access to all MySQL tools via the mysql.* API.
|
|
95
|
+
|
|
96
|
+
Available API groups:
|
|
97
|
+
- mysql.core: readQuery, writeQuery, listTables, describeTable, createTable, createIndex (8 methods)
|
|
98
|
+
- mysql.transactions: begin, commit, rollback, savepoint, execute (7 methods)
|
|
99
|
+
- mysql.json: extract, set, insert, remove, contains, keys, merge, diff, stats (17 methods)
|
|
100
|
+
- mysql.text: regexpMatch, likeSearch, soundex, substring, concat, collationConvert (6 methods)
|
|
101
|
+
- mysql.fulltext: fulltextSearch, fulltextCreate, fulltextBoolean, fulltextExpand (5 methods)
|
|
102
|
+
- mysql.performance: explain, explainAnalyze, slowQueries, bufferPoolStats, tableStats (8 methods)
|
|
103
|
+
- mysql.optimization: indexRecommendation, queryRewrite, forceIndex, optimizerTrace (4 methods)
|
|
104
|
+
- mysql.admin: optimizeTable, analyzeTable, checkTable, repairTable, flushTables, killQuery (6 methods)
|
|
105
|
+
- mysql.monitoring: showProcesslist, showStatus, showVariables, innodbStatus, poolStats (7 methods)
|
|
106
|
+
- mysql.backup: createDump, exportTable, importData, restoreDump (4 methods)
|
|
107
|
+
- mysql.replication: masterStatus, slaveStatus, binlogEvents, gtidStatus, replicationLag (5 methods)
|
|
108
|
+
- mysql.partitioning: partitionInfo, addPartition, dropPartition, reorganizePartition (4 methods)
|
|
109
|
+
- mysql.schema: listSchemas, createView, listFunctions, listTriggers (10 methods)
|
|
110
|
+
- mysql.events: eventCreate, eventAlter, eventDrop, eventList, schedulerStatus (6 methods)
|
|
111
|
+
- mysql.sysschema: sysSchemaStats, sysStatementSummary, sysIoSummary (8 methods)
|
|
112
|
+
- mysql.stats: descriptive, percentiles, correlation, regression, timeSeries, histogram (8 methods)
|
|
113
|
+
- mysql.spatial: distance, distanceSphere, point, polygon, buffer (12 methods)
|
|
114
|
+
- mysql.security: sslStatus, userPrivileges, audit, sensitiveTables (9 methods)
|
|
115
|
+
- mysql.cluster: clusterStatus, grStatus, grMembers, clusterTopology (10 methods)
|
|
116
|
+
- mysql.roles: roleCreate, roleGrant, roleAssign, roleList (8 methods)
|
|
117
|
+
- mysql.docstore: docCreateCollection, docFind, docAdd, docModify (9 methods)
|
|
118
|
+
- mysql.router: routerStatus, routerRoutes, routerRouteHealth (9 methods)
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
\`\`\`javascript
|
|
122
|
+
const tables = await mysql.core.listTables();
|
|
123
|
+
const results = [];
|
|
124
|
+
for (const t of tables.tables) {
|
|
125
|
+
const count = await mysql.core.readQuery(\`SELECT COUNT(*) as n FROM \\\`\${t.name}\\\`\`);
|
|
126
|
+
results.push({ table: t.name, rows: count.rows[0].n });
|
|
127
|
+
}
|
|
128
|
+
return results;
|
|
129
|
+
\`\`\``,
|
|
130
|
+
group: "codemode",
|
|
131
|
+
inputSchema: ExecuteCodeSchema,
|
|
132
|
+
requiredScopes: ["admin"],
|
|
133
|
+
annotations: {
|
|
134
|
+
readOnlyHint: false,
|
|
135
|
+
destructiveHint: true,
|
|
136
|
+
idempotentHint: false,
|
|
137
|
+
openWorldHint: false,
|
|
138
|
+
},
|
|
139
|
+
handler: async (params: unknown, _context: RequestContext) => {
|
|
140
|
+
const { code, readonly } = params as ExecuteCodeOptions;
|
|
141
|
+
|
|
142
|
+
// Initialize infrastructure
|
|
143
|
+
const { pool, security } = ensureInitialized();
|
|
144
|
+
|
|
145
|
+
// Validate code
|
|
146
|
+
const validation = security.validateCode(code);
|
|
147
|
+
if (!validation.valid) {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: `Code validation failed: ${validation.errors.join("; ")}`,
|
|
151
|
+
metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check rate limit
|
|
156
|
+
const clientId = "default";
|
|
157
|
+
if (!security.checkRateLimit(clientId)) {
|
|
158
|
+
return {
|
|
159
|
+
success: false,
|
|
160
|
+
error: "Rate limit exceeded. Please wait before executing more code.",
|
|
161
|
+
metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Create mysql API bindings
|
|
166
|
+
const mysqlApi = createMysqlApi(adapter);
|
|
167
|
+
const bindings = mysqlApi.createSandboxBindings();
|
|
168
|
+
|
|
169
|
+
// Validate bindings are populated
|
|
170
|
+
const totalMethods = Object.values(bindings).reduce(
|
|
171
|
+
(sum: number, group) => {
|
|
172
|
+
if (typeof group === "object" && group !== null) {
|
|
173
|
+
return sum + Object.keys(group).length;
|
|
174
|
+
}
|
|
175
|
+
return sum;
|
|
176
|
+
},
|
|
177
|
+
0,
|
|
178
|
+
);
|
|
179
|
+
if (totalMethods === 0) {
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
error:
|
|
183
|
+
"mysql.* API not available: no tool bindings were created. Ensure adapter.getToolDefinitions() returns valid tools.",
|
|
184
|
+
metrics: { wallTimeMs: 0, cpuTimeMs: 0, memoryUsedMb: 0 },
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Capture active transactions before execution for cleanup on error
|
|
189
|
+
const transactionsBefore = new Set(adapter.getActiveTransactionIds());
|
|
190
|
+
|
|
191
|
+
// Execute in sandbox
|
|
192
|
+
const result = await pool.execute(code, bindings);
|
|
193
|
+
|
|
194
|
+
// Always cleanup orphaned transactions (uncommitted txns from any execution)
|
|
195
|
+
const transactionsAfter = adapter.getActiveTransactionIds();
|
|
196
|
+
const orphanedTransactions = transactionsAfter.filter(
|
|
197
|
+
(txId: string) => !transactionsBefore.has(txId),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
for (const txId of orphanedTransactions) {
|
|
201
|
+
try {
|
|
202
|
+
await adapter.rollbackTransaction(txId);
|
|
203
|
+
} catch {
|
|
204
|
+
// Best-effort cleanup
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Sanitize result
|
|
209
|
+
if (result.success && result.result !== undefined) {
|
|
210
|
+
result.result = security.sanitizeResult(result.result);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Audit log
|
|
214
|
+
const record = security.createExecutionRecord(
|
|
215
|
+
code,
|
|
216
|
+
result,
|
|
217
|
+
readonly ?? false,
|
|
218
|
+
clientId,
|
|
219
|
+
);
|
|
220
|
+
security.auditLog(record);
|
|
221
|
+
|
|
222
|
+
// Add help hint for discoverability
|
|
223
|
+
const helpHint =
|
|
224
|
+
"Tip: Use mysql.help() to list all groups, or mysql.core.help() for group-specific methods.";
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
...result,
|
|
228
|
+
hint: helpHint,
|
|
229
|
+
};
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get all Code Mode tools
|
|
236
|
+
*/
|
|
237
|
+
export function getCodeModeTools(adapter: MySQLAdapter): ToolDefinition[] {
|
|
238
|
+
return [createExecuteCodeTool(adapter)];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Cleanup Code Mode resources (call on server shutdown)
|
|
243
|
+
*/
|
|
244
|
+
export function cleanupCodeMode(): void {
|
|
245
|
+
if (sandboxPool) {
|
|
246
|
+
sandboxPool.dispose();
|
|
247
|
+
sandboxPool = null;
|
|
248
|
+
}
|
|
249
|
+
}
|