taurusdb-mcp 0.1.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 (44) hide show
  1. package/README.md +32 -0
  2. package/dist/commands/init.d.ts +1 -0
  3. package/dist/commands/init.js +195 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +22 -0
  6. package/dist/server.d.ts +12 -0
  7. package/dist/server.js +60 -0
  8. package/dist/tools/common/input.d.ts +50 -0
  9. package/dist/tools/common/input.js +168 -0
  10. package/dist/tools/common/public-formatters.d.ts +344 -0
  11. package/dist/tools/common/public-formatters.js +348 -0
  12. package/dist/tools/common.d.ts +2 -0
  13. package/dist/tools/common.js +2 -0
  14. package/dist/tools/discovery.d.ts +5 -0
  15. package/dist/tools/discovery.js +113 -0
  16. package/dist/tools/error-handling.d.ts +10 -0
  17. package/dist/tools/error-handling.js +122 -0
  18. package/dist/tools/ping.d.ts +4 -0
  19. package/dist/tools/ping.js +13 -0
  20. package/dist/tools/processlist.d.ts +2 -0
  21. package/dist/tools/processlist.js +121 -0
  22. package/dist/tools/query.d.ts +4 -0
  23. package/dist/tools/query.js +224 -0
  24. package/dist/tools/registry.d.ts +22 -0
  25. package/dist/tools/registry.js +106 -0
  26. package/dist/tools/taurus/capability.d.ts +3 -0
  27. package/dist/tools/taurus/capability.js +66 -0
  28. package/dist/tools/taurus/cloud-context.d.ts +4 -0
  29. package/dist/tools/taurus/cloud-context.js +209 -0
  30. package/dist/tools/taurus/cloud-instances.d.ts +2 -0
  31. package/dist/tools/taurus/cloud-instances.js +73 -0
  32. package/dist/tools/taurus/diagnostics.d.ts +9 -0
  33. package/dist/tools/taurus/diagnostics.js +323 -0
  34. package/dist/tools/taurus/explain.d.ts +2 -0
  35. package/dist/tools/taurus/explain.js +69 -0
  36. package/dist/tools/taurus/flashback.d.ts +2 -0
  37. package/dist/tools/taurus/flashback.js +101 -0
  38. package/dist/tools/taurus/recycle-bin.d.ts +3 -0
  39. package/dist/tools/taurus/recycle-bin.js +148 -0
  40. package/dist/utils/formatter.d.ts +5 -0
  41. package/dist/utils/formatter.js +8 -0
  42. package/dist/version.d.ts +1 -0
  43. package/dist/version.js +1 -0
  44. package/package.json +40 -0
