@cloudbase/cloudbase-mcp 2.32.2 → 2.32.4

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
@@ -573,6 +573,31 @@ function applyBoundEnvRegion(server, region) {
573
573
  server.cloudBaseOptions.region = region;
574
574
  }
575
575
  }
576
+ /**
577
+ * 绑定环境后固化站点:region 歧义(如 ap-singapore 同时属于 domestic 与 intl)且
578
+ * 未显式指定 site 时,若仅一个凭证槽位可用,则将 TCB_SITE 固定为该站点,
579
+ * 避免后续资源调用再次落入歧义默认分支(issue #960)。
580
+ */
581
+ async function applyBoundEnvSite(server, region) {
582
+ if (!region || (0, site_map_js_1.getSite)(region) !== "ambiguous") {
583
+ return;
584
+ }
585
+ if ((0, site_map_js_1.normalizeSite)(process.env.TCB_SITE)) {
586
+ return; // 已显式指定 site,不覆盖
587
+ }
588
+ const usableSites = await (0, auth_js_1.listUsableCredentialSites)();
589
+ if (usableSites.length !== 1) {
590
+ return; // 无法唯一判定时不猜测,保持既有歧义回退逻辑
591
+ }
592
+ process.env.TCB_SITE = usableSites[0];
593
+ if (server.cloudBaseOptions) {
594
+ server.cloudBaseOptions.site = usableSites[0];
595
+ }
596
+ (0, logger_js_1.debug)("applyBoundEnvSite: pinned TCB_SITE from the only usable credential slot", {
597
+ region,
598
+ site: usableSites[0],
599
+ });
600
+ }
576
601
  const CODEBUDDY_AUTH_ACTIONS = ["status", "set_env", "login_by_api_key"];
577
602
  const DEFAULT_AUTH_ACTIONS = [
578
603
  "status",
@@ -1847,7 +1872,17 @@ function registerEnvTools(server) {
1847
1872
  : {}),
1848
1873
  onDeviceCode: deviceOnCode,
1849
1874
  })
