Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 74 additions & 14 deletions mcp/src/tools/databaseSQL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,39 @@ function extractErrorCode(error: unknown) {
return typeof maybeCode === "string" ? maybeCode : undefined;
}

function isMySQLNotFoundError(error: unknown): boolean {
if (!error || typeof error !== "object") {
return false;
}

const errorCode = extractErrorCode(error);
const errorMessage = (error as Error).message?.toLowerCase() ?? "";

// Known error codes that indicate MySQL is not provisioned
const notFoundErrorCodes = [
"FailedOperation.DataSourceNotExist",
"ResourceNotFound.InstanceNotFound",
];
Comment on lines +291 to +294
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish missing DB instance from missing MySQL env

Treating ResourceNotFound.InstanceNotFound as a generic "MySQL not created" signal causes false guidance when callers provide an explicit dbInstance.instanceId that does not exist. In that case MySQL may already be provisioned, but runQuery/runStatement/initializeSchema now return MYSQL_NOT_CREATED and suggest provisionMySQL, which can trigger unnecessary billable reprovision attempts instead of prompting the caller to correct the instance identifier.

Useful? React with 👍 / 👎.


// Known error message patterns that indicate MySQL is not provisioned
const notFoundMessagePatterns = [
"database instance not found",
"pg instance not found",
"mysql instance not found",
"instance not found",
];

if (errorCode && notFoundErrorCodes.includes(errorCode)) {
return true;
}

if (notFoundMessagePatterns.some((pattern) => errorMessage.includes(pattern))) {
return true;
}

return false;
}

function normalizeCreateResultStatus(rawStatus: unknown): SqlLifecycleStatus {
if (typeof rawStatus !== "string" || rawStatus.trim().length === 0) {
return "PENDING";
Expand Down Expand Up @@ -505,7 +538,14 @@ function buildProvisionNextActions(
}

if (status === "FAILED") {
return [];
return [
buildNextAction(
MANAGE_SQL_DATABASE,
"provisionMySQL",
"MySQL provisioning failed. Retry provisioning with a new request.",
{ action: "provisionMySQL", confirm: true },
),
];
}

return [
Expand Down Expand Up @@ -534,7 +574,20 @@ function buildTaskStatusNextActions(
request?: Record<string, unknown>,
) {
if (status === "FAILED") {
return [];
const taskKind = inferTaskKind(request);
if (taskKind === "destroy") {
// For failed destroy tasks, the caller should decide what to do
return [];
}
// For failed provision tasks, suggest retrying provisioning
return [
buildNextAction(
MANAGE_SQL_DATABASE,
"provisionMySQL",
"MySQL task failed. Retry provisioning with a new request.",
{ action: "provisionMySQL", confirm: true },
),
];
}

if (status === "READY") {
Expand Down Expand Up @@ -621,8 +674,7 @@ async function handleRunQuery(
});
logCloudBaseResult(context.server.logger, result);
} catch (error: any) {
const errorCode = typeof error === "object" && error && "code" in error ? (error as any).code : "";
if (errorCode === "FailedOperation.DataSourceNotExist" || error.message?.includes("Database instance not found")) {
if (isMySQLNotFoundError(error)) {
return buildSqlToolResult({
success: false,
errorCode: "MYSQL_NOT_CREATED",
Expand Down Expand Up @@ -668,12 +720,27 @@ async function handleDescribeCreateResult(
const createData = pickDataPayload(result);
const rawStatus = createData?.Status ?? pickLifecycleSource(result);
const status = normalizeCreateResultStatus(rawStatus);
const failReason = createData?.FailReason ?? null;

// Build a more informative message when provisioning fails
let message: string;
if (status === "READY") {
message = "MySQL provisioning result indicates the instance is ready.";
} else if (status === "FAILED") {
message = failReason
? `MySQL provisioning failed. Reason: ${failReason}. Retry provisioning or check the environment status in the CloudBase console.`
: "MySQL provisioning failed. The FailReason was not provided. Retry provisioning or check the environment status in the CloudBase console at: https://tcb.cloud.tencent.com/dev?envId=" + envId + "#/db/mysql";
} else {
message = "MySQL provisioning has not completed yet.";
}

return buildSqlToolResult({
success: status !== "FAILED",
errorCode: status === "FAILED" ? "MYSQL_PROVISION_FAILED" : undefined,
data: {
status,
rawStatus,
failReason,
createResult: createData ?? result,
instance: {
envId,
Expand All @@ -689,12 +756,7 @@ async function handleDescribeCreateResult(
},
progress: pickProgress(result),
},
message:
status === "READY"
? "MySQL provisioning result indicates the instance is ready."
: status === "FAILED"
? "MySQL provisioning failed. Review the returned status and task details before retrying."
: "MySQL provisioning has not completed yet.",
message,
nextActions: buildProvisionNextActions(status, buildTaskRequest(request, result)),
});
}
Expand Down Expand Up @@ -990,8 +1052,7 @@ async function handleRunStatement(
});
logCloudBaseResult(context.server.logger, result);
} catch (error: any) {
const errorCode = typeof error === "object" && error && "code" in error ? (error as any).code : "";
if (errorCode === "FailedOperation.DataSourceNotExist" || error.message?.includes("Database instance not found")) {
if (isMySQLNotFoundError(error)) {
return buildSqlToolResult({
success: false,
errorCode: "MYSQL_NOT_CREATED",
Expand Down Expand Up @@ -1155,8 +1216,7 @@ async function handleInitializeSchema(
});
logCloudBaseResult(context.server.logger, result);
} catch (error: any) {
const errorCode = typeof error === "object" && error && "code" in error ? (error as any).code : "";
if (errorCode === "FailedOperation.DataSourceNotExist" || error.message?.includes("Database instance not found")) {
if (isMySQLNotFoundError(error)) {
return buildSqlToolResult({
success: false,
errorCode: "MYSQL_NOT_CREATED",
Expand Down
Loading