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.
- package/README.md +32 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +195 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +22 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +60 -0
- package/dist/tools/common/input.d.ts +50 -0
- package/dist/tools/common/input.js +168 -0
- package/dist/tools/common/public-formatters.d.ts +344 -0
- package/dist/tools/common/public-formatters.js +348 -0
- package/dist/tools/common.d.ts +2 -0
- package/dist/tools/common.js +2 -0
- package/dist/tools/discovery.d.ts +5 -0
- package/dist/tools/discovery.js +113 -0
- package/dist/tools/error-handling.d.ts +10 -0
- package/dist/tools/error-handling.js +122 -0
- package/dist/tools/ping.d.ts +4 -0
- package/dist/tools/ping.js +13 -0
- package/dist/tools/processlist.d.ts +2 -0
- package/dist/tools/processlist.js +121 -0
- package/dist/tools/query.d.ts +4 -0
- package/dist/tools/query.js +224 -0
- package/dist/tools/registry.d.ts +22 -0
- package/dist/tools/registry.js +106 -0
- package/dist/tools/taurus/capability.d.ts +3 -0
- package/dist/tools/taurus/capability.js +66 -0
- package/dist/tools/taurus/cloud-context.d.ts +4 -0
- package/dist/tools/taurus/cloud-context.js +209 -0
- package/dist/tools/taurus/cloud-instances.d.ts +2 -0
- package/dist/tools/taurus/cloud-instances.js +73 -0
- package/dist/tools/taurus/diagnostics.d.ts +9 -0
- package/dist/tools/taurus/diagnostics.js +323 -0
- package/dist/tools/taurus/explain.d.ts +2 -0
- package/dist/tools/taurus/explain.js +69 -0
- package/dist/tools/taurus/flashback.d.ts +2 -0
- package/dist/tools/taurus/flashback.js +101 -0
- package/dist/tools/taurus/recycle-bin.d.ts +3 -0
- package/dist/tools/taurus/recycle-bin.js +148 -0
- package/dist/utils/formatter.d.ts +5 -0
- package/dist/utils/formatter.js +8 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# taurusdb-mcp
|
|
2
|
+
|
|
3
|
+
TaurusDB MCP server for MCP clients such as:
|
|
4
|
+
|
|
5
|
+
- Claude
|
|
6
|
+
- Cursor
|
|
7
|
+
- VS Code
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install taurusdb-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Run directly with:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx taurusdb-mcp --version
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Initialize local MCP client config:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx taurusdb-mcp init --client claude
|
|
25
|
+
npx taurusdb-mcp init --client cursor
|
|
26
|
+
npx taurusdb-mcp init --client vscode
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Notes
|
|
30
|
+
|
|
31
|
+
- Requires Node.js `>= 20`
|
|
32
|
+
- Depends on `taurusdb-core`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runInit(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const SERVER_NAME = "huaweicloud-taurusdb";
|
|
5
|
+
function printInitUsage() {
|
|
6
|
+
console.error("Usage: taurusdb-mcp init --client <claude|cursor|vscode>");
|
|
7
|
+
}
|
|
8
|
+
function parseClientArg(args) {
|
|
9
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
10
|
+
const arg = args[index];
|
|
11
|
+
if (arg === "--client" || arg === "-c") {
|
|
12
|
+
return toClientName(args[index + 1]);
|
|
13
|
+
}
|
|
14
|
+
if (arg.startsWith("--client=")) {
|
|
15
|
+
return toClientName(arg.slice("--client=".length));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
function toClientName(value) {
|
|
21
|
+
if (value === "claude" || value === "cursor" || value === "vscode") {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function getAppDataDir() {
|
|
27
|
+
const appData = process.env.APPDATA;
|
|
28
|
+
if (appData && appData.trim().length > 0) {
|
|
29
|
+
return appData;
|
|
30
|
+
}
|
|
31
|
+
return path.join(os.homedir(), "AppData", "Roaming");
|
|
32
|
+
}
|
|
33
|
+
async function fileExists(filePath) {
|
|
34
|
+
try {
|
|
35
|
+
await access(filePath);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function pickExistingOrDefault(candidates) {
|
|
43
|
+
for (const candidate of candidates) {
|
|
44
|
+
if (await fileExists(candidate)) {
|
|
45
|
+
return candidate;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return candidates[0];
|
|
49
|
+
}
|
|
50
|
+
async function readJsonObject(filePath) {
|
|
51
|
+
if (!(await fileExists(filePath))) {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
const raw = await readFile(filePath, "utf-8");
|
|
55
|
+
if (raw.trim().length === 0) {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new Error(`Invalid JSON in config file: ${filePath}`, { cause: error });
|
|
64
|
+
}
|
|
65
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
66
|
+
throw new Error(`Config file root must be a JSON object: ${filePath}`);
|
|
67
|
+
}
|
|
68
|
+
return parsed;
|
|
69
|
+
}
|
|
70
|
+
async function writeJsonObject(filePath, config) {
|
|
71
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
72
|
+
await writeFile(filePath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
73
|
+
}
|
|
74
|
+
function mergeWithKey(config, rootKey, serverName, entry) {
|
|
75
|
+
const current = config[rootKey];
|
|
76
|
+
const nextConfig = { ...config };
|
|
77
|
+
let servers;
|
|
78
|
+
if (current === undefined) {
|
|
79
|
+
servers = {};
|
|
80
|
+
}
|
|
81
|
+
else if (current !== null && typeof current === "object" && !Array.isArray(current)) {
|
|
82
|
+
servers = { ...current };
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
throw new Error(`Config field \`${rootKey}\` must be an object.`);
|
|
86
|
+
}
|
|
87
|
+
if (Object.hasOwn(servers, serverName)) {
|
|
88
|
+
return {
|
|
89
|
+
config,
|
|
90
|
+
changed: false,
|
|
91
|
+
conflict: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
servers[serverName] = entry;
|
|
95
|
+
nextConfig[rootKey] = servers;
|
|
96
|
+
return {
|
|
97
|
+
config: nextConfig,
|
|
98
|
+
changed: true,
|
|
99
|
+
conflict: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const SHARED_ENTRY = {
|
|
103
|
+
command: "npx",
|
|
104
|
+
args: ["-y", "taurusdb-mcp"],
|
|
105
|
+
};
|
|
106
|
+
function createClaudeAdapter() {
|
|
107
|
+
return {
|
|
108
|
+
name: "claude",
|
|
109
|
+
async getConfigPath() {
|
|
110
|
+
const home = os.homedir();
|
|
111
|
+
if (process.platform === "darwin") {
|
|
112
|
+
return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
113
|
+
}
|
|
114
|
+
if (process.platform === "win32") {
|
|
115
|
+
return pickExistingOrDefault([
|
|
116
|
+
path.join(getAppDataDir(), "Claude", "claude_desktop_config.json"),
|
|
117
|
+
path.join(getAppDataDir(), "Claude", "config.json"),
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
return path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
121
|
+
},
|
|
122
|
+
readConfig: readJsonObject,
|
|
123
|
+
mergeServerEntry(config, serverName, entry) {
|
|
124
|
+
return mergeWithKey(config, "mcpServers", serverName, entry);
|
|
125
|
+
},
|
|
126
|
+
writeConfig: writeJsonObject,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function createCursorAdapter() {
|
|
130
|
+
return {
|
|
131
|
+
name: "cursor",
|
|
132
|
+
async getConfigPath() {
|
|
133
|
+
return path.join(os.homedir(), ".cursor", "mcp.json");
|
|
134
|
+
},
|
|
135
|
+
readConfig: readJsonObject,
|
|
136
|
+
mergeServerEntry(config, serverName, entry) {
|
|
137
|
+
return mergeWithKey(config, "mcpServers", serverName, entry);
|
|
138
|
+
},
|
|
139
|
+
writeConfig: writeJsonObject,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function createVsCodeAdapter() {
|
|
143
|
+
return {
|
|
144
|
+
name: "vscode",
|
|
145
|
+
async getConfigPath() {
|
|
146
|
+
const home = os.homedir();
|
|
147
|
+
if (process.platform === "darwin") {
|
|
148
|
+
return path.join(home, "Library", "Application Support", "Code", "User", "mcp.json");
|
|
149
|
+
}
|
|
150
|
+
if (process.platform === "win32") {
|
|
151
|
+
return path.join(getAppDataDir(), "Code", "User", "mcp.json");
|
|
152
|
+
}
|
|
153
|
+
return path.join(home, ".config", "Code", "User", "mcp.json");
|
|
154
|
+
},
|
|
155
|
+
readConfig: readJsonObject,
|
|
156
|
+
mergeServerEntry(config, serverName, entry) {
|
|
157
|
+
return mergeWithKey(config, "servers", serverName, entry);
|
|
158
|
+
},
|
|
159
|
+
writeConfig: writeJsonObject,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function getClientAdapter(client) {
|
|
163
|
+
switch (client) {
|
|
164
|
+
case "claude":
|
|
165
|
+
return createClaudeAdapter();
|
|
166
|
+
case "cursor":
|
|
167
|
+
return createCursorAdapter();
|
|
168
|
+
case "vscode":
|
|
169
|
+
return createVsCodeAdapter();
|
|
170
|
+
default: {
|
|
171
|
+
const exhaustive = client;
|
|
172
|
+
throw new Error(`Unsupported client: ${String(exhaustive)}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export async function runInit(args) {
|
|
177
|
+
const client = parseClientArg(args);
|
|
178
|
+
if (!client) {
|
|
179
|
+
printInitUsage();
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
const adapter = getClientAdapter(client);
|
|
183
|
+
const configPath = await adapter.getConfigPath();
|
|
184
|
+
const config = await adapter.readConfig(configPath);
|
|
185
|
+
const merged = adapter.mergeServerEntry(config, SERVER_NAME, SHARED_ENTRY);
|
|
186
|
+
if (merged.conflict) {
|
|
187
|
+
console.error(`[init] ${adapter.name} config already contains '${SERVER_NAME}', skipped update.`);
|
|
188
|
+
console.error(`[init] config: ${configPath}`);
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
await adapter.writeConfig(configPath, merged.config);
|
|
192
|
+
console.error(`[init] wrote ${adapter.name} config: ${configPath}`);
|
|
193
|
+
console.error(`[init] added MCP server '${SERVER_NAME}'. Restart ${adapter.name} to apply.`);
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runInit } from "./commands/init.js";
|
|
3
|
+
import { startMcpServer } from "./server.js";
|
|
4
|
+
import { VERSION } from "./version.js";
|
|
5
|
+
async function main() {
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const firstArg = args[0];
|
|
8
|
+
if (firstArg === "init") {
|
|
9
|
+
process.exitCode = await runInit(args.slice(1));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (firstArg === "--version" || firstArg === "-v") {
|
|
13
|
+
process.stdout.write(`${VERSION}\n`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await startMcpServer();
|
|
17
|
+
}
|
|
18
|
+
main().catch((error) => {
|
|
19
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
20
|
+
console.error(message);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { TaurusDBEngine, type CapabilitySnapshot, type Config, type RuntimeTargetProfileLoader } from "taurusdb-core";
|
|
3
|
+
export interface ServerDeps {
|
|
4
|
+
config: Config;
|
|
5
|
+
profileLoader: RuntimeTargetProfileLoader;
|
|
6
|
+
engine: TaurusDBEngine;
|
|
7
|
+
pingResponse: string;
|
|
8
|
+
startupProbe?: CapabilitySnapshot;
|
|
9
|
+
}
|
|
10
|
+
export declare function bootstrapDependencies(): Promise<ServerDeps>;
|
|
11
|
+
export declare function createServer(deps: ServerDeps): McpServer;
|
|
12
|
+
export declare function startMcpServer(): Promise<void>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createSqlProfileLoader, getConfig, RuntimeOverrideProfileLoader, redactConfigForLog, TaurusDBEngine, } from "taurusdb-core";
|
|
4
|
+
import { registerTools } from "./tools/registry.js";
|
|
5
|
+
import { logger } from "taurusdb-core";
|
|
6
|
+
import { VERSION } from "./version.js";
|
|
7
|
+
export async function bootstrapDependencies() {
|
|
8
|
+
const config = getConfig();
|
|
9
|
+
const profileLoader = new RuntimeOverrideProfileLoader(createSqlProfileLoader({ config }));
|
|
10
|
+
const engine = await TaurusDBEngine.create({ config, profileLoader });
|
|
11
|
+
const defaultDatasource = await engine.getDefaultDataSource();
|
|
12
|
+
let startupProbe;
|
|
13
|
+
if (defaultDatasource) {
|
|
14
|
+
try {
|
|
15
|
+
const bootstrapContext = await engine.resolveContext({
|
|
16
|
+
datasource: defaultDatasource,
|
|
17
|
+
readonly: true,
|
|
18
|
+
}, "task_bootstrap_probe");
|
|
19
|
+
startupProbe = await engine.probeCapabilities(bootstrapContext);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
logger.warn({
|
|
23
|
+
err: error,
|
|
24
|
+
defaultDatasource,
|
|
25
|
+
}, "Capability probe failed during bootstrap; TaurusDB-specific tools will stay disabled");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
config,
|
|
30
|
+
profileLoader,
|
|
31
|
+
engine,
|
|
32
|
+
pingResponse: "pong",
|
|
33
|
+
startupProbe,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function createServer(deps) {
|
|
37
|
+
const server = new McpServer({
|
|
38
|
+
name: "huaweicloud-taurusdb",
|
|
39
|
+
version: VERSION,
|
|
40
|
+
});
|
|
41
|
+
registerTools(server, deps, deps.config, deps.startupProbe);
|
|
42
|
+
return server;
|
|
43
|
+
}
|
|
44
|
+
export async function startMcpServer() {
|
|
45
|
+
const deps = await bootstrapDependencies();
|
|
46
|
+
const [datasources, defaultDatasource] = await Promise.all([
|
|
47
|
+
deps.engine.listDataSources(),
|
|
48
|
+
deps.engine.getDefaultDataSource(),
|
|
49
|
+
]);
|
|
50
|
+
const server = createServer(deps);
|
|
51
|
+
logger.info({ config: redactConfigForLog(deps.config) }, "Loaded effective config");
|
|
52
|
+
logger.info({
|
|
53
|
+
profileCount: datasources.length,
|
|
54
|
+
defaultDatasource: defaultDatasource ?? null,
|
|
55
|
+
taurusdbProbe: deps.startupProbe?.kernelInfo?.isTaurusDB ?? null,
|
|
56
|
+
}, "SQL profiles resolved");
|
|
57
|
+
logger.info({ server: "huaweicloud-taurusdb", version: VERSION }, "Starting MCP server");
|
|
58
|
+
await server.connect(new StdioServerTransport());
|
|
59
|
+
logger.info({ server: "huaweicloud-taurusdb" }, "MCP server connected to stdio transport");
|
|
60
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ResponseMetadata, SessionContext, StatementType } from "taurusdb-core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { type ToolResponse } from "../../utils/formatter.js";
|
|
4
|
+
import type { ToolDeps, ToolInvokeContext } from "../registry.js";
|
|
5
|
+
type RawContextInput = {
|
|
6
|
+
datasource?: unknown;
|
|
7
|
+
database?: unknown;
|
|
8
|
+
schema?: unknown;
|
|
9
|
+
timeout_ms?: unknown;
|
|
10
|
+
};
|
|
11
|
+
export declare const contextInputShape: {
|
|
12
|
+
readonly datasource: z.ZodOptional<z.ZodString>;
|
|
13
|
+
readonly database: z.ZodOptional<z.ZodString>;
|
|
14
|
+
readonly schema: z.ZodOptional<z.ZodString>;
|
|
15
|
+
readonly timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
};
|
|
17
|
+
export declare const diagnosticBaseInputShape: {
|
|
18
|
+
readonly time_range: z.ZodOptional<z.ZodObject<{
|
|
19
|
+
from: z.ZodOptional<z.ZodString>;
|
|
20
|
+
to: z.ZodOptional<z.ZodString>;
|
|
21
|
+
relative: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
from?: string | undefined;
|
|
24
|
+
to?: string | undefined;
|
|
25
|
+
relative?: string | undefined;
|
|
26
|
+
}, {
|
|
27
|
+
from?: string | undefined;
|
|
28
|
+
to?: string | undefined;
|
|
29
|
+
relative?: string | undefined;
|
|
30
|
+
}>>;
|
|
31
|
+
readonly evidence_level: z.ZodOptional<z.ZodEnum<["basic", "standard", "full"]>>;
|
|
32
|
+
readonly include_raw_evidence: z.ZodOptional<z.ZodBoolean>;
|
|
33
|
+
readonly max_candidates: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
readonly datasource: z.ZodOptional<z.ZodString>;
|
|
35
|
+
readonly database: z.ZodOptional<z.ZodString>;
|
|
36
|
+
readonly schema: z.ZodOptional<z.ZodString>;
|
|
37
|
+
readonly timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
};
|
|
39
|
+
export declare function metadata(taskId: string, extra?: Omit<ResponseMetadata, "task_id">): ResponseMetadata;
|
|
40
|
+
export declare function resolveContext(input: RawContextInput, deps: ToolDeps, context: ToolInvokeContext, readonly: boolean): Promise<SessionContext>;
|
|
41
|
+
export declare function requireDatabase(value: unknown, ctx: SessionContext): string;
|
|
42
|
+
export declare function asRequiredString(value: unknown, fieldName: string): string;
|
|
43
|
+
export declare function asOptionalString(value: unknown, fieldName: string): string | undefined;
|
|
44
|
+
export declare function asOptionalPositiveInteger(value: unknown, fieldName: string): number | undefined;
|
|
45
|
+
export declare function asOptionalBoolean(value: unknown, fieldName: string): boolean | undefined;
|
|
46
|
+
export declare function summarizeRows(rowCount: number, truncated: boolean): string;
|
|
47
|
+
export declare function summarizeMutation(affectedRows: number): string;
|
|
48
|
+
export declare function statementTypeFromSql(sql: string): StatementType | undefined;
|
|
49
|
+
export declare function invalidInputResponse(message: string, taskId: string, summary?: string): ToolResponse;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatError, ErrorCode, } from "../../utils/formatter.js";
|
|
3
|
+
import { ToolInputError } from "../error-handling.js";
|
|
4
|
+
const STATEMENT_TYPES = new Set([
|
|
5
|
+
"select",
|
|
6
|
+
"show",
|
|
7
|
+
"explain",
|
|
8
|
+
"describe",
|
|
9
|
+
"insert",
|
|
10
|
+
"update",
|
|
11
|
+
"delete",
|
|
12
|
+
"alter",
|
|
13
|
+
"drop",
|
|
14
|
+
"create",
|
|
15
|
+
"grant",
|
|
16
|
+
"revoke",
|
|
17
|
+
"unknown",
|
|
18
|
+
]);
|
|
19
|
+
export const contextInputShape = {
|
|
20
|
+
datasource: z
|
|
21
|
+
.string()
|
|
22
|
+
.trim()
|
|
23
|
+
.min(1)
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Datasource profile name. If omitted, the configured default datasource is used."),
|
|
26
|
+
database: z
|
|
27
|
+
.string()
|
|
28
|
+
.trim()
|
|
29
|
+
.min(1)
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Database name. Overrides the datasource default database for this tool call."),
|
|
32
|
+
schema: z
|
|
33
|
+
.string()
|
|
34
|
+
.trim()
|
|
35
|
+
.min(1)
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Optional schema name for engines that support schema scoping."),
|
|
38
|
+
timeout_ms: z
|
|
39
|
+
.number()
|
|
40
|
+
.int()
|
|
41
|
+
.positive()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Statement timeout in milliseconds. Clamped by the server-side maximum."),
|
|
44
|
+
};
|
|
45
|
+
export const diagnosticBaseInputShape = {
|
|
46
|
+
...contextInputShape,
|
|
47
|
+
time_range: z
|
|
48
|
+
.object({
|
|
49
|
+
from: z.string().trim().min(1).optional(),
|
|
50
|
+
to: z.string().trim().min(1).optional(),
|
|
51
|
+
relative: z.string().trim().min(1).optional(),
|
|
52
|
+
})
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Optional diagnosis window. Use from/to or a relative window such as 15m or 1h."),
|
|
55
|
+
evidence_level: z
|
|
56
|
+
.enum(["basic", "standard", "full"])
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("How much evidence the diagnostic should attempt to gather."),
|
|
59
|
+
include_raw_evidence: z
|
|
60
|
+
.boolean()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("Whether to include raw evidence references in the response."),
|
|
63
|
+
max_candidates: z
|
|
64
|
+
.number()
|
|
65
|
+
.int()
|
|
66
|
+
.positive()
|
|
67
|
+
.max(10)
|
|
68
|
+
.optional()
|
|
69
|
+
.describe("Maximum number of root-cause candidates to return."),
|
|
70
|
+
};
|
|
71
|
+
export function metadata(taskId, extra = {}) {
|
|
72
|
+
return {
|
|
73
|
+
task_id: taskId,
|
|
74
|
+
...extra,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export async function resolveContext(input, deps, context, readonly) {
|
|
78
|
+
return deps.engine.resolveContext({
|
|
79
|
+
datasource: asOptionalString(input.datasource, "datasource"),
|
|
80
|
+
database: asOptionalString(input.database, "database"),
|
|
81
|
+
schema: asOptionalString(input.schema, "schema"),
|
|
82
|
+
timeout_ms: asOptionalPositiveInteger(input.timeout_ms, "timeout_ms"),
|
|
83
|
+
readonly,
|
|
84
|
+
}, context.taskId);
|
|
85
|
+
}
|
|
86
|
+
export function requireDatabase(value, ctx) {
|
|
87
|
+
const explicit = asOptionalString(value, "database");
|
|
88
|
+
const resolved = explicit ?? ctx.database;
|
|
89
|
+
if (!resolved) {
|
|
90
|
+
throw new ToolInputError("Missing database. Provide input.database or configure a default database on the datasource profile.");
|
|
91
|
+
}
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|
|
94
|
+
export function asRequiredString(value, fieldName) {
|
|
95
|
+
if (typeof value !== "string") {
|
|
96
|
+
throw new ToolInputError(`Invalid ${fieldName}: expected a string.`);
|
|
97
|
+
}
|
|
98
|
+
const trimmed = value.trim();
|
|
99
|
+
if (!trimmed) {
|
|
100
|
+
throw new ToolInputError(`Invalid ${fieldName}: value cannot be empty.`);
|
|
101
|
+
}
|
|
102
|
+
return trimmed;
|
|
103
|
+
}
|
|
104
|
+
export function asOptionalString(value, fieldName) {
|
|
105
|
+
if (value === undefined) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return asRequiredString(value, fieldName);
|
|
109
|
+
}
|
|
110
|
+
export function asOptionalPositiveInteger(value, fieldName) {
|
|
111
|
+
if (value === undefined) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
115
|
+
throw new ToolInputError(`Invalid ${fieldName}: expected a positive integer.`);
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
export function asOptionalBoolean(value, fieldName) {
|
|
120
|
+
if (value === undefined) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
if (typeof value !== "boolean") {
|
|
124
|
+
throw new ToolInputError(`Invalid ${fieldName}: expected a boolean.`);
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
export function summarizeRows(rowCount, truncated) {
|
|
129
|
+
if (rowCount === 1) {
|
|
130
|
+
return truncated ? "Returned 1 row (truncated)." : "Returned 1 row.";
|
|
131
|
+
}
|
|
132
|
+
return truncated
|
|
133
|
+
? `Returned ${rowCount} rows (truncated).`
|
|
134
|
+
: `Returned ${rowCount} rows.`;
|
|
135
|
+
}
|
|
136
|
+
export function summarizeMutation(affectedRows) {
|
|
137
|
+
if (affectedRows === 1) {
|
|
138
|
+
return "Mutation completed. 1 row affected.";
|
|
139
|
+
}
|
|
140
|
+
return `Mutation completed. ${affectedRows} rows affected.`;
|
|
141
|
+
}
|
|
142
|
+
export function statementTypeFromSql(sql) {
|
|
143
|
+
const trimmed = sql.trim();
|
|
144
|
+
if (!trimmed) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const firstToken = trimmed.match(/^([a-z]+)/i)?.[1]?.toLowerCase();
|
|
148
|
+
if (!firstToken) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
if (firstToken === "desc") {
|
|
152
|
+
return "describe";
|
|
153
|
+
}
|
|
154
|
+
if (firstToken === "with") {
|
|
155
|
+
return "select";
|
|
156
|
+
}
|
|
157
|
+
return STATEMENT_TYPES.has(firstToken)
|
|
158
|
+
? firstToken
|
|
159
|
+
: undefined;
|
|
160
|
+
}
|
|
161
|
+
export function invalidInputResponse(message, taskId, summary = "Tool call failed due to invalid input.") {
|
|
162
|
+
return formatError({
|
|
163
|
+
code: ErrorCode.INVALID_INPUT,
|
|
164
|
+
message,
|
|
165
|
+
summary,
|
|
166
|
+
metadata: metadata(taskId),
|
|
167
|
+
});
|
|
168
|
+
}
|