@cloudbase/cloudbase-mcp 2.25.4 → 2.25.5

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/dist/index.js CHANGED
@@ -2772,7 +2772,7 @@ ${envIdSection}
2772
2772
  ## 环境信息
2773
2773
  - 操作系统: ${os_1.default.type()} ${os_1.default.release()}
2774
2774
  - Node.js版本: ${process.version}
2775
- - MCP 版本:${process.env.npm_package_version || "2.25.4" || 0}
2775
+ - MCP 版本:${process.env.npm_package_version || "2.25.5" || 0}
2776
2776
  - 系统架构: ${os_1.default.arch()}
2777
2777
  - 时间: ${new Date().toISOString()}
2778
2778
  - 请求ID: ${requestId}
@@ -2853,7 +2853,7 @@ function createWrappedHandler(name, handler, server) {
2853
2853
  requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
2854
2854
  ide: server.ide || process.env.INTEGRATION_IDE || ''
2855
2855
  });
2856
- const mcpVersion = true ? "2.25.4" : 0;
2856
+ const mcpVersion = true ? "2.25.5" : 0;
2857
2857
  const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
2858
2858
  const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
2859
2859
  // 创建新的错误对象,保持原有的错误类型但更新消息
@@ -7960,7 +7960,11 @@ function registerGatewayTools(server) {
7960
7960
  return result ?? {};
7961
7961
  };
7962
7962
  const buildPrivilegeDescription = (privilege) => {
7963
- const serviceStatus = privilege.EnableService === true ? "已开启" : "未开启";
7963
+ const serviceStatus = privilege.EnableService === undefined
7964
+ ? "未知"
7965
+ : privilege.EnableService === true
7966
+ ? "已开启"
7967
+ : "未开启";
7964
7968
  const authStatus = privilege.EnableAuth === undefined
7965
7969
  ? "未知"
7966
7970
  : privilege.EnableAuth === true
@@ -8300,12 +8304,28 @@ function registerGatewayTools(server) {
8300
8304
  if (!input.domain || !input.certificateId) {
8301
8305
  throw new Error("action=bindCustomDomain 时必须提供 domain 和 certificateId");
8302
8306
  }
8307
+ const accessType = input.accessType ?? "DIRECT";
8308
+ if (accessType === "CUSTOM" && !input.customCname) {
8309
+ throw new Error("action=bindCustomDomain 且 accessType=CUSTOM 时必须提供 customCname(自有 CDN/WAF 的回源/回填地址,不是 DNS 解析目标)。" +
8310
+ "说明:https://docs.cloudbase.net/service/custom-domain");
8311
+ }
8312
+ if (accessType !== "CUSTOM" && input.customCname) {
8313
+ throw new Error("customCname 仅在 accessType=CUSTOM 时可用;普通绑定用默认 DIRECT,不要传 customCname。" +
8314
+ "说明:https://docs.cloudbase.net/service/custom-domain");
8315
+ }
8303
8316
  const cloudbase = await getManager();
8304
8317
  const result = await cloudbase.env.bindCustomDomain({
8305
8318
  EnvId: await resolveEnvId(),
8306
8319
  Domain: {
8307
8320
  Domain: input.domain,
8308
8321
  CertId: input.certificateId,
8322
+ AccessType: accessType,
8323
+ ...(input.enable !== undefined
8324
+ ? { Enable: input.enable }
8325
+ : {}),
8326
+ ...(input.customCname
8327
+ ? { CustomCname: input.customCname }
8328
+ : {}),
8309
8329
  },
8310
8330
  });
8311
8331
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
@@ -8313,24 +8333,64 @@ function registerGatewayTools(server) {
8313
8333
  action: input.action,
8314
8334
  domain: input.domain,
8315
8335
  certificateId: input.certificateId,
8336
+ accessType,
8337
+ ...(input.customCname
8338
+ ? { customCname: input.customCname }
8339
+ : {}),
8340
+ ...(input.enable !== undefined
8341
+ ? { enable: input.enable }
8342
+ : {}),
8316
8343
  raw: result,
8317
- }, "自定义域名绑定成功");
8344
+ }, `自定义域名绑定成功(${accessType})`, [
8345
+ {
8346
+ tool: "manageGateway",
8347
+ action: "createRoute",
8348
+ reason: "绑定后需 createRoute 添加访问路径,并完成 DNS CNAME 解析后才可访问",
8349
+ },
8350
+ ]);
8318
8351
  }