@@ -0,0 +1,323 @@
1
+ import { z } from "zod";
2
+ import { formatSuccess, } from "../../utils/formatter.js";
3
+ import { formatToolError, ToolInputError } from "../error-handling.js";
4
+ import { asOptionalBoolean, asOptionalPositiveInteger, asOptionalString, diagnosticBaseInputShape, metadata, toPublicDbHotspotResult, resolveContext, toPublicDiagnosticResult, toPublicServiceLatencyResult, toPublicTopSlowSqlResult, } from "../common.js";
5
+ function parseBaseInput(input) {
6
+ return {
7
+ datasource: asOptionalString(input.datasource, "datasource"),
8
+ database: asOptionalString(input.database, "database"),
9
+ timeRange: parseTimeRange(input.time_range),
10
+ evidenceLevel: parseEvidenceLevel(input.evidence_level),
11
+ includeRawEvidence: asOptionalBoolean(input.include_raw_evidence, "include_raw_evidence"),
12
+ maxCandidates: asOptionalPositiveInteger(input.max_candidates, "max_candidates"),
13
+ };
14
+ }
15
+ function parseTimeRange(value) {
16
+ if (value === undefined) {
17
+ return undefined;
18
+ }
19
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
20
+ throw new ToolInputError("Invalid time_range: expected an object.");
21
+ }
22
+ const raw = value;
23
+ return {
24
+ from: asOptionalString(raw.from, "time_range.from"),
25
+ to: asOptionalString(raw.to, "time_range.to"),
26
+ relative: asOptionalString(raw.relative, "time_range.relative"),
27
+ };
28
+ }
29
+ function parseEvidenceLevel(value) {
30
+ if (value === undefined) {
31
+ return undefined;
32
+ }
33
+ if (value === "basic" || value === "standard" || value === "full") {
34
+ return value;
35
+ }
36
+ throw new ToolInputError("Invalid evidence_level: expected basic, standard, or full.");
37
+ }
38
+ function parseStorageScope(value) {
39
+ if (value === undefined) {
40
+ return undefined;
41
+ }
42
+ if (value === "instance" || value === "database" || value === "table") {
43
+ return value;
44
+ }
45
+ throw new ToolInputError("Invalid scope: expected instance, database, or table.");
46
+ }
47
+ function parseDbHotspotScope(value) {
48
+ if (value === undefined) {
49
+ return undefined;
50
+ }
51
+ if (value === "sql" || value === "table" || value === "session") {
52
+ return value;
53
+ }
54
+ throw new ToolInputError("Invalid scope: expected sql, table, or session.");
55
+ }
56
+ function parseLatencySymptom(value) {
57
+ if (value === undefined) {
58
+ return undefined;
59
+ }
60
+ if (value === "latency"
61
+ || value === "timeout"
62
+ || value === "cpu"
63
+ || value === "connection_growth") {
64
+ return value;
65
+ }
66
+ throw new ToolInputError("Invalid symptom: expected latency, timeout, cpu, or connection_growth.");
67
+ }
68
+ function parseTopSlowSqlSortBy(value) {
69
+ if (value === undefined) {
70
+ return undefined;
71
+ }
72
+ if (value === "avg_latency"
73
+ || value === "total_latency"
74
+ || value === "exec_count"
75
+ || value === "lock_time") {
76
+ return value;
77
+ }
78
+ throw new ToolInputError("Invalid sort_by: expected avg_latency, total_latency, exec_count, or lock_time.");
79
+ }
80
+ function summarizeDiagnostic(toolLabel, status) {
81
+ return `${toolLabel} returned ${status}.`;
82
+ }
83
+ export const findTopSlowSqlTool = {
84
+ name: "find_top_slow_sql",
85
+ description: "Find the most suspicious slow SQL statements for the selected datasource, database, and time window.",
86
+ inputSchema: {
87
+ ...diagnosticBaseInputShape,
88
+ top_n: z.number().int().positive().max(20).optional().describe("Maximum number of suspect SQL statements to return."),
89
+ sort_by: z
90
+ .enum(["avg_latency", "total_latency", "exec_count", "lock_time"])
91
+ .optional()
92
+ .describe("Ranking strategy for slow SQL discovery."),
93
+ },
94
+ async handler(input, deps, context) {
95
+ try {
96
+ const ctx = await resolveContext(input, deps, context, true);
97
+ const result = await deps.engine.findTopSlowSql({
98
+ ...parseBaseInput(input),
99
+ topN: asOptionalPositiveInteger(input.top_n, "top_n"),
100
+ sortBy: parseTopSlowSqlSortBy(input.sort_by),
101
+ }, ctx);
102
+ return formatSuccess(toPublicTopSlowSqlResult(result), {
103
+ summary: summarizeDiagnostic("Top slow SQL discovery", result.status),
104
+ metadata: metadata(context.taskId),
105
+ });
106
+ }
107
+ catch (error) {
108
+ return formatToolError(error, {
109
+ action: "find_top_slow_sql",
110
+ metadata: metadata(context.taskId),
111
+ });
112
+ }
113
+ },
114
+ };
115
+ export const diagnoseServiceLatencyTool = {
116
+ name: "diagnose_service_latency",
117
+ description: "Route a business-latency symptom to the most likely SQL, lock, or connection suspects and suggest the next diagnostic tool.",
118
+ inputSchema: {
119
+ ...diagnosticBaseInputShape,
120
+ user: diagnosticString("Optional user to focus on."),
121
+ client_host: diagnosticString("Optional client host or IP to focus on."),
122
+ symptom: z
123
+ .enum(["latency", "timeout", "cpu", "connection_growth"])
124
+ .optional()
125
+ .describe("Primary service symptom to route: latency, timeout, cpu, or connection_growth."),
126
+ },
127
+ async handler(input, deps, context) {
128
+ try {
129
+ const ctx = await resolveContext(input, deps, context, true);
130
+ const result = await deps.engine.diagnoseServiceLatency({
131
+ ...parseBaseInput(input),
132
+ user: asOptionalString(input.user, "user"),
133
+ clientHost: asOptionalString(input.client_host, "client_host"),
134
+ symptom: parseLatencySymptom(input.symptom),
135
+ }, ctx);
136
+ return formatSuccess(toPublicServiceLatencyResult(result), {
137
+ summary: summarizeDiagnostic("Service-latency diagnosis", result.status),
138
+ metadata: metadata(context.taskId),
139
+ });
140
+ }
141
+ catch (error) {
142
+ return formatToolError(error, {
143
+ action: "diagnose_service_latency",
144
+ metadata: metadata(context.taskId),
145
+ });
146
+ }
147
+ },
148
+ };
149
+ export const diagnoseDbHotspotTool = {
150
+ name: "diagnose_db_hotspot",
151
+ description: "Identify the hottest SQL, table, or session currently dragging down the datasource and recommend the next diagnostic tool.",
152
+ inputSchema: {
153
+ ...diagnosticBaseInputShape,
154
+ scope: z
155
+ .enum(["sql", "table", "session"])
156
+ .optional()
157
+ .describe("Optional hotspot scope: sql, table, or session."),
158
+ },
159
+ async handler(input, deps, context) {
160
+ try {
161
+ const ctx = await resolveContext(input, deps, context, true);
162
+ const result = await deps.engine.diagnoseDbHotspot({
163
+ ...parseBaseInput(input),
164
+ scope: parseDbHotspotScope(input.scope),
165
+ }, ctx);
166
+ return formatSuccess(toPublicDbHotspotResult(result), {
167
+ summary: summarizeDiagnostic("Database-hotspot diagnosis", result.status),
168
+ metadata: metadata(context.taskId),
169
+ });
170
+ }
171
+ catch (error) {
172
+ return formatToolError(error, {
173
+ action: "diagnose_db_hotspot",
174
+ metadata: metadata(context.taskId),
175
+ });
176
+ }
177
+ },
178
+ };
179
+ export const diagnoseSlowQueryTool = {
180
+ name: "diagnose_slow_query",
181
+ description: "Diagnose why a SQL statement is slow using live EXPLAIN evidence and, when digest history is available, performance_schema runtime wait evidence.",
182
+ inputSchema: {
183
+ ...diagnosticBaseInputShape,
184
+ sql: diagnosticString("SQL text to diagnose."),
185
+ sql_hash: diagnosticString("Normalized SQL hash to diagnose."),
186
+ digest_text: diagnosticString("SQL digest text to diagnose."),
187
+ },
188
+ async handler(input, deps, context) {
189
+ try {
190
+ const ctx = await resolveContext(input, deps, context, true);
191
+ const diagnosticInput = {
192
+ ...parseBaseInput(input),
193
+ sql: asOptionalString(input.sql, "sql"),
194
+ sqlHash: asOptionalString(input.sql_hash, "sql_hash"),
195
+ digestText: asOptionalString(input.digest_text, "digest_text"),
196
+ };
197
+ if (!diagnosticInput.sql && !diagnosticInput.sqlHash && !diagnosticInput.digestText) {
198
+ throw new ToolInputError("diagnose_slow_query requires sql, sql_hash, or digest_text.");
199
+ }
200
+ const result = await deps.engine.diagnoseSlowQuery(diagnosticInput, ctx);
201
+ return formatSuccess(toPublicDiagnosticResult(result), {
202
+ summary: summarizeDiagnostic("Slow-query diagnosis", result.status),
203
+ metadata: metadata(context.taskId),
204
+ });
205
+ }
206
+ catch (error) {
207
+ return formatToolError(error, {
208
+ action: "diagnose_slow_query",
209
+ metadata: metadata(context.taskId),
210
+ });
211
+ }
212
+ },
213
+ };
214
+ export const diagnoseConnectionSpikeTool = {
215
+ name: "diagnose_connection_spike",
216
+ description: "Diagnose connection spikes using live processlist evidence plus structured heuristic analysis.",
217
+ inputSchema: {
218
+ ...diagnosticBaseInputShape,
219
+ user: diagnosticString("Optional user to focus on."),
220
+ client_host: diagnosticString("Optional client host or IP to focus on."),
221
+ compare_baseline: diagnosticBoolean("Whether to compare the selected window with a baseline."),
222
+ },
223
+ async handler(input, deps, context) {
224
+ try {
225
+ const ctx = await resolveContext(input, deps, context, true);
226
+ const diagnosticInput = {
227
+ ...parseBaseInput(input),
228
+ user: asOptionalString(input.user, "user"),
229
+ clientHost: asOptionalString(input.client_host, "client_host"),
230
+ compareBaseline: asOptionalBoolean(input.compare_baseline, "compare_baseline"),
231
+ };
232
+ const result = await deps.engine.diagnoseConnectionSpike(diagnosticInput, ctx);
233
+ return formatSuccess(toPublicDiagnosticResult(result), {
234
+ summary: summarizeDiagnostic("Connection-spike diagnosis", result.status),
235
+ metadata: metadata(context.taskId),
236
+ });
237
+ }
238
+ catch (error) {
239
+ return formatToolError(error, {
240
+ action: "diagnose_connection_spike",
241
+ metadata: metadata(context.taskId),
242
+ });
243
+ }
244
+ },
245
+ };
246
+ export const diagnoseLockContentionTool = {
247
+ name: "diagnose_lock_contention",
248
+ description: "Diagnose InnoDB lock contention using live lock-wait evidence plus structured heuristic analysis.",
249
+ inputSchema: {
250
+ ...diagnosticBaseInputShape,
251
+ table: diagnosticString("Optional table to focus on."),
252
+ blocker_session_id: diagnosticString("Optional blocker session identifier to focus on."),
253
+ },
254
+ async handler(input, deps, context) {
255
+ try {
256
+ const ctx = await resolveContext(input, deps, context, true);
257
+ const diagnosticInput = {
258
+ ...parseBaseInput(input),
259
+ table: asOptionalString(input.table, "table"),
260
+ blockerSessionId: asOptionalString(input.blocker_session_id, "blocker_session_id"),
261
+ };
262
+ const result = await deps.engine.diagnoseLockContention(diagnosticInput, ctx);
263
+ return formatSuccess(toPublicDiagnosticResult(result), {
264
+ summary: summarizeDiagnostic("Lock-contention diagnosis", result.status),
265
+ metadata: metadata(context.taskId),
266
+ });
267
+ }
268
+ catch (error) {
269
+ return formatToolError(error, {
270
+ action: "diagnose_lock_contention",
271
+ metadata: metadata(context.taskId),
272
+ });
273
+ }
274
+ },
275
+ };
276
+ export const diagnoseStoragePressureTool = {
277
+ name: "diagnose_storage_pressure",
278
+ description: "Diagnose local storage-pressure signals using statement digest counters and table storage metadata.",
279
+ inputSchema: {
280
+ ...diagnosticBaseInputShape,
281
+ scope: diagnosticEnum("Diagnosis scope: instance, database, or table."),
282
+ table: diagnosticString("Optional table to focus on when scope is table."),
283
+ },
284
+ async handler(input, deps, context) {
285
+ try {
286
+ const ctx = await resolveContext(input, deps, context, true);
287
+ const diagnosticInput = {
288
+ ...parseBaseInput(input),
289
+ scope: parseStorageScope(input.scope),
290
+ table: asOptionalString(input.table, "table"),
291
+ };
292
+ const result = await deps.engine.diagnoseStoragePressure(diagnosticInput, ctx);
293
+ return formatSuccess(toPublicDiagnosticResult(result), {
294
+ summary: summarizeDiagnostic("Storage-pressure diagnosis", result.status),
295
+ metadata: metadata(context.taskId),
296
+ });
297
+ }
298
+ catch (error) {
299
+ return formatToolError(error, {
300
+ action: "diagnose_storage_pressure",
301
+ metadata: metadata(context.taskId),
302
+ });
303
+ }
304
+ },
305
+ };
306
+ export const diagnosticToolDefinitions = [
307
+ diagnoseServiceLatencyTool,
308
+ diagnoseDbHotspotTool,
309
+ findTopSlowSqlTool,
310
+ diagnoseSlowQueryTool,
311
+ diagnoseConnectionSpikeTool,
312
+ diagnoseLockContentionTool,
313
+ diagnoseStoragePressureTool,
314
+ ];
315
+ function diagnosticString(description) {
316
+ return z.string().trim().min(1).optional().describe(description);
317
+ }
318
+ function diagnosticBoolean(description) {
319
+ return z.boolean().optional().describe(description);
320
+ }
321
+ function diagnosticEnum(description) {
322
+ return z.enum(["instance", "database", "table"]).optional().describe(description);
323
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolDefinition } from "../registry.js";
2
+ export declare const explainSqlEnhancedTool: ToolDefinition;
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import { formatBlocked, formatSuccess } from "../../utils/formatter.js";
3
+ import { formatToolError } from "../error-handling.js";
4
+ import { asRequiredString, contextInputShape, metadata, resolveContext, statementTypeFromSql, toPublicEnhancedExplainResult, } from "../common.js";
5
+ const READONLY_EXPLAIN_TYPES = new Set(["select", "show", "describe", "explain"]);
6
+ function isReadonlyExplainSql(sql) {
7
+ const statementType = statementTypeFromSql(sql);
8
+ return statementType !== undefined && READONLY_EXPLAIN_TYPES.has(statementType);
9
+ }
10
+ export const explainSqlEnhancedTool = {
11
+ name: "explain_sql_enhanced",
12
+ description: "Run TaurusDB-aware EXPLAIN on a readonly SQL statement and return NDP/PQ/OFFSET hints with optimization suggestions.",
13
+ inputSchema: {
14
+ ...contextInputShape,
15
+ sql: z.string().trim().min(1).describe("Readonly SQL statement to analyze."),
16
+ },
17
+ async handler(input, deps, context) {
18
+ const sql = asRequiredString(input.sql, "sql");
19
+ const statementType = statementTypeFromSql(sql);
20
+ try {
21
+ if (!isReadonlyExplainSql(sql)) {
22
+ return formatBlocked({
23
+ reason: "Tool explain_sql_enhanced only supports readonly SQL statements.",
24
+ metadata: metadata(context.taskId, {
25
+ statement_type: statementType,
26
+ }),
27
+ summary: "Enhanced EXPLAIN is limited to readonly SQL.",
28
+ });
29
+ }
30
+ const ctx = await resolveContext(input, deps, context, true);
31
+ const decision = await deps.engine.inspectSql({
32
+ toolName: "explain_sql_enhanced",
33
+ sql,
34
+ context: ctx,
35
+ });
36
+ if (decision.action === "block") {
37
+ return formatBlocked({
38
+ reason: decision.riskHints[0] ?? "The SQL statement is blocked by safety policy.",
39
+ metadata: metadata(context.taskId, {
40
+ sql_hash: decision.sqlHash,
41
+ statement_type: statementType,
42
+ }),
43
+ details: {
44
+ risk_level: decision.riskLevel,
45
+ reason_codes: decision.reasonCodes,
46
+ risk_hints: decision.riskHints,
47
+ },
48
+ });
49
+ }
50
+ const result = await deps.engine.explainEnhanced(sql, ctx);
51
+ return formatSuccess(toPublicEnhancedExplainResult(result, decision), {
52
+ summary: "Enhanced EXPLAIN generated.",
53
+ metadata: metadata(context.taskId, {
54
+ sql_hash: decision.sqlHash,
55
+ statement_type: statementType,
56
+ duration_ms: result.standardPlan.durationMs,
57
+ }),
58
+ });
59
+ }
60
+ catch (error) {
61
+ return formatToolError(error, {
62
+ action: "explain_sql_enhanced",
63
+ metadata: metadata(context.taskId, {
64
+ statement_type: statementType,
65
+ }),
66
+ });
67
+ }
68
+ },
69
+ };
@@ -0,0 +1,2 @@
1
+ import type { ToolDefinition } from "../registry.js";
2
+ export declare const flashbackQueryTool: ToolDefinition;
@@ -0,0 +1,101 @@
1
+ import { z } from "zod";
2
+ import { formatSuccess } from "../../utils/formatter.js";
3
+ import { formatToolError } from "../error-handling.js";
4
+ import { asOptionalPositiveInteger, asOptionalString, asRequiredString, contextInputShape, metadata, requireDatabase, resolveContext, summarizeRows, toPublicQueryResult, } from "../common.js";
5
+ const asOfSchema = z
6
+ .object({
7
+ timestamp: z.string().trim().min(1).optional(),
8
+ relative: z.string().trim().min(1).optional(),
9
+ })
10
+ .refine((value) => Boolean(value.timestamp) !== Boolean(value.relative), {
11
+ message: "Provide exactly one of as_of.timestamp or as_of.relative.",
12
+ })
13
+ .describe("Flashback point in time. Use either an absolute timestamp or a relative duration like 5m.");
14
+ function parseColumns(input) {
15
+ if (input === undefined) {
16
+ return undefined;
17
+ }
18
+ if (!Array.isArray(input)) {
19
+ throw new Error("Invalid columns: expected an array of strings.");
20
+ }
21
+ return input.map((value, index) => asRequiredString(value, `columns[${index}]`));
22
+ }
23
+ function parseAsOf(input) {
24
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
25
+ throw new Error("Invalid as_of: expected an object.");
26
+ }
27
+ const record = input;
28
+ const timestamp = asOptionalString(record.timestamp, "as_of.timestamp");
29
+ const relative = asOptionalString(record.relative, "as_of.relative");
30
+ if (Boolean(timestamp) === Boolean(relative)) {
31
+ throw new Error("Provide exactly one of as_of.timestamp or as_of.relative.");
32
+ }
33
+ if (timestamp) {
34
+ return { timestamp };
35
+ }
36
+ return { relative: relative };
37
+ }
38
+ export const flashbackQueryTool = {
39
+ name: "flashback_query",
40
+ description: "Run a TaurusDB flashback SELECT against a historical timestamp using the normal readonly execution path.",
41
+ inputSchema: {
42
+ ...contextInputShape,
43
+ table: z
44
+ .string()
45
+ .trim()
46
+ .min(1)
47
+ .describe("Table name to query historically."),
48
+ as_of: asOfSchema,
49
+ where: z
50
+ .string()
51
+ .trim()
52
+ .min(1)
53
+ .optional()
54
+ .describe("Optional SQL WHERE clause body."),
55
+ columns: z
56
+ .array(z.string().trim().min(1))
57
+ .optional()
58
+ .describe("Optional column projection."),
59
+ limit: z
60
+ .number()
61
+ .int()
62
+ .positive()
63
+ .optional()
64
+ .describe("Maximum rows to return."),
65
+ },
66
+ async handler(input, deps, context) {
67
+ try {
68
+ const ctx = await resolveContext(input, deps, context, true);
69
+ const database = requireDatabase(input.database, ctx);
70
+ const table = asRequiredString(input.table, "table");
71
+ const result = await deps.engine.flashbackQuery({
72
+ database,
73
+ table,
74
+ asOf: parseAsOf(input.as_of),
75
+ where: asOptionalString(input.where, "where"),
76
+ columns: parseColumns(input.columns),
77
+ limit: asOptionalPositiveInteger(input.limit, "limit"),
78
+ }, ctx);
79
+ return formatSuccess({
80
+ datasource: ctx.datasource,
81
+ database,
82
+ table,
83
+ ...toPublicQueryResult(result),
84
+ }, {
85
+ summary: summarizeRows(result.rowCount, result.truncated),
86
+ metadata: metadata(context.taskId, {
87
+ statement_type: "select",
88
+ duration_ms: result.durationMs,
89
+ }),
90
+ });
91
+ }
92
+ catch (error) {
93
+ return formatToolError(error, {
94
+ action: "flashback_query",
95
+ metadata: metadata(context.taskId, {
96
+ statement_type: "select",
97
+ }),
98
+ });
99
+ }
100
+ },
101
+ };
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from "../registry.js";
2
+ export declare const listRecycleBinTool: ToolDefinition;
3
+ export declare const restoreRecycleBinTableTool: ToolDefinition;
@@ -0,0 +1,148 @@
1
+ import { z } from "zod";
2
+ import { buildRestoreRecycleBinTableSql, } from "taurusdb-core";
3
+ import { ErrorCode, formatConfirmationRequired, formatError, formatSuccess, } from "../../utils/formatter.js";
4
+ import { formatToolError } from "../error-handling.js";
5
+ import { asOptionalString, asRequiredString, contextInputShape, metadata, resolveContext, summarizeMutation, summarizeRows, toPublicMutationResult, toPublicQueryResult, } from "../common.js";
6
+ function parseRestoreMethod(value) {
7
+ if (value === undefined) {
8
+ return undefined;
9
+ }
10
+ if (value === "native_restore" || value === "insert_select") {
11
+ return value;
12
+ }
13
+ throw new Error("Invalid method: expected native_restore or insert_select.");
14
+ }
15
+ export const listRecycleBinTool = {
16
+ name: "list_recycle_bin",
17
+ description: "List TaurusDB recycle bin tables. This is readonly and is intended for recovery triage after accidental DROP TABLE.",
18
+ inputSchema: {
19
+ datasource: contextInputShape.datasource,
20
+ timeout_ms: contextInputShape.timeout_ms,
21
+ },
22
+ async handler(input, deps, context) {
23
+ try {
24
+ const ctx = await resolveContext(input, deps, context, true);
25
+ const result = await deps.engine.listRecycleBin(ctx);
26
+ return formatSuccess({
27
+ datasource: ctx.datasource,
28
+ ...toPublicQueryResult(result),
29
+ }, {
30
+ summary: summarizeRows(result.rowCount, result.truncated),
31
+ metadata: metadata(context.taskId, {
32
+ statement_type: "show",
33
+ duration_ms: result.durationMs,
34
+ }),
35
+ });
36
+ }
37
+ catch (error) {
38
+ return formatToolError(error, {
39
+ action: "list_recycle_bin",
40
+ metadata: metadata(context.taskId, {
41
+ statement_type: "show",
42
+ }),
43
+ });
44
+ }
45
+ },
46
+ };
47
+ export const restoreRecycleBinTableTool = {
48
+ name: "restore_recycle_bin_table",
49
+ description: "Restore a TaurusDB recycle bin table after explicit confirmation. Use insert_select for DRS/binlog-friendly recovery into a pre-created destination table.",
50
+ inputSchema: {
51
+ ...contextInputShape,
52
+ recycle_table: z
53
+ .string()
54
+ .trim()
55
+ .min(1)
56
+ .describe("Recycle bin table name returned by list_recycle_bin."),
57
+ method: z
58
+ .enum(["native_restore", "insert_select"])
59
+ .optional()
60
+ .describe("Restore method. native_restore calls dbms_recyclebin.restore_table; insert_select copies rows into a pre-created destination table."),
61
+ destination_database: z
62
+ .string()
63
+ .trim()
64
+ .min(1)
65
+ .optional()
66
+ .describe("Destination database. Required for insert_select; optional with native_restore when renaming on restore."),
67
+ destination_table: z
68
+ .string()
69
+ .trim()
70
+ .min(1)
71
+ .optional()
72
+ .describe("Destination table. Required for insert_select; optional with native_restore when renaming on restore."),
73
+ confirmation_token: z
74
+ .string()
75
+ .trim()
76
+ .min(1)
77
+ .optional()
78
+ .describe("Confirmation token returned by the first guarded restore call."),
79
+ },
80
+ async handler(input, deps, context) {
81
+ const restoreInput = {
82
+ recycleTable: asRequiredString(input.recycle_table, "recycle_table"),
83
+ method: parseRestoreMethod(input.method),
84
+ destinationDatabase: asOptionalString(input.destination_database, "destination_database"),
85
+ destinationTable: asOptionalString(input.destination_table, "destination_table"),
86
+ };
87
+ try {
88
+ const ctx = await resolveContext(input, deps, context, false);
89
+ const sql = buildRestoreRecycleBinTableSql(restoreInput);
90
+ const responseMetadata = metadata(context.taskId, {
91
+ statement_type: restoreInput.method === "insert_select" ? "insert" : "unknown",
92
+ });
93
+ const confirmationToken = asOptionalString(input.confirmation_token, "confirmation_token");
94
+ if (confirmationToken) {
95
+ const validation = await deps.engine.validateConfirmation(confirmationToken, sql, ctx);
96
+ if (!validation.valid) {
97
+ return formatError({
98
+ code: ErrorCode.CONFIRMATION_INVALID,
99
+ message: validation.reason ?? "Confirmation token validation failed.",
100
+ summary: "The provided confirmation token is invalid for this recycle bin restore.",
101
+ metadata: responseMetadata,
102
+ details: {
103
+ reason_codes: validation.reasonCodes,
104
+ risk_hints: validation.riskHints,
105
+ },
106
+ });
107
+ }
108
+ }
109
+ else {
110
+ const token = await deps.engine.issueConfirmation({
111
+ sql,
112
+ context: ctx,
113
+ riskLevel: "high",
114
+ });
115
+ return formatConfirmationRequired({
116
+ confirmationToken: token.token,
117
+ metadata: responseMetadata,
118
+ riskLevel: "high",
119
+ summary: "Recycle bin restore requires explicit confirmation.",
120
+ message: "Re-run restore_recycle_bin_table with the same input and confirmation_token to continue.",
121
+ });
122
+ }
123
+ const result = await deps.engine.restoreRecycleBinTable(restoreInput, ctx);
124
+ return formatSuccess({
125
+ datasource: ctx.datasource,
126
+ recycle_table: restoreInput.recycleTable,
127
+ method: restoreInput.method ?? "native_restore",
128
+ destination_database: restoreInput.destinationDatabase,
129
+ destination_table: restoreInput.destinationTable,
130
+ ...toPublicMutationResult(result),
131
+ }, {
132
+ summary: summarizeMutation(result.affectedRows),
133
+ metadata: metadata(context.taskId, {
134
+ statement_type: restoreInput.method === "insert_select" ? "insert" : "unknown",
135
+ duration_ms: result.durationMs,
136
+ }),
137
+ });
138
+ }
139
+ catch (error) {
140
+ return formatToolError(error, {
141
+ action: "restore_recycle_bin_table",
142
+ metadata: metadata(context.taskId, {
143
+ statement_type: restoreInput.method === "insert_select" ? "insert" : "unknown",
144
+ }),
145
+ });
146
+ }
147
+ },
148
+ };
@@ -0,0 +1,5 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { ToolResponse } from "taurusdb-core";
3
+ export { ErrorCode, formatBlocked, formatConfirmationRequired, formatError, formatSuccess, } from "taurusdb-core";
4
+ export type { ResponseMetadata, ToolError, ToolResponse, } from "taurusdb-core";
5
+ export declare function toMcpToolResult<T>(response: ToolResponse<T>): CallToolResult;
@@ -0,0 +1,8 @@
1
+ export { ErrorCode, formatBlocked, formatConfirmationRequired, formatError, formatSuccess, } from "taurusdb-core";
2
+ export function toMcpToolResult(response) {
3
+ return {
4
+ content: [{ type: "text", text: JSON.stringify(response) }],
5
+ structuredContent: response,
6
+ isError: !response.ok,
7
+ };
8
+ }
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.1.0";
@@ -0,0 +1 @@
1
+ export const VERSION = "0.1.0";