1850
- .then(() => {
1875
+ .then(async () => {
1876
+ // toolbox 将新凭证写入 flat 'credential',这里迁移为分槽格式
1877
+ //(legacy flat 等价 domestic 槽位),对齐 ensureLogin() 的分槽行为
1878
+ try {
1879
+ await (0, auth_js_1.ensureSlottedCredential)();
1880
+ }
1881
+ catch (err) {
1882
+ (0, logger_js_1.debug)("device auth: slotted credential migration failed", {
1883
+ error: err instanceof Error ? err.message : String(err),
1884
+ });
1885
+ }
1851
1886
  (0, auth_js_1.resolveAuthProgressState)();
1852
1887
  })
1853
1888
  .catch((err) => {
@@ -2059,7 +2094,17 @@ function registerEnvTools(server) {
2059
2094
  });
2060
2095
  }
2061
2096
  await cloudbase_manager_js_1.envManager.setEnvId(envId);
2097
+ // 跨客户端通用事件推送(MCP 协议 notification,无 id):任何客户端
2098
+ // (如 dsh-plugin 面板)都能通过 notifications/cloudbase/env_changed
2099
+ // 感知环境变更,替代客户端本地推断与兜底轮询。通知失败不影响主流程。
2100
+ void server.server
2101
+ .notification({
2102
+ method: "notifications/cloudbase/env_changed",
2103
+ params: { envId },
2104
+ })
2105
+ .catch(() => { });
2062
2106
  applyBoundEnvRegion(server, target?.region);
2107
+ await applyBoundEnvSite(server, target?.region);
2063
2108
  const regionHint = target?.region
2064
2109
  ? `,地域: ${target.region}`
2065
2110
  : target
@@ -3272,6 +3317,11 @@ function buildCapiErrorMessage(service, action, error) {
3272
3317
  function registerCapiTools(server) {
3273
3318
  const cloudBaseOptions = server.cloudBaseOptions;
3274
3319
  const logger = server.logger;
3320
+ // commonService 透传调用只依赖凭据上下文(secretId/secretKey/token/region),
3321
+ // EnvId 由 params 携带,不依赖 MCP 绑定的环境。强制绑定会在「已登录但未绑环境」
3322
+ // 场景把 DescribeEnvs/CreateEnv/CAM 等无 env 依赖的 Action 全部挡在 ENV_REQUIRED,
3323
+ // 并诱发无头客户端原样重试风暴(2026-08-20 单日 15 万次报错),故豁免环境绑定。
3324
+ const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions, requireEnvId: false });
3275
3325
  server.registerTool?.("callCloudApi", {
3276
3326
  title: "调用云API",
3277
3327
  description: `通用的云 API 调用工具,主要用于 CloudBase / 腾讯云管控面与依赖资源相关 API 调用。调用前请先确认 service、Action 与 Param,避免猜测 Action 名称。如果你的目标是通过 HTTP 协议直接集成 auth/functions/cloudrun/storage/mysqldb 等 CloudBase 业务 API,请不要优先使用 callCloudApi,而应优先查看对应 OpenAPI / Swagger。现有 OpenAPI / Swagger 能力不是通用的管控面 Action 集合;管控面 API 请优先参考 CloudBase API 概览 ${CLOUDBASE_CONTROL_PLANE_DOC_URL} 与云开发依赖资源接口指引 ${CLOUDBASE_DEPENDENCY_API_DOC_URL}。对于 tcb service,常用 Action 分类如下:
@@ -3378,6 +3428,86 @@ function registerCapiTools(server) {
3378
3428
  }
3379
3429
 
3380
3430
 
3431
+ /***/ }),
3432
+
3433
+ /***/ 827:
3434
+ /***/ ((__unused_webpack_module, exports) => {
3435
+
3436
+
3437
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
3438
+ exports.REPEAT_GUARD_THRESHOLD = void 0;
3439
+ exports.applyRepeatGuardToPayload = applyRepeatGuardToPayload;
3440
+ exports.resetRepeatGuard = resetRepeatGuard;
3441
+ exports.__resetRepeatGuardForTests = __resetRepeatGuardForTests;
3442
+ /**
3443
+ * 连续重复错误熔断(repeat guard)。
3444
+ *
3445
+ * 背景:无头自动化客户端收到结构化引导错误(如 ENV_REQUIRED)后,可能不执行
3446
+ * 引导动作而是原样重试,形成重试风暴(2026-08-20 单日 15 万次同文案报错)。
3447
+ * 本模块在同一进程(≈同一 MCP session)内跟踪连续相同错误:
3448
+ * - 连续次数达到阈值后,在 payload 注入 repeat_guard 升级字段,提醒模型
3449
+ * 「原样重试无效,必须先完成 next_step 指向的修复动作」;
3450
+ * - 不改写 message 本身,保持灯塔侧按错误文案聚合的稳定性;
3451
+ * - 任一工具调用成功即整体清零(说明循环已被打破)。
3452
+ */
3453
+ exports.REPEAT_GUARD_THRESHOLD = 3;
3454
+ const REPEAT_GUARD_KEY_MAX_LENGTH = 200;
3455
+ let lastKey = null;
3456
+ let consecutiveCount = 0;
3457
+ function buildRepeatGuardKey(payload) {
3458
+ const code = typeof payload.code === "string" ? payload.code : "";
3459
+ const message = typeof payload.message === "string" ? payload.message : "";
3460
+ return `${code}|${message.slice(0, REPEAT_GUARD_KEY_MAX_LENGTH)}`;
3461
+ }
3462
+ function buildRepeatGuardNotice(count) {
3463
+ return (`当前会话已连续 ${count} 次返回相同错误。原样重试不会成功:` +
3464
+ "请停止重试当前调用,先按 payload 中的 message / next_step 完成修复动作" +
3465
+ "(如完成 auth 登录或绑定环境),或调整参数后再继续。");
3466
+ }
3467
+ /**
3468
+ * 记录一次结构化工具错误;达到阈值时返回注入 repeat_guard 的新 payload。
3469
+ */
3470
+ function applyRepeatGuardToPayload(payload) {
3471
+ if (!payload || typeof payload !== "object") {
3472
+ return payload;
3473
+ }
3474
+ const key = buildRepeatGuardKey(payload);
3475
+ if (key === lastKey) {
3476
+ consecutiveCount += 1;
3477
+ }
3478
+ else {
3479
+ lastKey = key;
3480
+ consecutiveCount = 1;
3481
+ }
3482
+ if (consecutiveCount < exports.REPEAT_GUARD_THRESHOLD) {
3483
+ return payload;
3484
+ }
3485
+ const code = typeof payload.code === "string" && payload.code.length > 0
3486
+ ? payload.code
3487
+ : "UNKNOWN";
3488
+ const repeatGuard = {
3489
+ code,
3490
+ consecutive_count: consecutiveCount,
3491
+ threshold: exports.REPEAT_GUARD_THRESHOLD,
3492
+ notice: buildRepeatGuardNotice(consecutiveCount),
3493
+ };
3494
+ return { ...payload, repeat_guard: repeatGuard };
3495
+ }
3496
+ /**
3497
+ * 工具调用成功时清零计数。
3498
+ */
3499
+ function resetRepeatGuard() {
3500
+ lastKey = null;
3501
+ consecutiveCount = 0;
3502
+ }
3503
+ /**
3504
+ * 测试辅助:恢复初始状态。
3505
+ */
3506
+ function __resetRepeatGuardForTests() {
3507
+ resetRepeatGuard();
3508
+ }
3509
+
3510
+
3381
3511
  /***/ }),
3382
3512
 
3383
3513
  /***/ 959:
@@ -3403,6 +3533,7 @@ const cloud_mode_js_1 = __webpack_require__(9684);
3403
3533
  const logger_js_1 = __webpack_require__(3039);
3404
3534
  const telemetry_js_1 = __webpack_require__(5880);
3405
3535
  const tool_result_js_1 = __webpack_require__(9835);
3536
+ const repeat_error_guard_js_1 = __webpack_require__(827);
3406
3537
  /**
3407
3538
  * Mirror annotations.category into _meta.category.
3408
3539
  *
@@ -3474,7 +3605,7 @@ ${envIdSection}
3474
3605
  ## 环境信息
3475
3606
  - 操作系统: ${os_1.default.type()} ${os_1.default.release()}
3476
3607
  - Node.js版本: ${process.version}
3477
- - MCP 版本:${process.env.npm_package_version || ( true ? "2.32.2" : 0)}
3608
+ - MCP 版本:${process.env.npm_package_version || ( true ? "2.32.4" : 0)}
3478
3609
  - 系统架构: ${os_1.default.arch()}
3479
3610
  - 时间: ${new Date().toISOString()}
3480
3611
  - 请求ID: ${requestId}
@@ -3520,6 +3651,8 @@ function createWrappedHandler(name, handler, server) {
3520
3651
  // 执行原始处理函数
3521
3652
  const result = await handler(args);
3522
3653
  success = true;
3654
+ // 任一工具成功说明重复错误循环已被打破,清零连续错误计数
3655
+ (0, repeat_error_guard_js_1.resetRepeatGuard)();
3523
3656
  requestId = extractRequestIdFromToolResult(result);
3524
3657
  const duration = Date.now() - startTime;
3525
3658
  (0, logger_js_1.debug)(`工具执行成功: ${name}`, { duration, requestId: requestId || undefined });
@@ -3544,7 +3677,9 @@ function createWrappedHandler(name, handler, server) {
3544
3677
  // Preserve structured tool guidance such as next_step.
3545
3678
  // These errors are expected control flow and should be serialized by the outer server wrapper.
3546
3679
  if ((0, tool_result_js_1.isToolPayloadError)(error)) {
3547
- throw error;
3680
+ // 连续相同结构化错误达到阈值时注入 repeat_guard 升级提示,
3681
+ // 打断无头客户端的原样重试循环(不改写 message,保持遥测聚合稳定)
3682
+ throw new tool_result_js_1.ToolPayloadError((0, repeat_error_guard_js_1.applyRepeatGuardToPayload)(error.payload));
3548
3683
  }
3549
3684
  // In tests, avoid any extra work that may block (envId lookup, issue link generation, etc.)
3550
3685
  if (isTestEnvironment) {
@@ -3555,7 +3690,7 @@ function createWrappedHandler(name, handler, server) {
3555
3690
  requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
3556
3691
  ide: server.ide || process.env.INTEGRATION_IDE || ''
3557
3692
  });
3558
- const mcpVersion = true ? "2.32.2" : 0;
3693
+ const mcpVersion = true ? "2.32.4" : 0;
3559
3694
  const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
3560
3695
  const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
3561
3696
  // 创建新的错误对象,保持原有的错误类型但更新消息
@@ -5581,14 +5716,20 @@ async function throwPendingAuthError() {
5581
5716
  async function throwEnvRequiredError(options) {
5582
5717
  const envCandidates = options?.envCandidates ?? (await listAvailableEnvCandidates(options));
5583
5718
  const singleEnvId = envCandidates.length === 1 ? envCandidates[0].envId : undefined;
5719
+ // 终止性引导:明确「重试当前工具不会成功」,给出可直接执行的替代动作与
5720
+ // 无头/无人值守环境的 fallback(CLOUDBASE_ENV_ID 或宿主配置传入 envId),
5721
+ // 避免客户端把本错误当作可重试错误陷入死循环(2026-08-20 单日 15 万次重试风暴)。
5722
+ const retryHint = "重试当前工具不会成功,请停止原样重试。";
5723
+ const headlessHint = "若运行在无浏览器/无人值守环境而无法交互完成登录绑定,可在启动 MCP 前设置 CLOUDBASE_ENV_ID 环境变量指定默认环境。";
5724
+ const message = envCandidates.length === 0
5725
+ ? `当前已登录,但还没有可用环境。${retryHint}请改为在腾讯云控制台创建 CloudBase 环境后重新绑定;${headlessHint}`
5726
+ : envCandidates.length === 1
5727
+ ? `当前已登录,但尚未绑定环境。${retryHint}请先调用 auth(action="set_env", envId="${singleEnvId}") 完成绑定后再继续;${headlessHint}`
5728
+ : `当前已登录,但尚未绑定环境。${retryHint}请先调用 auth(action="set_env", envId=候选环境之一) 完成绑定后再继续;${headlessHint}`;
5584
5729
  (0, tool_result_js_1.throwToolPayloadError)({
5585
5730
  ok: false,
5586
5731
  code: "ENV_REQUIRED",
5587
- message: envCandidates.length === 0
5588
- ? "当前已登录,但还没有可用环境,请先调用 auth 工具完成环境选择或创建环境。"
5589
- : envCandidates.length === 1
5590
- ? `当前已登录,但尚未绑定环境。可直接选择环境 ${singleEnvId}。`
5591
- : "当前已登录,但尚未绑定环境,请先调用 auth 工具选择环境。",
5732
+ message,
5592
5733
  env_candidates: envCandidates,
5593
5734
  next_step: (0, tool_result_js_1.buildAuthNextStep)("set_env", {
5594
5735
  requiredParams: singleEnvId ? undefined : ["envId"],
@@ -13041,8 +13182,9 @@ const SUPABASE_LIKE_SDK_HINTS = {
13041
13182
  emailOtp: "auth.signInWithOtp({ email })",
13042
13183
  password: "auth.signInWithPassword({ username|email|phone, password })",
13043
13184
  signup: "auth.signUp({ phone|email, ... })",
13044
- verifyOtp: "verifyOtp({ token })",
13185
+ verifyOtp: "const { data, error } = await auth.signInWithOtp({ email })\nconst { data: loginData, error: loginError } = await data.verifyOtp({ token })",
13045
13186
  anonymous: "auth.signInAnonymously()",
13187
+ caution: "verifyOtp is a callback on the signInWithOtp / signUp result: call data.verifyOtp({ token }) with only the token. Do NOT call a standalone auth.verifyOtp({ token }) — it requires an extra messageId and fails with 'messageId is required' when the messageId is missing. Error 'messageId is required' means you used the standalone call instead of the callback.",
13046
13188
  };
13047
13189
  function buildErrorEnvelope(error) {
13048
13190
  return {
@@ -13752,7 +13894,7 @@ class TelemetryReporter {
13752
13894
  const arch = os_1.default.arch(); // 系统架构
13753
13895
  // Build-time injected version; guard for vitest / non-webpack runtimes.
13754
13896
  const mcpVersion = process.env.npm_package_version ||
13755
- ( true ? "2.32.2" : 0);
13897
+ ( true ? "2.32.4" : 0);
13756
13898
  return {
13757
13899
  userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
13758
13900
  deviceId: this.deviceId,
@@ -15781,20 +15923,26 @@ async function resolvePgDbContext(cloudBaseOptions, args) {
15781
15923
  envId,
15782
15924
  instanceId: args?.instanceId ?? "cloudbase-pg",
15783
15925
  defaultSchema: args?.defaultSchema ?? "public",
15784
- role: args?.role ?? "cloudbase_admin",
15926
+ role: args?.role ?? PG_DEFAULT_ROLE,
15785
15927
  };
15786
15928
  }
15787
15929
  /** Default / recommended roles for CloudBase PG ExecutePGSql.Role */
15788
- const PG_DEFAULT_ROLE = "cloudbase_admin";
15930
+ const PG_DEFAULT_ROLE = "cloudbase_postgres";
15789
15931
  const PG_RECOMMENDED_ROLES = [
15790
- "cloudbase_admin",
15932
+ "cloudbase_postgres",
15791
15933
  "anon",
15792
15934
  "authenticated",
15793
15935
  "service_role",
15794
15936
  ];
15937
+ /**
15938
+ * Platform-owned admin roles. cloudbase_admin is the platform management
15939
+ * account and must not be used by user-facing ExecutePGSql calls.
15940
+ */
15941
+ const PG_PLATFORM_RESERVED_ROLES = ["cloudbase_admin"];
15795
15942
  /**
15796
15943
  * Roles that look like login/instance principals and routinely fail SET ROLE
15797
15944
  * in beacon data (e.g. postgres, postgres_pgdb_<id>). Reject before the API call.
15945
+ * Also rejects platform-reserved roles (cloudbase_admin) that are not for user calls.
15798
15946
  */
15799
15947
  function isLikelyInvalidPgExecuteRole(role) {
15800
15948
  const normalized = role.trim();
@@ -15804,6 +15952,10 @@ function isLikelyInvalidPgExecuteRole(role) {
15804
15952
  if (normalized === "postgres") {
15805
15953
  return true;
15806
15954
  }
15955
+ // Platform management account, reserved for the platform itself.
15956
+ if (PG_PLATFORM_RESERVED_ROLES.includes(normalized)) {
15957
+ return true;
15958
+ }
15807
15959
  // Instance-derived login principals, not valid ExecutePGSql Role targets
15808
15960
  if (/^postgres_pgdb_[a-z0-9]+$/i.test(normalized)) {
15809
15961
  return true;
@@ -15840,8 +15992,10 @@ function isPgRoleExecutionError(message) {
15840
15992
  }
15841
15993
  function buildPgRoleGuidanceMessage(attemptedRole) {
15842
15994
  const recommended = PG_RECOMMENDED_ROLES.join(", ");
15995
+ const reserved = PG_PLATFORM_RESERVED_ROLES.join(", ");
15843
15996
  return (`PostgreSQL role "${attemptedRole}" cannot be used with ExecutePGSql (SET ROLE failed or role does not exist). ` +
15844
15997
  `Do not invent roles like postgres / postgres_pgdb_* from env or instance names. ` +
15998
+ `Platform-reserved roles (${reserved}) are the platform management account and must not be used by user calls. ` +
15845
15999
  `Omit role (defaults to ${PG_DEFAULT_ROLE}) or pass one of: ${recommended}. ` +
15846
16000
  `To list roles that exist in this database, retry with role=${PG_DEFAULT_ROLE} and SQL: SELECT rolname FROM pg_roles ORDER BY rolname;`);
15847
16001
  }
@@ -18110,7 +18264,7 @@ function registerPGDatabaseTools(server, providedDeps) {
18110
18264
  .optional()
18111
18265
  .describe(`可选的 PostgreSQL role,传给 Manager SDK executePGSql 的 Role(平台会 SET ROLE)。默认 ${PG_DEFAULT_ROLE}。` +
18112
18266
  `推荐取值:${PG_RECOMMENDED_ROLES.join(" / ")}。` +
18113
- `不要传 postgres、postgres_pgdb_* 或从环境名臆造的角色;不确定时省略本字段,或先用 ${PG_DEFAULT_ROLE} 执行 SELECT rolname FROM pg_roles。`),
18267
+ `不要传 postgres、postgres_pgdb_*、平台保留角色(${PG_PLATFORM_RESERVED_ROLES.join("、")},为平台管理账号不对用户开放)或从环境名臆造的角色;不确定时省略本字段,或先用 ${PG_DEFAULT_ROLE} 执行 SELECT rolname FROM pg_roles。`),
18114
18268
  objectName: zod_1.z
18115
18269
  .string()
18116
18270
  .optional()
@@ -19659,7 +19813,7 @@ function registerSetupTools(server) {
19659
19813
  server.registerTool("downloadTemplate", {
19660
19814
  title: "下载项目模板",
19661
19815
  description: `自动下载并部署CloudBase项目模板。
19662
- **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.32.2" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
19816
+ **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.32.4" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
19663
19817
  inputSchema: {
19664
19818
  template: zod_1.z
19665
19819
  .enum(["react", "vue", "miniprogram", "uniapp", "rules"])
@@ -20982,9 +21136,55 @@ function buildUploadErrorMessage(error, localPath) {
20982
21136
  }
20983
21137
  return `[manageHosting(upload)] ${baseMessage}\n建议:${suggestions.join(' ')}`;
20984
21138
  }
21139
+ /**
21140
+ * TCB 管控面 DescribeStaticStore 接口 QPS 限流(20 次/秒)。
21141
+ * Manager SDK 的 hosting.deleteFiles / uploadFiles / findFiles 等每次调用
21142
+ * 都会先 checkStatus() → getInfo() → DescribeStaticStore(无缓存),
21143
+ * AI 连续快速删除多个文件或失败后立即重试时极易触发该限流。
21144
+ */
21145
+ const DESCRIBE_STATIC_STORE_RATE_LIMIT_RE = /\[DescribeStaticStore\][\s\S]*exceeds the frequency limit `20` for a second/i;
21146
+ function buildDeleteErrorMessage(error) {
21147
+ const baseMessage = error instanceof Error ? error.message : String(error);
21148
+ if (DESCRIBE_STATIC_STORE_RATE_LIMIT_RE.test(baseMessage)) {
21149
+ return (`[manageHosting(delete)] ${baseMessage}\n` +
21150
+ '原因:静态托管底层 DescribeStaticStore 管控接口有 20 次/秒的 QPS 限制,' +
21151
+ '连续快速删除多个文件(或失败后立即重试)容易触发限流,删除操作本身可能已经部分生效。\n' +
21152
+ '处理建议:\n' +
21153
+ '1) 等待 1-2 秒后重试本次删除,不要立即连续重试;\n' +
21154
+ '2) 批量删除多个文件时,逐次调用并保持间隔(建议每秒不超过 10 次),不要并发或循环快速重试;\n' +
21155
+ '3) 同一目录下的多个文件可改用 isDir=true 一次删除整个目录,减少调用次数;\n' +
21156
+ '4) 若不确定删除是否已生效,可调用 queryHosting(action="findFiles") 核对。');
21157
+ }
21158
+ return `[manageHosting(delete)] ${baseMessage}`;
21159
+ }
20985
21160
  function isRecord(value) {
20986
21161
  return typeof value === 'object' && value !== null && !Array.isArray(value);
20987
21162
  }
21163
+ /**
21164
+ * Manager SDK hosting.deleteFiles 对 COS 删除失败不抛异常,返回
21165
+ * { Deleted: [], Error: [e] };此处提取 Error 数组为可读字符串列表,
21166
+ * 供 delete 路径做失败判定与引导。
21167
+ */
21168
+ function extractDeleteErrors(result) {
21169
+ if (!isRecord(result)) {
21170
+ return [];
21171
+ }
21172
+ const rawErrors = result.Error ?? result.error;
21173
+ if (!Array.isArray(rawErrors)) {
21174
+ return [];
21175
+ }
21176
+ return rawErrors
21177
+ .map((item) => {
21178
+ if (typeof item === 'string' && item.trim()) {
21179
+ return item.trim();
21180
+ }
21181
+ if (isRecord(item) && typeof item.message === 'string') {
21182
+ return item.message;
21183
+ }
21184
+ return JSON.stringify(item);
21185
+ })
21186
+ .filter(Boolean);
21187
+ }
20988
21188
  function getRecordString(record, keys) {
20989
21189
  for (const key of keys) {
20990
21190
  const value = record[key];
@@ -21209,11 +21409,24 @@ async function getHostingStoreOrThrow(cloudbase, cloudBaseOptions, logger) {
21209
21409
  }
21210
21410
  throw new Error(`当前环境 ${envId} 未发现静态托管资源配置,无法上传文件。请先确认 MCP 已绑定到已开通静态托管的 CloudBase 环境;如需切换环境,请调用 auth(action="set_env", envId="目标环境ID") 后重试。`);
21211
21411
  }
21412
+ function enrichRateLimitMessage(message) {
21413
+ // delete 路径已由 buildDeleteErrorMessage 提供完整引导,避免重复追加
21414
+ if (message.includes('QPS 限制') || message.includes('等待 1-2 秒后重试')) {
21415
+ return message;
21416
+ }
21417
+ if (DESCRIBE_STATIC_STORE_RATE_LIMIT_RE.test(message)) {
21418
+ return (`${message}\n` +
21419
+ '原因:静态托管底层 DescribeStaticStore 管控接口有 20 次/秒的 QPS 限制,' +
21420
+ '连续快速调用(或失败后立即重试)容易触发限流,本次查询/操作可能没有完整生效。\n' +
21421
+ '处理建议:等待 1-2 秒后重试,不要连续快速重试;多个文件操作请逐次调用并保持间隔。');
21422
+ }
21423
+ return message;
21424
+ }
21212
21425
  function buildFailureResult(action, error) {
21213
21426
  return (0, tool_result_js_1.buildJsonToolResult)({
21214
21427
  success: false,
21215
21428
  errorCode: `HOSTING_${action.toUpperCase()}_FAILED`,
21216
- message: error instanceof Error ? error.message : String(error),
21429
+ message: enrichRateLimitMessage(error instanceof Error ? error.message : String(error)),
21217
21430
  });
21218
21431
  }
21219
21432
  function normalizeFileFields(files) {
@@ -21393,7 +21606,7 @@ function registerHostingTools(server) {
21393
21606
  });
21394
21607
  server.registerTool('manageHosting', {
21395
21608
  title: '管理 CloudBase 静态托管',
21396
- description: '管理 CloudBase 静态托管的变更操作。action=upload 上传本地构建产物到共享域名(域名格式:<envId>-<appId>.tcloudbaseapp.com/<cloudPath>);action=delete 删除托管文件或目录(必须 confirm=true);action=setWebsiteDocument 设置首页/错误页/路由规则;action=enableService 开通静态托管;action=bindDomain / unbindDomain / updateDomain 管理自定义域名;action=downloadFile / downloadDirectory 下载托管内容到本地。⚠️ 本工具没有关闭默认域名(*.tcloudbaseapp.com)的 action;要禁用该默认公网域名,请用 manageGateway(action="disableRoute", domain=该 STATIC_STORE IsDefault 域名, path="/")(底层 ModifyHTTPServiceRoute,不是 ModifyGatewayRoute)。⚠️ 新项目部署优先使用 manageApps(部署到独立子域名),本工具适合已有老项目继续使用或作为 manageApps 的 fallback。manageApps 与 manageHosting 域名不同,切换会导致老链接失效。若任务只是查看配置、文件或域名状态,请改用 queryHosting。',
21609
+ description: '管理 CloudBase 静态托管的变更操作。action=upload 上传本地构建产物到共享域名(域名格式:<envId>-<appId>.tcloudbaseapp.com/<cloudPath>);action=delete 删除托管文件或目录(必须 confirm=true);action=setWebsiteDocument 设置首页/错误页/路由规则;action=enableService 开通静态托管;action=bindDomain / unbindDomain / updateDomain 管理自定义域名;action=downloadFile / downloadDirectory 下载托管内容到本地。⚠️ 底层每次托管操作都会请求 DescribeStaticStore 管控接口(20 次/秒 QPS 限制):批量删除多个文件请逐次调用并保持间隔(建议每秒不超过 10 次),同一目录下多个文件可优先用 isDir=true 一次删除整个目录;若报错含 "frequency limit" 说明触发了限流,请等待 1-2 秒后重试,不要连续快速重试。⚠️ 本工具没有关闭默认域名(*.tcloudbaseapp.com)的 action;要禁用该默认公网域名,请用 manageGateway(action="disableRoute", domain=该 STATIC_STORE IsDefault 域名, path="/")(底层 ModifyHTTPServiceRoute,不是 ModifyGatewayRoute)。⚠️ 新项目部署优先使用 manageApps(部署到独立子域名),本工具适合已有老项目继续使用或作为 manageApps 的 fallback。manageApps 与 manageHosting 域名不同,切换会导致老链接失效。若任务只是查看配置、文件或域名状态,请改用 queryHosting。',
21397
21610
  inputSchema: manageHostingInputSchema,
21398
21611
  annotations: {
21399
21612
  readOnlyHint: false,
@@ -21563,14 +21776,27 @@ function registerHostingTools(server) {
21563
21776
  if (!input.confirm) {
21564
21777
  throw new Error('manageHosting(action="delete") 是破坏性操作,必须显式传 confirm=true。');
21565
21778
  }
21566
- const result = await cloudbase.hosting.deleteFiles({
21567
- cloudPath: input.cloudPath,
21568
- isDir: input.isDir ?? false,
21569
- });
21779
+ let result;
21780
+ try {
21781
+ result = await cloudbase.hosting.deleteFiles({
21782
+ cloudPath: input.cloudPath,
21783
+ isDir: input.isDir ?? false,
21784
+ });
21785
+ }
21786
+ catch (error) {
21787
+ throw new Error(buildDeleteErrorMessage(error));
21788
+ }
21570
21789
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
21790
+ // Manager SDK 的 deleteFiles 对 COS 单文件删除失败不抛异常,
21791
+ // 而是返回 { Deleted: [], Error: [e] },需要显式检查 Error 数组。
21792
+ const deleteErrors = extractDeleteErrors(result);
21571
21793
  // Post-validation: verify deletion was successful
21572
21794
  let deleteVerified = true;
21573
21795
  let verificationError;
21796
+ if (deleteErrors.length > 0) {
21797
+ deleteVerified = false;
21798
+ verificationError = `删除请求未生效:${deleteErrors.join(';')}`;
21799
+ }
21574
21800
  try {
21575
21801
  const checkResult = await cloudbase.hosting.findFiles({
21576
21802
  prefix: input.cloudPath,
@@ -21578,7 +21804,7 @@ function registerHostingTools(server) {
21578
21804
  });
21579
21805
  if (Array.isArray(checkResult) && checkResult.length > 0) {
21580
21806
  deleteVerified = false;
21581
- verificationError = `删除后验证失败:文件仍在静态托管中`;
21807
+ verificationError = verificationError ?? `删除后验证失败:文件仍在静态托管中`;
21582
21808
  }
21583
21809
  }
21584
21810
  catch (error) {
@@ -21597,7 +21823,11 @@ function registerHostingTools(server) {
21597
21823
  },
21598
21824
  message: deleteVerified
21599
21825
  ? `已删除静态托管${input.isDir ? '目录' : '文件'} \`${input.cloudPath}\`。`
21600
- : `删除操作已提交,但验证发现文件可能未完全删除,请手动确认。`,
21826
+ : `删除操作已提交,但验证发现文件可能未完全删除。` +
21827
+ `可能原因:底层 COS 删除请求失败(如文件不存在、存储桶权限不足),或触发 DescribeStaticStore 限流后删除未生效。` +
21828
+ `建议:1) 等待 1-2 秒后用 queryHosting(action="findFiles", prefix="${input.cloudPath}") 核对文件状态;` +
21829
+ `2) 若确认文件仍存在,重新调用 manageHosting(action="delete", confirm=true) 重试;` +
21830
+ `3) 批量删除多个文件请逐次调用并保持间隔(DescribeStaticStore 有 20 次/秒 QPS 限制)。`,
21601
21831
  });
21602
21832
  }
21603
21833
  case 'setWebsiteDocument': {
@@ -21766,6 +21996,8 @@ exports.setPendingAuthProgressState = setPendingAuthProgressState;
21766
21996
  exports.resolveAuthProgressState = resolveAuthProgressState;
21767
21997
  exports.rejectAuthProgressState = rejectAuthProgressState;
21768
21998
  exports.resetAuthProgressState = resetAuthProgressState;
21999
+ exports.ensureSlottedCredential = ensureSlottedCredential;
22000
+ exports.listUsableCredentialSites = listUsableCredentialSites;
21769
22001
  exports.peekLoginState = peekLoginState;
21770
22002
  exports.ensureLogin = ensureLogin;
21771
22003
  exports.getLoginState = getLoginState;
@@ -22113,6 +22345,20 @@ function isUsableCredential(value) {
22113
22345
  typeof value === "object" &&
22114
22346
  Object.keys(value).length > 0);
22115
22347
  }
22348
+ /**
22349
+ * 返回当前存在可用凭证的 site 槽位列表(凭证续期失败视为不可用)。
22350
+ * 用于歧义 region(如 ap-singapore)时判定用户实际所属站点。
22351
+ */
22352
+ async function listUsableCredentialSites() {
22353
+ const usable = [];
22354
+ for (const site of ["domestic", "intl"]) {
22355
+ const state = await resolveSiteLoginState(site).catch(() => null);
22356
+ if (state) {
22357
+ usable.push(site);
22358
+ }
22359
+ }
22360
+ return usable;
22361
+ }
22116
22362
  async function peekLoginState(options) {
22117
22363
  const envVarLoginState = normalizeLoginStateFromEnvVars(options);
22118
22364
  if (envVarLoginState) {
@@ -22135,8 +22381,23 @@ async function peekLoginState(options) {
22135
22381
  return envVarLoginState;
22136
22382
  }
22137
22383
  // 按 site 读取分槽凭证(缺省按 TCB_SITE/region 解析站点,默认 domestic)
22138
- const site = (0, site_map_js_1.normalizeSite)(options?.site) ?? (0, site_map_js_1.resolveSite)(options?.region, process.env.TCB_SITE);
22139
- const slotLoginState = await resolveSiteLoginState(site);
22384
+ const explicitSite = (0, site_map_js_1.normalizeSite)(options?.site) ?? (0, site_map_js_1.normalizeSite)(process.env.TCB_SITE);
22385
+ const site = explicitSite ?? (0, site_map_js_1.resolveSite)(options?.region);
22386
+ let slotLoginState = await resolveSiteLoginState(site);
22387
+ // 歧义 region(如 ap-singapore 同时属于 domestic 与 intl)且未显式指定 site 时,
22388
+ // 解析槽位为空则回退检查另一槽位:仅另一槽位有可用凭证时直接使用,
22389
+ // 避免对单站点用户误报 AUTH_REQUIRED(如国内站 ap-singapore 环境场景)。
22390
+ if (!slotLoginState && !explicitSite && (0, site_map_js_1.getSite)(options?.region) === "ambiguous") {
22391
+ const fallbackSite = site === "domestic" ? "intl" : "domestic";
22392
+ const fallbackLoginState = await resolveSiteLoginState(fallbackSite);
22393
+ if (fallbackLoginState) {
22394
+ (0, logger_js_1.debug)("peekLoginState: ambiguous region, using the only usable site slot", {
22395
+ resolvedSite: site,
22396
+ fallbackSite,
22397
+ });
22398
+ return fallbackLoginState;
22399
+ }
22400
+ }
22140
22401
  if (slotLoginState) {
22141
22402
  return slotLoginState;
22142
22403
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/cloudbase-mcp",
3
- "version": "2.32.2",
3
+ "version": "2.32.4",
4
4
  "mcpName": "io.github.TencentCloudBase/cloudbase-mcp",
5
5
  "packageManager": "pnpm@10.8.0",
6
6
  "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.",