8319
8352
  case "deleteCustomDomain": {
8320
8353
  if (!input.domain) {
8321
8354
  throw new Error("action=deleteCustomDomain 时必须提供 domain");
8322
8355
  }
8323
8356
  const cloudbase = await getManager();
8324
- const result = await cloudbase.env.deleteCustomDomain({
8325
- EnvId: await resolveEnvId(),
8326
- Domain: input.domain,
8327
- });
8328
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
8329
- return buildEnvelope({
8330
- action: input.action,
8331
- domain: input.domain,
8332
- raw: result,
8333
- }, "自定义域名删除成功");
8357
+ try {
8358
+ const result = await cloudbase.env.deleteCustomDomain({
8359
+ EnvId: await resolveEnvId(),
8360
+ Domain: input.domain,
8361
+ });
8362
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
8363
+ return buildEnvelope({
8364
+ action: input.action,
8365
+ domain: input.domain,
8366
+ raw: result,
8367
+ }, "自定义域名删除成功");
8368
+ }
8369
+ catch (error) {
8370
+ const message = error instanceof Error ? error.message : String(error);
8371
+ // SDK 在域名下仍有路由绑定时抛错,需先删路由再删域名
8372
+ if (/route binding/i.test(message)) {
8373
+ return {
8374
+ success: false,
8375
+ data: { action: input.action, domain: input.domain },
8376
+ message: `域名 ${input.domain} 下仍有路由绑定,需先删除路由再删除域名。` +
8377
+ `原始错误:${message}`,
8378
+ nextActions: [
8379
+ {
8380
+ tool: "queryGateway",
8381
+ action: "listRoutes",
8382
+ reason: "查看该域名下的路由,确认需要删除的路径",
8383
+ },
8384
+ {
8385
+ tool: "manageGateway",
8386
+ action: "deleteRoute",
8387
+ reason: "先删除该域名下的全部路由(逐个 deleteRoute),再重试 deleteCustomDomain",
8388
+ },
8389
+ ],
8390
+ };
8391
+ }
8392
+ throw error;
8393
+ }
8334
8394
  }
