@cloudbase/cloudbase-mcp 2.32.2 → 2.32.3
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/cli.cjs +3 -3
- package/dist/index.cjs +286 -25
- package/dist/index.js +285 -25
- package/package.json +1 -1
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.
|
|
3608
|
+
- MCP 版本:${process.env.npm_package_version || ( true ? "2.32.3" : 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
|
-
|
|
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.
|
|
3693
|
+
const mcpVersion = true ? "2.32.3" : 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
|
|
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"],
|
|
@@ -13752,7 +13893,7 @@ class TelemetryReporter {
|
|
|
13752
13893
|
const arch = os_1.default.arch(); // 系统架构
|
|
13753
13894
|
// Build-time injected version; guard for vitest / non-webpack runtimes.
|
|
13754
13895
|
const mcpVersion = process.env.npm_package_version ||
|
|
13755
|
-
( true ? "2.32.
|
|
13896
|
+
( true ? "2.32.3" : 0);
|
|
13756
13897
|
return {
|
|
13757
13898
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
13758
13899
|
deviceId: this.deviceId,
|
|
@@ -15781,20 +15922,26 @@ async function resolvePgDbContext(cloudBaseOptions, args) {
|
|
|
15781
15922
|
envId,
|
|
15782
15923
|
instanceId: args?.instanceId ?? "cloudbase-pg",
|
|
15783
15924
|
defaultSchema: args?.defaultSchema ?? "public",
|
|
15784
|
-
role: args?.role ??
|
|
15925
|
+
role: args?.role ?? PG_DEFAULT_ROLE,
|
|
15785
15926
|
};
|
|
15786
15927
|
}
|
|
15787
15928
|
/** Default / recommended roles for CloudBase PG ExecutePGSql.Role */
|
|
15788
|
-
const PG_DEFAULT_ROLE = "
|
|
15929
|
+
const PG_DEFAULT_ROLE = "cloudbase_postgres";
|
|
15789
15930
|
const PG_RECOMMENDED_ROLES = [
|
|
15790
|
-
"
|
|
15931
|
+
"cloudbase_postgres",
|
|
15791
15932
|
"anon",
|
|
15792
15933
|
"authenticated",
|
|
15793
15934
|
"service_role",
|
|
15794
15935
|
];
|
|
15936
|
+
/**
|
|
15937
|
+
* Platform-owned admin roles. cloudbase_admin is the platform management
|
|
15938
|
+
* account and must not be used by user-facing ExecutePGSql calls.
|
|
15939
|
+
*/
|
|
15940
|
+
const PG_PLATFORM_RESERVED_ROLES = ["cloudbase_admin"];
|
|
15795
15941
|
/**
|
|
15796
15942
|
* Roles that look like login/instance principals and routinely fail SET ROLE
|
|
15797
15943
|
* in beacon data (e.g. postgres, postgres_pgdb_<id>). Reject before the API call.
|
|
15944
|
+
* Also rejects platform-reserved roles (cloudbase_admin) that are not for user calls.
|
|
15798
15945
|
*/
|
|
15799
15946
|
function isLikelyInvalidPgExecuteRole(role) {
|
|
15800
15947
|
const normalized = role.trim();
|
|
@@ -15804,6 +15951,10 @@ function isLikelyInvalidPgExecuteRole(role) {
|
|
|
15804
15951
|
if (normalized === "postgres") {
|
|
15805
15952
|
return true;
|
|
15806
15953
|
}
|
|
15954
|
+
// Platform management account, reserved for the platform itself.
|
|
15955
|
+
if (PG_PLATFORM_RESERVED_ROLES.includes(normalized)) {
|
|
15956
|
+
return true;
|
|
15957
|
+
}
|
|
15807
15958
|
// Instance-derived login principals, not valid ExecutePGSql Role targets
|
|
15808
15959
|
if (/^postgres_pgdb_[a-z0-9]+$/i.test(normalized)) {
|
|
15809
15960
|
return true;
|
|
@@ -15840,8 +15991,10 @@ function isPgRoleExecutionError(message) {
|
|
|
15840
15991
|
}
|
|
15841
15992
|
function buildPgRoleGuidanceMessage(attemptedRole) {
|
|
15842
15993
|
const recommended = PG_RECOMMENDED_ROLES.join(", ");
|
|
15994
|
+
const reserved = PG_PLATFORM_RESERVED_ROLES.join(", ");
|
|
15843
15995
|
return (`PostgreSQL role "${attemptedRole}" cannot be used with ExecutePGSql (SET ROLE failed or role does not exist). ` +
|
|
15844
15996
|
`Do not invent roles like postgres / postgres_pgdb_* from env or instance names. ` +
|
|
15997
|
+
`Platform-reserved roles (${reserved}) are the platform management account and must not be used by user calls. ` +
|
|
15845
15998
|
`Omit role (defaults to ${PG_DEFAULT_ROLE}) or pass one of: ${recommended}. ` +
|
|
15846
15999
|
`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
16000
|
}
|
|
@@ -18110,7 +18263,7 @@ function registerPGDatabaseTools(server, providedDeps) {
|
|
|
18110
18263
|
.optional()
|
|
18111
18264
|
.describe(`可选的 PostgreSQL role,传给 Manager SDK executePGSql 的 Role(平台会 SET ROLE)。默认 ${PG_DEFAULT_ROLE}。` +
|
|
18112
18265
|
`推荐取值:${PG_RECOMMENDED_ROLES.join(" / ")}。` +
|
|
18113
|
-
`不要传 postgres、postgres_pgdb_
|
|
18266
|
+
`不要传 postgres、postgres_pgdb_*、平台保留角色(${PG_PLATFORM_RESERVED_ROLES.join("、")},为平台管理账号不对用户开放)或从环境名臆造的角色;不确定时省略本字段,或先用 ${PG_DEFAULT_ROLE} 执行 SELECT rolname FROM pg_roles。`),
|
|
18114
18267
|
objectName: zod_1.z
|
|
18115
18268
|
.string()
|
|
18116
18269
|
.optional()
|
|
@@ -19659,7 +19812,7 @@ function registerSetupTools(server) {
|
|
|
19659
19812
|
server.registerTool("downloadTemplate", {
|
|
19660
19813
|
title: "下载项目模板",
|
|
19661
19814
|
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.
|
|
19815
|
+
**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.3" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
|
|
19663
19816
|
inputSchema: {
|
|
19664
19817
|
template: zod_1.z
|
|
19665
19818
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|
|
@@ -20982,9 +21135,55 @@ function buildUploadErrorMessage(error, localPath) {
|
|
|
20982
21135
|
}
|
|
20983
21136
|
return `[manageHosting(upload)] ${baseMessage}\n建议:${suggestions.join(' ')}`;
|
|
20984
21137
|
}
|
|
21138
|
+
/**
|
|
21139
|
+
* TCB 管控面 DescribeStaticStore 接口 QPS 限流(20 次/秒)。
|
|
21140
|
+
* Manager SDK 的 hosting.deleteFiles / uploadFiles / findFiles 等每次调用
|
|
21141
|
+
* 都会先 checkStatus() → getInfo() → DescribeStaticStore(无缓存),
|
|
21142
|
+
* AI 连续快速删除多个文件或失败后立即重试时极易触发该限流。
|
|
21143
|
+
*/
|
|
21144
|
+
const DESCRIBE_STATIC_STORE_RATE_LIMIT_RE = /\[DescribeStaticStore\][\s\S]*exceeds the frequency limit `20` for a second/i;
|
|
21145
|
+
function buildDeleteErrorMessage(error) {
|
|
21146
|
+
const baseMessage = error instanceof Error ? error.message : String(error);
|
|
21147
|
+
if (DESCRIBE_STATIC_STORE_RATE_LIMIT_RE.test(baseMessage)) {
|
|
21148
|
+
return (`[manageHosting(delete)] ${baseMessage}\n` +
|
|
21149
|
+
'原因:静态托管底层 DescribeStaticStore 管控接口有 20 次/秒的 QPS 限制,' +
|
|
21150
|
+
'连续快速删除多个文件(或失败后立即重试)容易触发限流,删除操作本身可能已经部分生效。\n' +
|
|
21151
|
+
'处理建议:\n' +
|
|
21152
|
+
'1) 等待 1-2 秒后重试本次删除,不要立即连续重试;\n' +
|
|
21153
|
+
'2) 批量删除多个文件时,逐次调用并保持间隔(建议每秒不超过 10 次),不要并发或循环快速重试;\n' +
|
|
21154
|
+
'3) 同一目录下的多个文件可改用 isDir=true 一次删除整个目录,减少调用次数;\n' +
|
|
21155
|
+
'4) 若不确定删除是否已生效,可调用 queryHosting(action="findFiles") 核对。');
|
|
21156
|
+
}
|
|
21157
|
+
return `[manageHosting(delete)] ${baseMessage}`;
|
|
21158
|
+
}
|
|
20985
21159
|
function isRecord(value) {
|
|
20986
21160
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
20987
21161
|
}
|
|
21162
|
+
/**
|
|
21163
|
+
* Manager SDK hosting.deleteFiles 对 COS 删除失败不抛异常,返回
|
|
21164
|
+
* { Deleted: [], Error: [e] };此处提取 Error 数组为可读字符串列表,
|
|
21165
|
+
* 供 delete 路径做失败判定与引导。
|
|
21166
|
+
*/
|
|
21167
|
+
function extractDeleteErrors(result) {
|
|
21168
|
+
if (!isRecord(result)) {
|
|
21169
|
+
return [];
|
|
21170
|
+
}
|
|
21171
|
+
const rawErrors = result.Error ?? result.error;
|
|
21172
|
+
if (!Array.isArray(rawErrors)) {
|
|
21173
|
+
return [];
|
|
21174
|
+
}
|
|
21175
|
+
return rawErrors
|
|
21176
|
+
.map((item) => {
|
|
21177
|
+
if (typeof item === 'string' && item.trim()) {
|
|
21178
|
+
return item.trim();
|
|
21179
|
+
}
|
|
21180
|
+
if (isRecord(item) && typeof item.message === 'string') {
|
|
21181
|
+
return item.message;
|
|
21182
|
+
}
|
|
21183
|
+
return JSON.stringify(item);
|
|
21184
|
+
})
|
|
21185
|
+
.filter(Boolean);
|
|
21186
|
+
}
|
|
20988
21187
|
function getRecordString(record, keys) {
|
|
20989
21188
|
for (const key of keys) {
|
|
20990
21189
|
const value = record[key];
|
|
@@ -21209,11 +21408,24 @@ async function getHostingStoreOrThrow(cloudbase, cloudBaseOptions, logger) {
|
|
|
21209
21408
|
}
|
|
21210
21409
|
throw new Error(`当前环境 ${envId} 未发现静态托管资源配置,无法上传文件。请先确认 MCP 已绑定到已开通静态托管的 CloudBase 环境;如需切换环境,请调用 auth(action="set_env", envId="目标环境ID") 后重试。`);
|
|
21211
21410
|
}
|
|
21411
|
+
function enrichRateLimitMessage(message) {
|
|
21412
|
+
// delete 路径已由 buildDeleteErrorMessage 提供完整引导,避免重复追加
|
|
21413
|
+
if (message.includes('QPS 限制') || message.includes('等待 1-2 秒后重试')) {
|
|
21414
|
+
return message;
|
|
21415
|
+
}
|
|
21416
|
+
if (DESCRIBE_STATIC_STORE_RATE_LIMIT_RE.test(message)) {
|
|
21417
|
+
return (`${message}\n` +
|
|
21418
|
+
'原因:静态托管底层 DescribeStaticStore 管控接口有 20 次/秒的 QPS 限制,' +
|
|
21419
|
+
'连续快速调用(或失败后立即重试)容易触发限流,本次查询/操作可能没有完整生效。\n' +
|
|
21420
|
+
'处理建议:等待 1-2 秒后重试,不要连续快速重试;多个文件操作请逐次调用并保持间隔。');
|
|
21421
|
+
}
|
|
21422
|
+
return message;
|
|
21423
|
+
}
|
|
21212
21424
|
function buildFailureResult(action, error) {
|
|
21213
21425
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
21214
21426
|
success: false,
|
|
21215
21427
|
errorCode: `HOSTING_${action.toUpperCase()}_FAILED`,
|
|
21216
|
-
message: error instanceof Error ? error.message : String(error),
|
|
21428
|
+
message: enrichRateLimitMessage(error instanceof Error ? error.message : String(error)),
|
|
21217
21429
|
});
|
|
21218
21430
|
}
|
|
21219
21431
|
function normalizeFileFields(files) {
|
|
@@ -21393,7 +21605,7 @@ function registerHostingTools(server) {
|
|
|
21393
21605
|
});
|
|
21394
21606
|
server.registerTool('manageHosting', {
|
|
21395
21607
|
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。',
|
|
21608
|
+
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
21609
|
inputSchema: manageHostingInputSchema,
|
|
21398
21610
|
annotations: {
|
|
21399
21611
|
readOnlyHint: false,
|
|
@@ -21563,14 +21775,27 @@ function registerHostingTools(server) {
|
|
|
21563
21775
|
if (!input.confirm) {
|
|
21564
21776
|
throw new Error('manageHosting(action="delete") 是破坏性操作,必须显式传 confirm=true。');
|
|
21565
21777
|
}
|
|
21566
|
-
|
|
21567
|
-
|
|
21568
|
-
|
|
21569
|
-
|
|
21778
|
+
let result;
|
|
21779
|
+
try {
|
|
21780
|
+
result = await cloudbase.hosting.deleteFiles({
|
|
21781
|
+
cloudPath: input.cloudPath,
|
|
21782
|
+
isDir: input.isDir ?? false,
|
|
21783
|
+
});
|
|
21784
|
+
}
|
|
21785
|
+
catch (error) {
|
|
21786
|
+
throw new Error(buildDeleteErrorMessage(error));
|
|
21787
|
+
}
|
|
21570
21788
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
21789
|
+
// Manager SDK 的 deleteFiles 对 COS 单文件删除失败不抛异常,
|
|
21790
|
+
// 而是返回 { Deleted: [], Error: [e] },需要显式检查 Error 数组。
|
|
21791
|
+
const deleteErrors = extractDeleteErrors(result);
|
|
21571
21792
|
// Post-validation: verify deletion was successful
|
|
21572
21793
|
let deleteVerified = true;
|
|
21573
21794
|
let verificationError;
|
|
21795
|
+
if (deleteErrors.length > 0) {
|
|
21796
|
+
deleteVerified = false;
|
|
21797
|
+
verificationError = `删除请求未生效:${deleteErrors.join(';')}`;
|
|
21798
|
+
}
|
|
21574
21799
|
try {
|
|
21575
21800
|
const checkResult = await cloudbase.hosting.findFiles({
|
|
21576
21801
|
prefix: input.cloudPath,
|
|
@@ -21578,7 +21803,7 @@ function registerHostingTools(server) {
|
|
|
21578
21803
|
});
|
|
21579
21804
|
if (Array.isArray(checkResult) && checkResult.length > 0) {
|
|
21580
21805
|
deleteVerified = false;
|
|
21581
|
-
verificationError = `删除后验证失败:文件仍在静态托管中`;
|
|
21806
|
+
verificationError = verificationError ?? `删除后验证失败:文件仍在静态托管中`;
|
|
21582
21807
|
}
|
|
21583
21808
|
}
|
|
21584
21809
|
catch (error) {
|
|
@@ -21597,7 +21822,11 @@ function registerHostingTools(server) {
|
|
|
21597
21822
|
},
|
|
21598
21823
|
message: deleteVerified
|
|
21599
21824
|
? `已删除静态托管${input.isDir ? '目录' : '文件'} \`${input.cloudPath}\`。`
|
|
21600
|
-
:
|
|
21825
|
+
: `删除操作已提交,但验证发现文件可能未完全删除。` +
|
|
21826
|
+
`可能原因:底层 COS 删除请求失败(如文件不存在、存储桶权限不足),或触发 DescribeStaticStore 限流后删除未生效。` +
|
|
21827
|
+
`建议:1) 等待 1-2 秒后用 queryHosting(action="findFiles", prefix="${input.cloudPath}") 核对文件状态;` +
|
|
21828
|
+
`2) 若确认文件仍存在,重新调用 manageHosting(action="delete", confirm=true) 重试;` +
|
|
21829
|
+
`3) 批量删除多个文件请逐次调用并保持间隔(DescribeStaticStore 有 20 次/秒 QPS 限制)。`,
|
|
21601
21830
|
});
|
|
21602
21831
|
}
|
|
21603
21832
|
case 'setWebsiteDocument': {
|
|
@@ -21766,6 +21995,8 @@ exports.setPendingAuthProgressState = setPendingAuthProgressState;
|
|
|
21766
21995
|
exports.resolveAuthProgressState = resolveAuthProgressState;
|
|
21767
21996
|
exports.rejectAuthProgressState = rejectAuthProgressState;
|
|
21768
21997
|
exports.resetAuthProgressState = resetAuthProgressState;
|
|
21998
|
+
exports.ensureSlottedCredential = ensureSlottedCredential;
|
|
21999
|
+
exports.listUsableCredentialSites = listUsableCredentialSites;
|
|
21769
22000
|
exports.peekLoginState = peekLoginState;
|
|
21770
22001
|
exports.ensureLogin = ensureLogin;
|
|
21771
22002
|
exports.getLoginState = getLoginState;
|
|
@@ -22113,6 +22344,20 @@ function isUsableCredential(value) {
|
|
|
22113
22344
|
typeof value === "object" &&
|
|
22114
22345
|
Object.keys(value).length > 0);
|
|
22115
22346
|
}
|
|
22347
|
+
/**
|
|
22348
|
+
* 返回当前存在可用凭证的 site 槽位列表(凭证续期失败视为不可用)。
|
|
22349
|
+
* 用于歧义 region(如 ap-singapore)时判定用户实际所属站点。
|
|
22350
|
+
*/
|
|
22351
|
+
async function listUsableCredentialSites() {
|
|
22352
|
+
const usable = [];
|
|
22353
|
+
for (const site of ["domestic", "intl"]) {
|
|
22354
|
+
const state = await resolveSiteLoginState(site).catch(() => null);
|
|
22355
|
+
if (state) {
|
|
22356
|
+
usable.push(site);
|
|
22357
|
+
}
|
|
22358
|
+
}
|
|
22359
|
+
return usable;
|
|
22360
|
+
}
|
|
22116
22361
|
async function peekLoginState(options) {
|
|
22117
22362
|
const envVarLoginState = normalizeLoginStateFromEnvVars(options);
|
|
22118
22363
|
if (envVarLoginState) {
|
|
@@ -22135,8 +22380,23 @@ async function peekLoginState(options) {
|
|
|
22135
22380
|
return envVarLoginState;
|
|
22136
22381
|
}
|
|
22137
22382
|
// 按 site 读取分槽凭证(缺省按 TCB_SITE/region 解析站点,默认 domestic)
|
|
22138
|
-
const
|
|
22139
|
-
const
|
|
22383
|
+
const explicitSite = (0, site_map_js_1.normalizeSite)(options?.site) ?? (0, site_map_js_1.normalizeSite)(process.env.TCB_SITE);
|
|
22384
|
+
const site = explicitSite ?? (0, site_map_js_1.resolveSite)(options?.region);
|
|
22385
|
+
let slotLoginState = await resolveSiteLoginState(site);
|
|
22386
|
+
// 歧义 region(如 ap-singapore 同时属于 domestic 与 intl)且未显式指定 site 时,
|
|
22387
|
+
// 解析槽位为空则回退检查另一槽位:仅另一槽位有可用凭证时直接使用,
|
|
22388
|
+
// 避免对单站点用户误报 AUTH_REQUIRED(如国内站 ap-singapore 环境场景)。
|
|
22389
|
+
if (!slotLoginState && !explicitSite && (0, site_map_js_1.getSite)(options?.region) === "ambiguous") {
|
|
22390
|
+
const fallbackSite = site === "domestic" ? "intl" : "domestic";
|
|
22391
|
+
const fallbackLoginState = await resolveSiteLoginState(fallbackSite);
|
|
22392
|
+
if (fallbackLoginState) {
|
|
22393
|
+
(0, logger_js_1.debug)("peekLoginState: ambiguous region, using the only usable site slot", {
|
|
22394
|
+
resolvedSite: site,
|
|
22395
|
+
fallbackSite,
|
|
22396
|
+
});
|
|
22397
|
+
return fallbackLoginState;
|
|
22398
|
+
}
|
|
22399
|
+
}
|
|
22140
22400
|
if (slotLoginState) {
|
|
22141
22401
|
return slotLoginState;
|
|
22142
22402
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudbase/cloudbase-mcp",
|
|
3
|
-
"version": "2.32.
|
|
3
|
+
"version": "2.32.3",
|
|
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.",
|