8335
8395
  case "enableService": {
8336
8396
  if (typeof input.enable !== "boolean") {
@@ -8443,7 +8503,8 @@ function registerGatewayTools(server) {
8443
8503
  "enableService/authSwitch 开关 HTTP 网关总开关与访问鉴权(需配合 enable 参数)。" +
8444
8504
  "createRoute/updateRoute 必须提供 upstreamResourceType。" +
8445
8505
  "已有自定义域名时优先 createRoute(domain=已有域名) 实现访问,不必再次 bindCustomDomain / 传入 certificateId;" +
8446
- "bindCustomDomain 仅用于首次绑定新域名。"),
8506
+ "bindCustomDomain 仅用于首次绑定新域名(需 certificateId;可选 accessType=DIRECT|CDN|CUSTOM,CUSTOM 需 customCname;普通场景用默认 DIRECT)。" +
8507
+ "接入说明:https://docs.cloudbase.net/service/custom-domain"),
8447
8508
  targetName: zod_1.z
8448
8509
  .string()
8449
8510
  .optional()
@@ -8506,11 +8567,24 @@ function registerGatewayTools(server) {
8506
8567
  .optional()
8507
8568
  .describe("证书 ID。仅首次 bindCustomDomain 必填。" +
8508
8569
  "在已有自定义域名上 createRoute / updateRoute / deleteRoute 不需要 certificateId。"),
8570
+ accessType: zod_1.z
8571
+ .enum(["DIRECT", "CDN", "CUSTOM"])
8572
+ .optional()
8573
+ .describe("绑定类型(仅 bindCustomDomain,默认 DIRECT)。" +
8574
+ "DIRECT=直连(普通绑域名用这个);CDN=云开发 CDN;CUSTOM=自有 CDN/WAF(需 customCname)。" +
8575
+ "详见 https://docs.cloudbase.net/service/custom-domain"),
8576
+ customCname: zod_1.z
8577
+ .string()
8578
+ .optional()
8579
+ .describe("自有 CDN/WAF 的回源/回填地址(仅 bindCustomDomain 且 accessType=CUSTOM)。" +
8580
+ "不是 DNS 里用户域名要解析到的那个 CNAME;DIRECT/CDN 不要传。" +
8581
+ "详见 https://docs.cloudbase.net/service/custom-domain"),
8509
8582
  enable: zod_1.z
8510
8583
  .boolean()
8511
8584
  .optional()
8512
- .describe("开关目标状态(仅 enableService / authSwitch 使用,必填):" +
8513
- "enable=true 开启,enable=false 关闭。省略或非布尔值会返回参数错误。"),
8585
+ .describe("开关目标状态(enableService / authSwitch 必填):enable=true 开启,enable=false 关闭。" +
8586
+ "bindCustomDomain 可选:enable=false 表示绑定后禁用域名(默认启用)。" +
8587
+ "省略或非布尔值会返回参数错误。"),
8514
8588
  },
8515
8589
  annotations: {
8516
8590
  readOnlyHint: false,
@@ -11134,7 +11208,7 @@ class TelemetryReporter {
11134
11208
  const nodeVersion = process.version; // Node.js版本
11135
11209
  const arch = os_1.default.arch(); // 系统架构
11136
11210
  // 从构建时注入的版本号获取MCP版本信息
11137
- const mcpVersion = process.env.npm_package_version || "2.25.4" || 0;
11211
+ const mcpVersion = process.env.npm_package_version || "2.25.5" || 0;
11138
11212
  return {
11139
11213
  userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
11140
11214
  deviceId: this.deviceId,
@@ -13144,6 +13218,14 @@ function collectMigrationVersions(value, found = new Set()) {
13144
13218
  /** Brief retries before concluding a pushed migration never landed (Push can be async). */
13145
13219
  const MIGRATION_VERIFY_ATTEMPTS = 4;
13146
13220
  const MIGRATION_VERIFY_DELAY_MS = 1500;
13221
+ /** Page size when hydrating remote migration history into a Push/Preview payload. */
13222
+ const MIGRATION_LIST_PAGE_SIZE = 100;
13223
+ /**
13224
+ * PushPGUserMigrations is async: poll DescribeTaskResult until Succeed/Failed.
13225
+ * Aligns with CLI `tcb db pg migration up` and Manager SDK docs.
13226
+ */
13227
+ const MIGRATION_TASK_POLL_INTERVAL_MS = 1500;
13228
+ const MIGRATION_TASK_MAX_WAIT_MS = 90_000;
13147
13229
  /**
13148
13230
  * Confirm a pushed migration version is present in the remote migration history.
13149
13231
  *
@@ -13177,6 +13259,99 @@ async function verifyMigrationApplied(context, version, cloudBaseOptions) {
13177
13259
  }
13178
13260
  return { applied: null, error: lastError };
13179
13261
  }
13262
+ function extractMigrationSummaries(history) {
13263
+ const raw = history.Migrations;
13264
+ if (!Array.isArray(raw)) {
13265
+ return [];
13266
+ }
13267
+ const summaries = [];
13268
+ for (const item of raw) {
13269
+ if (!item || typeof item !== "object") {
13270
+ continue;
13271
+ }
13272
+ const row = item;
13273
+ const version = typeof row.Version === "string" ? row.Version.trim() : "";
13274
+ const name = typeof row.Name === "string" ? row.Name.trim() : "";
13275
+ if (version) {
13276
+ summaries.push({ Version: version, Name: name || version });
13277
+ }
13278
+ }
13279
+ return summaries;
13280
+ }
13281
+ /**
13282
+ * List every remote migration summary (paginated). Required before Push/Preview:
13283
+ * submitting only the pending migration while remote history exists yields
13284
+ * Executable=false with Conflicts reason remote_history_not_found_locally, and the
13285
+ * async task fails with "migration plan is not executable".
13286
+ */
13287
+ async function listAllRemoteMigrationSummaries(context, cloudBaseOptions) {
13288
+ const summaries = [];
13289
+ let offset = 0;
13290
+ let latestVersion = "";
13291
+ let total = 0;
13292
+ while (true) {
13293
+ const page = await callPgMigrationApi(context, "ListPGUserMigrations", { Limit: MIGRATION_LIST_PAGE_SIZE, Offset: offset }, cloudBaseOptions);
13294
+ const pageItems = extractMigrationSummaries(page);
13295
+ summaries.push(...pageItems);
13296
+ if (typeof page.LatestVersion === "string" && page.LatestVersion.trim()) {
13297
+ latestVersion = page.LatestVersion.trim();
13298
+ }
13299
+ if (typeof page.Total === "number" && Number.isFinite(page.Total)) {
13300
+ total = page.Total;
13301
+ }
13302
+ else {
13303
+ total = summaries.length;
13304
+ }
13305
+ if (pageItems.length < MIGRATION_LIST_PAGE_SIZE) {
13306
+ break;
13307
+ }
13308
+ offset += MIGRATION_LIST_PAGE_SIZE;
13309
+ }
13310
+ return { summaries, latestVersion, total };
13311
+ }
13312
+ /**
13313
+ * Build Push/Preview Migrations = remote history Queries + the pending migration.
13314
+ * Matches CLI behavior (submit full set; server skips already-applied versions).
13315
+ */
13316
+ async function buildMigrationsPayloadWithRemoteHistory(context, pending, cloudBaseOptions) {
13317
+ const { summaries, latestVersion } = await listAllRemoteMigrationSummaries(context, cloudBaseOptions);
13318
+ const migrations = [];
13319
+ for (const summary of summaries) {
13320
+ if (summary.Version === pending.Version) {
13321
+ // Caller supplies the authoritative SQL for this version.
13322
+ continue;
13323
+ }
13324
+ const detail = await callPgMigrationApi(context, "DescribePGUserMigration", { MigrationVersion: summary.Version }, cloudBaseOptions);
13325
+ const query = typeof detail.Query === "string" ? detail.Query : "";
13326
+ if (!query.trim()) {
13327
+ throw new Error(`DescribePGUserMigration returned empty Query for version=${summary.Version} (${summary.Name}). ` +
13328
+ "Cannot hydrate remote history for PushPGUserMigrations.");
13329
+ }
13330
+ migrations.push({
13331
+ Version: typeof detail.Version === "string" && detail.Version.trim() ? detail.Version.trim() : summary.Version,
13332
+ Name: typeof detail.Name === "string" && detail.Name.trim() ? detail.Name.trim() : summary.Name,
13333
+ Query: query,
13334
+ });
13335
+ }
13336
+ migrations.push(pending);
13337
+ return { migrations, remoteCount: summaries.length, latestVersion };
13338
+ }
13339
+ async function waitPgMigrationTask(context, taskId, cloudBaseOptions) {
13340
+ const deadline = Date.now() + MIGRATION_TASK_MAX_WAIT_MS;
13341
+ let last = { TaskId: taskId };
13342
+ while (Date.now() <= deadline) {
13343
+ last = (await callPgMigrationApi(context, "DescribeTaskResult", { TaskId: taskId }, cloudBaseOptions));
13344
+ const status = String(last.Status || "").toLowerCase();
13345
+ if (status === "succeed" || status === "failed") {
13346
+ return last;
13347
+ }
13348
+ await sleep(MIGRATION_TASK_POLL_INTERVAL_MS);
13349
+ }
13350
+ const error = new Error(`DescribeTaskResult timed out after ${Math.floor(MIGRATION_TASK_MAX_WAIT_MS / 1000)}s ` +
13351
+ `for TaskId=${taskId} (last Status=${last.Status || "-"}, Phase=${last.Phase || "-"}).`);
13352
+ error.lastTask = last;
13353
+ throw error;
13354
+ }
13180
13355
  const MIGRATION_VERSION_REQUIRED_MESSAGE = "migrationVersion is required (14-digit UTC timestamp YYYYMMDDHHMMSS). " +
13181
13356
  "Decide the version first, write local file migrations/<version>_<migrationName>.sql, " +
13182
13357
  "then call planMigration/applyMigration with the same migrationVersion and migrationName.";
@@ -13963,29 +14138,38 @@ async function handlePlanMigration(args, context, deps, cloudBaseOptions) {
13963
14138
  }
13964
14139
  const version = versionOrError;
13965
14140
  const localFileHint = buildLocalMigrationFileHint(version, args.migrationName);
13966
- const migration = {
14141
+ const pending = {
13967
14142
  Version: version,
13968
14143
  Name: args.migrationName,
13969
14144
  Query: args.sql,
13970
14145
  };
13971
14146
  if (args.rollbackSql?.trim()) {
13972
- migration.Rollback = args.rollbackSql;
14147
+ pending.Rollback = args.rollbackSql;
13973
14148
  }
13974
14149
  try {
14150
+ const hydrated = await buildMigrationsPayloadWithRemoteHistory(context, pending, cloudBaseOptions);
13975
14151
  const result = await callPgMigrationApi(context, "PreviewPGUserMigrations", {
13976
- Migrations: [migration],
14152
+ Migrations: hydrated.migrations,
13977
14153
  }, cloudBaseOptions);
14154
+ const executable = result.Executable === true;
13978
14155
  return buildPgToolResult({
13979
14156
  success: true,
13980
14157
  data: {
13981
14158
  migrationVersion: version,
13982
14159
  migrationName: args.migrationName,
13983
14160
  localFileHint,
14161
+ hydratedRemoteCount: hydrated.remoteCount,
14162
+ latestRemoteVersion: hydrated.latestVersion || null,
14163
+ executable,
13984
14164
  apiResult: result,
13985
14165
  },
13986
- message: `Migration plan generated via PreviewPGUserMigrations. Reuse migrationVersion=${version} on applyMigration. Ensure local file ${localFileHint} exists and matches.`,
14166
+ message: executable
14167
+ ? `Migration plan generated via PreviewPGUserMigrations (hydrated ${hydrated.remoteCount} remote migration(s)). Reuse migrationVersion=${version} on applyMigration. Ensure local file ${localFileHint} exists and matches.`
14168
+ : `Migration plan is NOT executable (PreviewPGUserMigrations.Executable=false after hydrating ${hydrated.remoteCount} remote migration(s)). Inspect apiResult.Conflicts before applyMigration. Common causes: version older than latest remote (${hydrated.latestVersion || "unknown"}), or checksum mismatch.`,
13987
14169
  nextActions: [
13988
- buildNextAction(MANAGE_PG_DATABASE, "applyMigration", "Review the plan above. If it looks correct, call applyMigration with the same migrationVersion.", {
14170
+ buildNextAction(MANAGE_PG_DATABASE, "applyMigration", executable
14171
+ ? "Review the plan above. If it looks correct, call applyMigration with the same migrationVersion."
14172
+ : "Resolve Conflicts (often pick a migrationVersion newer than LatestVersion), then retry planMigration/applyMigration.", {
13989
14173
  action: "applyMigration",
13990
14174
  migrationName: args.migrationName,
13991
14175
  migrationVersion: version,
@@ -14032,26 +14216,118 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14032
14216
  }
14033
14217
  const version = versionOrError;
14034
14218
  const localFileHint = buildLocalMigrationFileHint(version, args.migrationName);
14035
- const migration = {
14219
+ const pending = {
14036
14220
  Version: version,
14037
14221
  Name: args.migrationName,
14038
14222
  Query: args.sql,
14039
14223
  };
14040
14224
  if (args.rollbackSql?.trim()) {
14041
- migration.Rollback = args.rollbackSql;
14042
- }
14043
- const params = { Migrations: [migration] };
14044
- if (args.lockTimeoutMs !== undefined) {
14045
- params.LockTimeoutMs = args.lockTimeoutMs;
14046
- }
14047
- if (args.statementTimeoutMs !== undefined) {
14048
- params.StatementTimeoutMs = args.statementTimeoutMs;
14225
+ pending.Rollback = args.rollbackSql;
14049
14226
  }
14050
14227
  try {
14228
+ // Pushing only the pending migration while remote history exists makes the plan
14229
+ // non-executable (remote_history_not_found_locally). Hydrate remote Queries first,
14230
+ // matching CLI `tcb db pg migration up` (full list; server skips applied).
14231
+ const hydrated = await buildMigrationsPayloadWithRemoteHistory(context, pending, cloudBaseOptions);
14232
+ const preview = await callPgMigrationApi(context, "PreviewPGUserMigrations", { Migrations: hydrated.migrations }, cloudBaseOptions);
14233
+ if (preview.Executable !== true) {
14234
+ return buildPgToolResult({
14235
+ success: false,
14236
+ errorCode: "MIGRATION_NOT_EXECUTABLE",
14237
+ data: {
14238
+ migrationVersion: version,
14239
+ migrationName: args.migrationName,
14240
+ localFileHint,
14241
+ hydratedRemoteCount: hydrated.remoteCount,
14242
+ latestRemoteVersion: hydrated.latestVersion || null,
14243
+ previewResult: preview,
14244
+ verified: false,
14245
+ },
14246
+ message: `PreviewPGUserMigrations reported Executable=false for migrationVersion=${version} ` +
14247
+ `(latest remote=${hydrated.latestVersion || "unknown"}, hydratedRemoteCount=${hydrated.remoteCount}). ` +
14248
+ "Push was NOT submitted. Inspect previewResult.Conflicts — common reasons: " +
14249
+ "local_migration_before_latest_remote (pick a newer 14-digit version) or checksum_mismatch. " +
14250
+ "If the schema change is urgent you may fall back to action=execute with allowDdlViaExecute=true, but that bypasses migration history.",
14251
+ nextActions: [
14252
+ buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Check LatestVersion and pick a migrationVersion strictly newer than it.", { action: "listMigrations", limit: 20 }),
14253
+ buildNextAction(MANAGE_PG_DATABASE, "planMigration", "Re-run planMigration after adjusting migrationVersion/SQL.", {
14254
+ action: "planMigration",
14255
+ migrationName: args.migrationName,
14256
+ migrationVersion: version,
14257
+ sql: args.sql,
14258
+ ...(args.rollbackSql ? { rollbackSql: args.rollbackSql } : {}),
14259
+ }),
14260
+ ],
14261
+ });
14262
+ }
14263
+ const params = { Migrations: hydrated.migrations };
14264
+ if (args.lockTimeoutMs !== undefined) {
14265
+ params.LockTimeoutMs = args.lockTimeoutMs;
14266
+ }
14267
+ if (args.statementTimeoutMs !== undefined) {
14268
+ params.StatementTimeoutMs = args.statementTimeoutMs;
14269
+ }
14051
14270
  const result = await callPgMigrationApi(context, "PushPGUserMigrations", params, cloudBaseOptions);
14052
- // PushPGUserMigrations may return a TaskId without the migration having taken
14053
- // effect. Confirm against the remote history before reporting success, so callers
14054
- // never get a false-positive success for a silently dropped migration.
14271
+ const taskId = typeof result.TaskId === "string" ? result.TaskId.trim() : "";
14272
+ let taskResult = null;
14273
+ if (taskId) {
14274
+ try {
14275
+ taskResult = await waitPgMigrationTask(context, taskId, cloudBaseOptions);
14276
+ }
14277
+ catch (error) {
14278
+ const lastTask = error.lastTask;
14279
+ return buildPgToolResult({
14280
+ success: false,
14281
+ errorCode: "MIGRATION_TASK_TIMEOUT",
14282
+ data: {
14283
+ migrationVersion: version,
14284
+ migrationName: args.migrationName,
14285
+ localFileHint,
14286
+ hydratedRemoteCount: hydrated.remoteCount,
14287
+ apiResult: result,
14288
+ taskResult: lastTask ?? { TaskId: taskId },
14289
+ verified: null,
14290
+ },
14291
+ message: `PushPGUserMigrations returned TaskId=${taskId}, but waiting for DescribeTaskResult timed out: ` +
14292
+ `${error instanceof Error ? error.message : String(error)}. ` +
14293
+ "The migration may still be running — check action=listMigrations before retrying or falling back to execute.",
14294
+ nextActions: [
14295
+ buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Check whether the migrationVersion eventually landed.", { action: "listMigrations", limit: 20 }),
14296
+ ],
14297
+ });
14298
+ }
14299
+ const status = String(taskResult.Status || "").toLowerCase();
14300
+ if (status === "failed") {
14301
+ return buildPgToolResult({
14302
+ success: false,
14303
+ errorCode: "MIGRATION_TASK_FAILED",
14304
+ data: {
14305
+ migrationVersion: version,
14306
+ migrationName: args.migrationName,
14307
+ localFileHint,
14308
+ hydratedRemoteCount: hydrated.remoteCount,
14309
+ apiResult: result,
14310
+ taskResult: taskResult,
14311
+ verified: false,
14312
+ },
14313
+ message: `PushPGUserMigrations TaskId=${taskId} failed at phase=${taskResult.Phase || "-"}: ` +
14314
+ `${taskResult.Reason || "unknown reason"}. ` +
14315
+ "The migration did NOT take effect. Fix Conflicts/SQL (or use a newer migrationVersion), then retry applyMigration. " +
14316
+ "Urgent bypass: action=execute with allowDdlViaExecute=true (skips migration history).",
14317
+ nextActions: [
14318
+ buildNextAction(MANAGE_PG_DATABASE, "planMigration", "Preview again to inspect Conflicts before retrying apply.", {
14319
+ action: "planMigration",
14320
+ migrationName: args.migrationName,
14321
+ migrationVersion: version,
14322
+ sql: args.sql,
14323
+ ...(args.rollbackSql ? { rollbackSql: args.rollbackSql } : {}),
14324
+ }),
14325
+ buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Confirm the version is still absent from remote history.", { action: "listMigrations", limit: 20 }),
14326
+ ],
14327
+ });
14328
+ }
14329
+ }
14330
+ // Task Succeed (or sync response without TaskId): confirm history records the version.
14055
14331
  const verification = await verifyMigrationApplied(context, version, cloudBaseOptions);
14056
14332
  if (verification.applied === false) {
14057
14333
  return buildPgToolResult({
@@ -14061,10 +14337,12 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14061
14337
  migrationVersion: version,
14062
14338
  migrationName: args.migrationName,
14063
14339
  localFileHint,
14340
+ hydratedRemoteCount: hydrated.remoteCount,
14064
14341
  apiResult: result,
14342
+ taskResult: taskResult,
14065
14343
  verified: false,
14066
14344
  },
14067
- message: `PushPGUserMigrations returned without error, but migrationVersion=${version} is not present in the remote migration history, ` +
14345
+ message: `PushPGUserMigrations completed (TaskId=${taskId || "none"}), but migrationVersion=${version} is not present in the remote migration history, ` +
14068
14346
  "so the migration did NOT take effect. Do not assume the schema change is applied. " +
14069
14347
  "Re-check the SQL and migrationVersion, inspect the migration with action=migrationDetail, and retry action=applyMigration. " +
14070
14348
  "If the schema change is urgent you may fall back to action=execute with allowDdlViaExecute=true, but that bypasses migration history.",
@@ -14082,7 +14360,9 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14082
14360
  migrationVersion: version,
14083
14361
  migrationName: args.migrationName,
14084
14362
  localFileHint,
14363
+ hydratedRemoteCount: hydrated.remoteCount,
14085
14364
  apiResult: result,
14365
+ taskResult: taskResult,
14086
14366
  verified: null,
14087
14367
  },
14088
14368
  message: `PushPGUserMigrations was submitted, but verifying migrationVersion=${version} against the remote history failed: ${verification.error}. ` +
@@ -14098,10 +14378,12 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14098
14378
  migrationVersion: version,
14099
14379
  migrationName: args.migrationName,
14100
14380
  localFileHint,
14381
+ hydratedRemoteCount: hydrated.remoteCount,
14101
14382
  apiResult: result,
14383
+ taskResult: taskResult,
14102
14384
  verified: true,
14103
14385
  },
14104
- message: `Migrations applied via PushPGUserMigrations and verified present in the remote migration history. Ensure local file ${localFileHint} exists and matches.`,
14386
+ message: `Migrations applied via PushPGUserMigrations (hydrated ${hydrated.remoteCount} remote migration(s)), task ${taskId ? `TaskId=${taskId} ` : ""}verified present in the remote migration history. Ensure local file ${localFileHint} exists and matches.`,
14105
14387
  });
14106
14388
  }
14107
14389
  catch (error) {
@@ -15616,7 +15898,7 @@ function registerSetupTools(server) {
15616
15898
  server.registerTool("downloadTemplate", {
15617
15899
  title: "下载项目模板",
15618
15900
  description: `自动下载并部署CloudBase项目模板。
15619
- **Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.25.4" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
15901
+ **Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.25.5" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
15620
15902
  inputSchema: {
15621
15903
  template: zod_1.z
15622
15904
  .enum(["react", "vue", "miniprogram", "uniapp", "rules"])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/cloudbase-mcp",
3
- "version": "2.25.4",
3
+ "version": "2.25.5",
4
4
  "packageManager": "pnpm@10.8.0",
5
5
  "description": "CloudBase MCP Server — operate Tencent CloudBase (database, auth, functions, storage, hosting) from AI coding tools via the Model Context Protocol. Part of CloudBase AI Toolkit.",
6
6
  "main": "./dist/index.cjs",