@cloudbase/cloudbase-mcp 2.25.2 → 2.25.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/cli.cjs +3 -3
- package/dist/index.cjs +1006 -67
- package/dist/index.js +1006 -67
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2218,15 +2218,20 @@ async function prepareAuthEnvironment(params) {
|
|
|
2218
2218
|
if (setupContext.createFreeEnvAttempted) {
|
|
2219
2219
|
envSetupActions.push("create_free_env");
|
|
2220
2220
|
}
|
|
2221
|
+
// Surface the user-facing notice emitted at checkAndCreateFreeEnv entry.
|
|
2222
|
+
// Informational only — no confirm required. Prepend to the final message so
|
|
2223
|
+
// the user knows an automatic free-env creation was attempted.
|
|
2224
|
+
const userNotice = createResult.userNotice?.trim() || "";
|
|
2221
2225
|
if (createResult.success && createResult.envId) {
|
|
2222
2226
|
await cloudbase_manager_js_1.envManager.setEnvId(createResult.envId);
|
|
2227
|
+
const successMessage = `当前已登录,已自动创建并绑定环境: ${createResult.envId}`;
|
|
2223
2228
|
return {
|
|
2224
2229
|
currentEnvId: createResult.envId,
|
|
2225
2230
|
envStatus: "READY",
|
|
2226
2231
|
envCandidates: [],
|
|
2227
2232
|
envSetupStatus: "AUTO_CREATED",
|
|
2228
2233
|
envSetupActions: dedupeActions(envSetupActions),
|
|
2229
|
-
message:
|
|
2234
|
+
message: userNotice ? `${userNotice}\n${successMessage}` : successMessage,
|
|
2230
2235
|
nextStep: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
2231
2236
|
suggestedArgs: { action: "status" },
|
|
2232
2237
|
}),
|
|
@@ -2245,6 +2250,9 @@ async function prepareAuthEnvironment(params) {
|
|
|
2245
2250
|
message: "环境创建失败,请稍后重试或手动创建环境。",
|
|
2246
2251
|
helpUrl: "https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp",
|
|
2247
2252
|
});
|
|
2253
|
+
const failureMessage = userNotice
|
|
2254
|
+
? `${userNotice}\n${createFailure.message}`
|
|
2255
|
+
: createFailure.message;
|
|
2248
2256
|
return {
|
|
2249
2257
|
currentEnvId: null,
|
|
2250
2258
|
envStatus: "NONE",
|
|
@@ -2252,7 +2260,7 @@ async function prepareAuthEnvironment(params) {
|
|
|
2252
2260
|
envSetupStatus: "ACTION_REQUIRED",
|
|
2253
2261
|
envSetupActions: dedupeActions(envSetupActions),
|
|
2254
2262
|
envSetupFailure: createFailure,
|
|
2255
|
-
message:
|
|
2263
|
+
message: failureMessage,
|
|
2256
2264
|
nextStep: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
2257
2265
|
suggestedArgs: { action: "status" },
|
|
2258
2266
|
}),
|
|
@@ -2574,6 +2582,297 @@ function buildEnvQueryErrorMessage(error, action) {
|
|
|
2574
2582
|
function normalizeOptionalToolBoolean(value) {
|
|
2575
2583
|
return typeof value === "boolean" ? value : undefined;
|
|
2576
2584
|
}
|
|
2585
|
+
/**
|
|
2586
|
+
* 解析用于询价的 region。与 cloudbase-manager.ts 一致:
|
|
2587
|
+
* cloudBaseOptions.region → TCB_REGION → 'ap-shanghai'。
|
|
2588
|
+
*/
|
|
2589
|
+
function resolvePricingRegion(cloudBaseOptions) {
|
|
2590
|
+
return ((typeof cloudBaseOptions?.region === "string" && cloudBaseOptions.region) ||
|
|
2591
|
+
process.env.TCB_REGION ||
|
|
2592
|
+
"ap-shanghai");
|
|
2593
|
+
}
|
|
2594
|
+
/**
|
|
2595
|
+
* 从 describeBaasPackageList 查询某个 packageId 的人类可读套餐名。
|
|
2596
|
+
* 失败时返回 undefined,调用方降级为只显示 packageId。
|
|
2597
|
+
*/
|
|
2598
|
+
async function fetchPackageTitle(manager, packageId) {
|
|
2599
|
+
try {
|
|
2600
|
+
const result = await manager.env.describeBaasPackageList({
|
|
2601
|
+
TargetAction: "new",
|
|
2602
|
+
Source: "qcloud",
|
|
2603
|
+
});
|
|
2604
|
+
const packageList = result?.PackageList || [];
|
|
2605
|
+
const matched = packageList.find((item) => item?.BillTags === packageId || item?.PackageName === packageId);
|
|
2606
|
+
if (matched) {
|
|
2607
|
+
return (matched.PackageTitle || matched.PackageName || matched.BillTags);
|
|
2608
|
+
}
|
|
2609
|
+
return undefined;
|
|
2610
|
+
}
|
|
2611
|
+
catch (error) {
|
|
2612
|
+
(0, logger_js_1.debug)("fetchPackageTitle failed", { packageId, error });
|
|
2613
|
+
return undefined;
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* 查询环境的当前计费/套餐信息(PackageName、PackageId、Region、到期时间、PayMode)。
|
|
2618
|
+
* 失败时返回 undefined。
|
|
2619
|
+
*/
|
|
2620
|
+
async function fetchEnvBillingSummary(manager, envId) {
|
|
2621
|
+
try {
|
|
2622
|
+
// describeBillingInfo 返回 EnvBillingInfoList;从中挑出 envId 对应项
|
|
2623
|
+
const billingResult = await manager.env.describeBillingInfo({ EnvId: envId });
|
|
2624
|
+
const billingList = billingResult?.EnvBillingInfoList ||
|
|
2625
|
+
billingResult?.Response?.EnvBillingInfoList ||
|
|
2626
|
+
billingResult?.Data?.EnvBillingInfoList ||
|
|
2627
|
+
[];
|
|
2628
|
+
const matched = Array.isArray(billingList)
|
|
2629
|
+
? billingList.find((item) => item?.EnvId === envId) ?? billingList[0]
|
|
2630
|
+
: undefined;
|
|
2631
|
+
if (!matched) {
|
|
2632
|
+
return undefined;
|
|
2633
|
+
}
|
|
2634
|
+
return {
|
|
2635
|
+
packageName: matched.PackageName,
|
|
2636
|
+
packageId: matched.PackageId,
|
|
2637
|
+
region: matched.Region,
|
|
2638
|
+
expireTime: matched.ExpireTime,
|
|
2639
|
+
payMode: matched.PayMode,
|
|
2640
|
+
isAutoRenew: matched.IsAutoRenew,
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
catch (error) {
|
|
2644
|
+
(0, logger_js_1.debug)("fetchEnvBillingSummary failed", { envId, error });
|
|
2645
|
+
return undefined;
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
/**
|
|
2649
|
+
* 询价新购价格。失败时返回 { error }。
|
|
2650
|
+
*/
|
|
2651
|
+
async function calculateCreatePrice(manager, params) {
|
|
2652
|
+
try {
|
|
2653
|
+
const priceResult = await manager.env.calculatePackageCreatePrice({
|
|
2654
|
+
packageId: params.packageId,
|
|
2655
|
+
region: params.region,
|
|
2656
|
+
period: params.period,
|
|
2657
|
+
});
|
|
2658
|
+
return { priceResult };
|
|
2659
|
+
}
|
|
2660
|
+
catch (error) {
|
|
2661
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2662
|
+
(0, logger_js_1.debug)("calculatePackageCreatePrice failed", { params, error });
|
|
2663
|
+
return { error: message };
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* 询价续费价格。失败时返回 { error }。
|
|
2668
|
+
*/
|
|
2669
|
+
async function calculateRenewPrice(manager, envId, period) {
|
|
2670
|
+
try {
|
|
2671
|
+
const priceResult = await manager.env.calculatePackageRenewPrice({
|
|
2672
|
+
envId,
|
|
2673
|
+
period,
|
|
2674
|
+
});
|
|
2675
|
+
return { priceResult };
|
|
2676
|
+
}
|
|
2677
|
+
catch (error) {
|
|
2678
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2679
|
+
(0, logger_js_1.debug)("calculatePackageRenewPrice failed", { envId, period, error });
|
|
2680
|
+
return { error: message };
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
/**
|
|
2684
|
+
* 询价变配价格(含退款)。失败时返回 { error }。
|
|
2685
|
+
*/
|
|
2686
|
+
async function calculateModifyPrice(manager, envId, newPackageId) {
|
|
2687
|
+
try {
|
|
2688
|
+
const priceResult = await manager.env.calculatePackageModifyPrice({
|
|
2689
|
+
envId,
|
|
2690
|
+
newPackageId,
|
|
2691
|
+
});
|
|
2692
|
+
return { priceResult };
|
|
2693
|
+
}
|
|
2694
|
+
catch (error) {
|
|
2695
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2696
|
+
(0, logger_js_1.debug)("calculatePackageModifyPrice failed", { envId, newPackageId, error });
|
|
2697
|
+
return { error: message };
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
/**
|
|
2701
|
+
* 把 PriceResult 格式化为人类可读的价格摘要。
|
|
2702
|
+
* 返回 { summary, detail }:summary 是一行短语,detail 是多行明细。
|
|
2703
|
+
*/
|
|
2704
|
+
function formatPriceSection(priceResult) {
|
|
2705
|
+
if (!priceResult || typeof priceResult !== "object") {
|
|
2706
|
+
return { summary: "询价返回为空", detail: "" };
|
|
2707
|
+
}
|
|
2708
|
+
const currency = priceResult.Currency === "USD" ? "$" : "¥";
|
|
2709
|
+
const realTotalCost = Number(priceResult.RealTotalCost);
|
|
2710
|
+
const totalCost = Number(priceResult.TotalCost);
|
|
2711
|
+
const unitPrice = Number(priceResult.Price);
|
|
2712
|
+
const timeSpan = priceResult.TimeSpan;
|
|
2713
|
+
const TimeUnit = priceResult.TimeUnit || "";
|
|
2714
|
+
const refund = priceResult.Refund ? Number(priceResult.Refund) : undefined;
|
|
2715
|
+
const lines = [];
|
|
2716
|
+
let summary = "";
|
|
2717
|
+
if (Number.isFinite(realTotalCost) && realTotalCost > 0) {
|
|
2718
|
+
summary = `预计实付 ${currency}${realTotalCost}`;
|
|
2719
|
+
lines.push(`- 实付总价: ${currency}${realTotalCost}`);
|
|
2720
|
+
}
|
|
2721
|
+
else if (Number.isFinite(totalCost) && totalCost > 0) {
|
|
2722
|
+
summary = `预计费用 ${currency}${totalCost}`;
|
|
2723
|
+
lines.push(`- 总价: ${currency}${totalCost}`);
|
|
2724
|
+
}
|
|
2725
|
+
if (Number.isFinite(totalCost) && totalCost > 0 && totalCost !== realTotalCost) {
|
|
2726
|
+
lines.push(`- 原价: ${currency}${totalCost}`);
|
|
2727
|
+
}
|
|
2728
|
+
if (Number.isFinite(unitPrice) && unitPrice > 0) {
|
|
2729
|
+
lines.push(`- 单价: ${currency}${unitPrice}`);
|
|
2730
|
+
}
|
|
2731
|
+
if (timeSpan !== undefined && TimeUnit) {
|
|
2732
|
+
lines.push(`- 时长: ${timeSpan} ${TimeUnit}`);
|
|
2733
|
+
}
|
|
2734
|
+
if (refund !== undefined && Number.isFinite(refund) && refund > 0) {
|
|
2735
|
+
lines.push(`- 退款: ${currency}${refund}(变配降级时退回差价)`);
|
|
2736
|
+
if (summary) {
|
|
2737
|
+
summary += `,退款 ${currency}${refund}`;
|
|
2738
|
+
}
|
|
2739
|
+
else {
|
|
2740
|
+
summary = `退款 ${currency}${refund}`;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
if (priceResult.Formula && typeof priceResult.Formula === "string") {
|
|
2744
|
+
lines.push(`- 计价公式: ${priceResult.Formula}`);
|
|
2745
|
+
}
|
|
2746
|
+
return {
|
|
2747
|
+
summary: summary || "询价返回缺少总价字段",
|
|
2748
|
+
detail: lines.join("\n"),
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
/**
|
|
2752
|
+
* 构建释放方式说明。manageEnv 当前未提供 destroy action,
|
|
2753
|
+
* 指向控制台让用户手动销毁。
|
|
2754
|
+
*/
|
|
2755
|
+
function buildReleaseMethodHint() {
|
|
2756
|
+
return {
|
|
2757
|
+
method: "手动销毁",
|
|
2758
|
+
consoleUrl: "https://console.cloud.tencent.com/tcb",
|
|
2759
|
+
note: '环境创建后可随时销毁以停止计费。当前 manageEnv 未提供 destroy action,请前往控制台手动销毁,或调用 manageEnv(action="listPackages") 查看其他套餐。',
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* manageEnv 涉及计费的官方文档链接。来源:腾讯云开发 CloudBase 公开文档。
|
|
2764
|
+
* 任何 confirm 消息展示这些链接前不需要再次校验 URL 真实性。
|
|
2765
|
+
*/
|
|
2766
|
+
const MANAGE_ENV_DOC_LINKS = {
|
|
2767
|
+
// 包年包月套餐说明(个人版 / 标准版 / 企业版 / 企业高级版)
|
|
2768
|
+
package: "https://cloud.tencent.com/document/product/876/39093",
|
|
2769
|
+
// 计费能力项说明
|
|
2770
|
+
billingItems: "https://cloud.tencent.com/document/product/876/120713",
|
|
2771
|
+
// 资源点价格文档
|
|
2772
|
+
resourcePointPrice: "https://cloud.tencent.com/document/product/876/127357",
|
|
2773
|
+
// 预付费计费与到期释放(费用中心通用)
|
|
2774
|
+
prepayExpiry: "https://cloud.tencent.com/document/product/555/9618",
|
|
2775
|
+
};
|
|
2776
|
+
/**
|
|
2777
|
+
* 把文档链接渲染为多行 markdown 列表文本。
|
|
2778
|
+
* filter 不传时全部展示。
|
|
2779
|
+
*/
|
|
2780
|
+
function renderDocLinksBlock(filter) {
|
|
2781
|
+
const labels = {
|
|
2782
|
+
package: "包年包月套餐说明",
|
|
2783
|
+
billingItems: "计费能力项说明",
|
|
2784
|
+
resourcePointPrice: "资源点价格文档",
|
|
2785
|
+
prepayExpiry: "预付费计费与到期释放(费用中心通用)",
|
|
2786
|
+
};
|
|
2787
|
+
const entries = (filter ?? Object.keys(MANAGE_ENV_DOC_LINKS))
|
|
2788
|
+
.map((k) => `- [${labels[k]}](${MANAGE_ENV_DOC_LINKS[k]})`)
|
|
2789
|
+
.join("\n");
|
|
2790
|
+
return `参考文档:\n${entries}`;
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* 资源清单详细描述(对应控制台购买页"资源清单"段)。
|
|
2794
|
+
*/
|
|
2795
|
+
function buildResourceListText() {
|
|
2796
|
+
return "资源清单:\n- 云开发环境 ×1(含 云数据库 / 云函数 / 云存储 / 静态托管 / 身份认证 等基础资源)";
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* 计费项披露(对应控制台购买页"计费项"段)。
|
|
2800
|
+
* 此处为静态披露,调用方在 confirm 消息中拼接即可。
|
|
2801
|
+
*/
|
|
2802
|
+
function buildBillingItemsText() {
|
|
2803
|
+
return "计费项:\n- 数据库容量/调用 · 云函数调用/资源/流量 · 存储读写/CDN · 网关/认证/API 调用 · QPS 超限按量 · 日志";
|
|
2804
|
+
}
|
|
2805
|
+
/**
|
|
2806
|
+
* 计费方式披露(对应控制台购买页"计费方式"段)。
|
|
2807
|
+
* 针对用户传入的 packageId 判断套餐类型:
|
|
2808
|
+
* - 含 free/activity/trial/试用 等关键字视为免费体验版
|
|
2809
|
+
* - 其余视为付费套餐
|
|
2810
|
+
*/
|
|
2811
|
+
function buildBillingModeText(packageId) {
|
|
2812
|
+
const id = (packageId || "").toLowerCase();
|
|
2813
|
+
const isFree = id.includes("free") ||
|
|
2814
|
+
id.includes("activity") ||
|
|
2815
|
+
id.includes("trial") ||
|
|
2816
|
+
id.includes("试用") ||
|
|
2817
|
+
id.includes("体验");
|
|
2818
|
+
if (isFree) {
|
|
2819
|
+
return ("计费方式:\n- 免费体验版(每月赠送约 3000 资源点 ≈ 3 元,0 元开通)\n" +
|
|
2820
|
+
"- 付费套餐:个人版 / 标准版 / 企业版 / 企业高级版");
|
|
2821
|
+
}
|
|
2822
|
+
return ("计费方式:\n- 付费套餐:个人版 / 标准版 / 企业版 / 企业高级版\n" +
|
|
2823
|
+
"- 免费体验版通过 auth 工具自动创建;本次 manageEnv(create) 不会创建免费版");
|
|
2824
|
+
}
|
|
2825
|
+
/**
|
|
2826
|
+
* 资源释放方式详细说明(对应控制台购买页"资源释放方式"段)。
|
|
2827
|
+
* - 免费版:1 个月有效期 + 免费续期 + 停服(保留数据) + 1~7天回收站 + 释放(数据不可恢复)
|
|
2828
|
+
* - 付费版:到期未续费 → 停服(保留数据) + 1~7天可回收站找回 + 释放(数据不可恢复)
|
|
2829
|
+
* 任何时候都可在控制台主动销毁、关闭按量、退订加购资源。
|
|
2830
|
+
*/
|
|
2831
|
+
function buildReleaseMethodDetailText(packageId) {
|
|
2832
|
+
const id = (packageId || "").toLowerCase();
|
|
2833
|
+
const isFree = id.includes("free") ||
|
|
2834
|
+
id.includes("activity") ||
|
|
2835
|
+
id.includes("trial") ||
|
|
2836
|
+
id.includes("试用") ||
|
|
2837
|
+
id.includes("体验");
|
|
2838
|
+
const lines = ["资源释放方式:\n- 可随时在控制台销毁环境、关闭按量、退订加购资源"];
|
|
2839
|
+
if (isFree) {
|
|
2840
|
+
lines.push("- 免费体验版:有效期 1 个月,到期可免费续期 1 个月;未续期则停服(保留数据)→1~7天回收站→释放(数据不可恢复)");
|
|
2841
|
+
}
|
|
2842
|
+
else {
|
|
2843
|
+
lines.push("- 付费套餐到期未续费:停服(保留数据)→1~7天可回收站找回→释放(数据不可恢复)");
|
|
2844
|
+
lines.push("- 主动销毁:随时生效;销毁前请确保已迁移或备份数据");
|
|
2845
|
+
}
|
|
2846
|
+
return lines.join("\n");
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* 预计费用披露段:把询价结果 + 周期说明 + 超额按量说明组合成人类可读文本。
|
|
2850
|
+
*/
|
|
2851
|
+
function buildPricingDisclosureText(packageId, priceSection, priceError, period) {
|
|
2852
|
+
const id = (packageId || "").toLowerCase();
|
|
2853
|
+
const isFree = id.includes("free") ||
|
|
2854
|
+
id.includes("activity") ||
|
|
2855
|
+
id.includes("trial") ||
|
|
2856
|
+
id.includes("试用") ||
|
|
2857
|
+
id.includes("体验");
|
|
2858
|
+
const lines = ["预计费用:"];
|
|
2859
|
+
if (isFree) {
|
|
2860
|
+
lines.push(`- 免费体验版:本次开通 0 元;超免费额度后需升级为付费套餐(免费版不支持开按量)`);
|
|
2861
|
+
lines.push("- 实际单价以资源点价格文档为准(3000 资源点 ≈ 3 元)");
|
|
2862
|
+
}
|
|
2863
|
+
else {
|
|
2864
|
+
lines.push(`- 付费套餐:${priceSection?.summary ?? (priceError ? `⚠️ 询价失败(${priceError}),请前往控制台确认` : "询价返回为空")}`);
|
|
2865
|
+
if (priceSection?.detail) {
|
|
2866
|
+
lines.push(priceSection.detail);
|
|
2867
|
+
}
|
|
2868
|
+
if (priceError && priceSection) {
|
|
2869
|
+
lines.push(`- ⚠️ 部分明细缺失:${priceError}`);
|
|
2870
|
+
}
|
|
2871
|
+
lines.push(`- 计费周期:约 ${period} 个月(包年包月),到期可续费或变配`);
|
|
2872
|
+
lines.push("- 超额可另开按量(次日结算),详细计费项以官方文档为准");
|
|
2873
|
+
}
|
|
2874
|
+
return lines.join("\n");
|
|
2875
|
+
}
|
|
2577
2876
|
function resolveToolAuthOptions(server, overrides) {
|
|
2578
2877
|
return (0, auth_js_1.resolveAuthOptions)({
|
|
2579
2878
|
...overrides,
|
|
@@ -3402,7 +3701,7 @@ function registerEnvTools(server) {
|
|
|
3402
3701
|
if (server.ide !== 'wxide') {
|
|
3403
3702
|
server.registerTool?.("envDomainManagement", {
|
|
3404
3703
|
title: "CloudBase 环境域名管理(安全域名 / CORS 白名单)",
|
|
3405
|
-
description: "管理 CloudBase 环境的安全域名(安全域名 / CORS 白名单),支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 直接访问 CloudBase 资源时,应先用 queryEnv(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。新增或删除后通常需要继续轮询 queryEnv(action=domains) 确认状态收敛;安全域名一般约 10 分钟生效。⚠️ 重要:此工具仅用于 CORS/请求来源验证,不涉及 SSL
|
|
3704
|
+
description: "管理 CloudBase 环境的安全域名(安全域名 / CORS 白名单),支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 直接访问 CloudBase 资源时,应先用 queryEnv(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。新增或删除后通常需要继续轮询 queryEnv(action=domains) 确认状态收敛;安全域名一般约 10 分钟生效。⚠️ 重要:此工具仅用于 CORS/请求来源验证,不涉及 SSL 证书。自定义域名公网 HTTPS:先 queryGateway(listCustomDomains);已有域名则 manageGateway(createRoute) 显式传 domain(无需证书);仅首次绑定新域名才用 bindCustomDomain(需 certificateId)。",
|
|
3406
3705
|
inputSchema: {
|
|
3407
3706
|
action: zod_1.z
|
|
3408
3707
|
.enum(["create", "delete"])
|
|
@@ -3526,10 +3825,92 @@ function registerEnvTools(server) {
|
|
|
3526
3825
|
// CreateEnv (Manager SDK / Cloud API) accepts Alias, PackageId, Resources, Period, etc.
|
|
3527
3826
|
// It does NOT accept Region — region is determined by account/package, not this call.
|
|
3528
3827
|
if (!confirmed) {
|
|
3828
|
+
// 查询套餐名和预计费用(失败降级,不阻塞 confirm 流程)
|
|
3829
|
+
const packageTitle = packageId
|
|
3830
|
+
? await fetchPackageTitle(cloudbase, packageId)
|
|
3831
|
+
: undefined;
|
|
3832
|
+
const pricingRegion = resolvePricingRegion(cloudBaseOptions);
|
|
3833
|
+
const priceProbe = packageId
|
|
3834
|
+
? await calculateCreatePrice(cloudbase, {
|
|
3835
|
+
packageId,
|
|
3836
|
+
region: pricingRegion,
|
|
3837
|
+
period: duration,
|
|
3838
|
+
})
|
|
3839
|
+
: { error: "缺少 packageId,无法询价" };
|
|
3840
|
+
const priceSection = priceProbe.priceResult
|
|
3841
|
+
? formatPriceSection(priceProbe.priceResult)
|
|
3842
|
+
: null;
|
|
3843
|
+
const releaseMethod = buildReleaseMethodHint();
|
|
3844
|
+
// 组合多段披露文案(对照控制台购买页确认对话框)
|
|
3845
|
+
const messageLines = [];
|
|
3846
|
+
messageLines.push("即将为你开通云开发环境");
|
|
3847
|
+
messageLines.push("本操作会创建腾讯云开发环境资源。");
|
|
3848
|
+
messageLines.push("为付费套餐后才会产生费用;免费体验版不会立即扣费(每月赠送约 3000 资源点 ≈ 3 元)。");
|
|
3849
|
+
messageLines.push("请核对以下配置信息后传入 confirm=\"yes\":");
|
|
3850
|
+
messageLines.push(`- 别名: ${alias ?? "(未提供)"}`);
|
|
3851
|
+
messageLines.push(`- 套餐: ${packageId ?? "(未提供)"}` +
|
|
3852
|
+
(packageTitle ? `(${packageTitle})` : ""));
|
|
3853
|
+
messageLines.push(`- 资源类型: ${resolvedResources.join(", ")}`);
|
|
3854
|
+
messageLines.push(`- 时长: ${duration} 个月`);
|
|
3855
|
+
messageLines.push("- 说明: CreateEnv 不接受 Region 入参,环境地域由账号与套餐侧决定。");
|
|
3856
|
+
messageLines.push("");
|
|
3857
|
+
// 资源清单 / 计费项 / 计费方式
|
|
3858
|
+
messageLines.push(buildResourceListText());
|
|
3859
|
+
messageLines.push(buildBillingItemsText());
|
|
3860
|
+
messageLines.push(buildBillingModeText(packageId));
|
|
3861
|
+
messageLines.push("");
|
|
3862
|
+
// 预计费用
|
|
3863
|
+
messageLines.push(buildPricingDisclosureText(packageId, priceSection, priceProbe.error, duration));
|
|
3864
|
+
messageLines.push("");
|
|
3865
|
+
// 资源释放方式
|
|
3866
|
+
messageLines.push(buildReleaseMethodDetailText(packageId));
|
|
3867
|
+
messageLines.push("");
|
|
3868
|
+
// 文档链接
|
|
3869
|
+
messageLines.push(renderDocLinksBlock([
|
|
3870
|
+
"package",
|
|
3871
|
+
"billingItems",
|
|
3872
|
+
"resourcePointPrice",
|
|
3873
|
+
"prepayExpiry",
|
|
3874
|
+
]));
|
|
3875
|
+
messageLines.push("");
|
|
3876
|
+
// 已知晓声明
|
|
3877
|
+
messageLines.push("☐ 我已知晓将创建付费资源及计费规则,确认按上述配置开通。");
|
|
3878
|
+
messageLines.push(`(如需取消或修改,请勿传 confirm="yes",改传其他参数重试)`);
|
|
3529
3879
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
3530
3880
|
ok: false,
|
|
3531
3881
|
code: "CONFIRM_REQUIRED",
|
|
3532
|
-
message:
|
|
3882
|
+
message: messageLines.join("\n"),
|
|
3883
|
+
package_info: packageTitle
|
|
3884
|
+
? { packageId, packageTitle }
|
|
3885
|
+
: packageId
|
|
3886
|
+
? { packageId, packageTitle: null }
|
|
3887
|
+
: undefined,
|
|
3888
|
+
pricing: priceSection
|
|
3889
|
+
? {
|
|
3890
|
+
summary: priceSection.summary,
|
|
3891
|
+
currency: priceProbe.priceResult?.Currency,
|
|
3892
|
+
realTotalCost: priceProbe.priceResult?.RealTotalCost,
|
|
3893
|
+
totalCost: priceProbe.priceResult?.TotalCost,
|
|
3894
|
+
timeSpan: priceProbe.priceResult?.TimeSpan,
|
|
3895
|
+
timeUnit: priceProbe.priceResult?.TimeUnit,
|
|
3896
|
+
inquiryRegion: pricingRegion,
|
|
3897
|
+
}
|
|
3898
|
+
: priceProbe.error
|
|
3899
|
+
? { inquiryFailed: true, error: priceProbe.error }
|
|
3900
|
+
: undefined,
|
|
3901
|
+
release_method: {
|
|
3902
|
+
...releaseMethod,
|
|
3903
|
+
detail: buildReleaseMethodDetailText(packageId),
|
|
3904
|
+
docLinks: [
|
|
3905
|
+
MANAGE_ENV_DOC_LINKS.package,
|
|
3906
|
+
MANAGE_ENV_DOC_LINKS.prepayExpiry,
|
|
3907
|
+
],
|
|
3908
|
+
},
|
|
3909
|
+
doc_links: MANAGE_ENV_DOC_LINKS,
|
|
3910
|
+
confirmation_acknowledgement: {
|
|
3911
|
+
text: "我已知晓将创建付费资源及计费规则",
|
|
3912
|
+
required: true,
|
|
3913
|
+
},
|
|
3533
3914
|
next_step: {
|
|
3534
3915
|
tool: "manageEnv",
|
|
3535
3916
|
action: "create",
|
|
@@ -3565,10 +3946,91 @@ function registerEnvTools(server) {
|
|
|
3565
3946
|
if (!envId || !packageId) {
|
|
3566
3947
|
throw new Error("变更套餐时 envId 和 packageId 为必填参数");
|
|
3567
3948
|
}
|
|
3949
|
+
// 查询当前套餐名 + 新套餐名 + 询价(失败降级)
|
|
3950
|
+
const [currentBilling, newPackageTitle] = await Promise.all([
|
|
3951
|
+
fetchEnvBillingSummary(cloudbase, envId),
|
|
3952
|
+
fetchPackageTitle(cloudbase, packageId),
|
|
3953
|
+
]);
|
|
3954
|
+
const priceProbe = await calculateModifyPrice(cloudbase, envId, packageId);
|
|
3955
|
+
const priceSection = priceProbe.priceResult
|
|
3956
|
+
? formatPriceSection(priceProbe.priceResult)
|
|
3957
|
+
: null;
|
|
3958
|
+
const releaseMethod = buildReleaseMethodHint();
|
|
3959
|
+
const messageLines = [];
|
|
3960
|
+
messageLines.push(`变更环境 ${envId} 的套餐需要您确认。变配后不会立即重新计费,但会触发差价结算或退款。`);
|
|
3961
|
+
messageLines.push("请核对后传入 confirm=\"yes\":");
|
|
3962
|
+
messageLines.push(`- 当前套餐: ${currentBilling?.packageName ?? currentBilling?.packageId ?? "(未知)"}` +
|
|
3963
|
+
(currentBilling?.packageId && currentBilling.packageName
|
|
3964
|
+
? `(${currentBilling.packageId})`
|
|
3965
|
+
: ""));
|
|
3966
|
+
if (currentBilling?.expireTime) {
|
|
3967
|
+
messageLines.push(`- 当前到期时间: ${currentBilling.expireTime}`);
|
|
3968
|
+
}
|
|
3969
|
+
messageLines.push(`- 新套餐: ${packageId}` +
|
|
3970
|
+
(newPackageTitle ? `(${newPackageTitle})` : ""));
|
|
3971
|
+
messageLines.push("");
|
|
3972
|
+
if (priceSection) {
|
|
3973
|
+
messageLines.push(`预计费用变化:${priceSection.summary}(差价/退款以账单为准)`);
|
|
3974
|
+
if (priceSection.detail) {
|
|
3975
|
+
messageLines.push(priceSection.detail);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
else if (priceProbe.error) {
|
|
3979
|
+
messageLines.push(`预计费用变化:⚠️ 询价失败(${priceProbe.error}),请前往控制台确认价格`);
|
|
3980
|
+
}
|
|
3981
|
+
else {
|
|
3982
|
+
messageLines.push("预计费用变化:询价返回为空,请前往控制台确认价格");
|
|
3983
|
+
}
|
|
3984
|
+
messageLines.push("");
|
|
3985
|
+
// 资源释放方式(变配不影响存续,但与到期/销毁规则相同)
|
|
3986
|
+
messageLines.push(buildReleaseMethodDetailText(packageId));
|
|
3987
|
+
messageLines.push("");
|
|
3988
|
+
// 文档链接(变配与套餐/计费/到期都相关)
|
|
3989
|
+
messageLines.push(renderDocLinksBlock(["package", "prepayExpiry"]));
|
|
3990
|
+
messageLines.push("");
|
|
3991
|
+
messageLines.push("☐ 我已知晓变配将触发差价结算或退款,确认按上述配置变更。");
|
|
3992
|
+
messageLines.push(`(如需取消或修改,请勿传 confirm="yes")`);
|
|
3568
3993
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
3569
3994
|
ok: false,
|
|
3570
3995
|
code: "CONFIRM_REQUIRED",
|
|
3571
|
-
message:
|
|
3996
|
+
message: messageLines.join("\n"),
|
|
3997
|
+
env_id: envId,
|
|
3998
|
+
current_package: currentBilling
|
|
3999
|
+
? {
|
|
4000
|
+
packageId: currentBilling.packageId,
|
|
4001
|
+
packageName: currentBilling.packageName,
|
|
4002
|
+
expireTime: currentBilling.expireTime,
|
|
4003
|
+
payMode: currentBilling.payMode,
|
|
4004
|
+
}
|
|
4005
|
+
: undefined,
|
|
4006
|
+
new_package: {
|
|
4007
|
+
packageId,
|
|
4008
|
+
packageTitle: newPackageTitle ?? null,
|
|
4009
|
+
},
|
|
4010
|
+
pricing: priceSection
|
|
4011
|
+
? {
|
|
4012
|
+
summary: priceSection.summary,
|
|
4013
|
+
currency: priceProbe.priceResult?.Currency,
|
|
4014
|
+
realTotalCost: priceProbe.priceResult?.RealTotalCost,
|
|
4015
|
+
totalCost: priceProbe.priceResult?.TotalCost,
|
|
4016
|
+
refund: priceProbe.priceResult?.Refund,
|
|
4017
|
+
}
|
|
4018
|
+
: priceProbe.error
|
|
4019
|
+
? { inquiryFailed: true, error: priceProbe.error }
|
|
4020
|
+
: undefined,
|
|
4021
|
+
release_method: {
|
|
4022
|
+
...releaseMethod,
|
|
4023
|
+
detail: buildReleaseMethodDetailText(packageId),
|
|
4024
|
+
docLinks: [MANAGE_ENV_DOC_LINKS.prepayExpiry],
|
|
4025
|
+
},
|
|
4026
|
+
doc_links: {
|
|
4027
|
+
package: MANAGE_ENV_DOC_LINKS.package,
|
|
4028
|
+
prepayExpiry: MANAGE_ENV_DOC_LINKS.prepayExpiry,
|
|
4029
|
+
},
|
|
4030
|
+
confirmation_acknowledgement: {
|
|
4031
|
+
text: "我已知晓变配将触发差价结算或退款",
|
|
4032
|
+
required: true,
|
|
4033
|
+
},
|
|
3572
4034
|
next_step: {
|
|
3573
4035
|
tool: "manageEnv",
|
|
3574
4036
|
action: "modifyPlan",
|
|
@@ -3598,10 +4060,87 @@ function registerEnvTools(server) {
|
|
|
3598
4060
|
if (!envId) {
|
|
3599
4061
|
throw new Error("续费环境时 envId 为必填参数");
|
|
3600
4062
|
}
|
|
4063
|
+
// 查询当前套餐名 + 到期时间 + 询价(失败降级)
|
|
4064
|
+
const [currentBilling, priceProbe] = await Promise.all([
|
|
4065
|
+
fetchEnvBillingSummary(cloudbase, envId),
|
|
4066
|
+
calculateRenewPrice(cloudbase, envId, duration),
|
|
4067
|
+
]);
|
|
4068
|
+
const priceSection = priceProbe.priceResult
|
|
4069
|
+
? formatPriceSection(priceProbe.priceResult)
|
|
4070
|
+
: null;
|
|
4071
|
+
const releaseMethod = buildReleaseMethodHint();
|
|
4072
|
+
const currentPackageId = currentBilling?.packageId;
|
|
4073
|
+
const messageLines = [];
|
|
4074
|
+
messageLines.push(`续费环境 ${envId} 需要您确认。续费按当前套餐类型计算,延长到期时间。`);
|
|
4075
|
+
messageLines.push("请核对后传入 confirm=\"yes\":");
|
|
4076
|
+
messageLines.push(`- 当前套餐: ${currentBilling?.packageName ?? currentBilling?.packageId ?? "(未知)"}` +
|
|
4077
|
+
(currentBilling?.packageId && currentBilling.packageName
|
|
4078
|
+
? `(${currentBilling.packageId})`
|
|
4079
|
+
: ""));
|
|
4080
|
+
if (currentBilling?.expireTime) {
|
|
4081
|
+
messageLines.push(`- 当前到期时间: ${currentBilling.expireTime}`);
|
|
4082
|
+
}
|
|
4083
|
+
messageLines.push(`- 续费时长: ${duration} 个月`);
|
|
4084
|
+
messageLines.push("");
|
|
4085
|
+
if (priceSection) {
|
|
4086
|
+
messageLines.push(`预计费用:${priceSection.summary}`);
|
|
4087
|
+
if (priceSection.detail) {
|
|
4088
|
+
messageLines.push(priceSection.detail);
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
else if (priceProbe.error) {
|
|
4092
|
+
messageLines.push(`预计费用:⚠️ 询价失败(${priceProbe.error}),请前往控制台确认价格`);
|
|
4093
|
+
}
|
|
4094
|
+
else {
|
|
4095
|
+
messageLines.push("预计费用:询价返回为空,请前往控制台确认价格");
|
|
4096
|
+
}
|
|
4097
|
+
messageLines.push("");
|
|
4098
|
+
// 资源释放方式
|
|
4099
|
+
messageLines.push(buildReleaseMethodDetailText(currentPackageId));
|
|
4100
|
+
messageLines.push("");
|
|
4101
|
+
// 文档链接(续费主要看预付费与到期释放)
|
|
4102
|
+
messageLines.push(renderDocLinksBlock(["prepayExpiry"]));
|
|
4103
|
+
messageLines.push("");
|
|
4104
|
+
messageLines.push("☐ 我已知晓续费将延长当前套餐的到期时间,确认按上述配置续费。");
|
|
4105
|
+
messageLines.push(`(如需取消或修改,请勿传 confirm="yes")`);
|
|
3601
4106
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
3602
4107
|
ok: false,
|
|
3603
4108
|
code: "CONFIRM_REQUIRED",
|
|
3604
|
-
message:
|
|
4109
|
+
message: messageLines.join("\n"),
|
|
4110
|
+
env_id: envId,
|
|
4111
|
+
current_package: currentBilling
|
|
4112
|
+
? {
|
|
4113
|
+
packageId: currentBilling.packageId,
|
|
4114
|
+
packageName: currentBilling.packageName,
|
|
4115
|
+
expireTime: currentBilling.expireTime,
|
|
4116
|
+
payMode: currentBilling.payMode,
|
|
4117
|
+
isAutoRenew: currentBilling.isAutoRenew,
|
|
4118
|
+
}
|
|
4119
|
+
: undefined,
|
|
4120
|
+
pricing: priceSection
|
|
4121
|
+
? {
|
|
4122
|
+
summary: priceSection.summary,
|
|
4123
|
+
currency: priceProbe.priceResult?.Currency,
|
|
4124
|
+
realTotalCost: priceProbe.priceResult?.RealTotalCost,
|
|
4125
|
+
totalCost: priceProbe.priceResult?.TotalCost,
|
|
4126
|
+
timeSpan: priceProbe.priceResult?.TimeSpan,
|
|
4127
|
+
timeUnit: priceProbe.priceResult?.TimeUnit,
|
|
4128
|
+
}
|
|
4129
|
+
: priceProbe.error
|
|
4130
|
+
? { inquiryFailed: true, error: priceProbe.error }
|
|
4131
|
+
: undefined,
|
|
4132
|
+
release_method: {
|
|
4133
|
+
...releaseMethod,
|
|
4134
|
+
detail: buildReleaseMethodDetailText(currentPackageId),
|
|
4135
|
+
docLinks: [MANAGE_ENV_DOC_LINKS.prepayExpiry],
|
|
4136
|
+
},
|
|
4137
|
+
doc_links: {
|
|
4138
|
+
prepayExpiry: MANAGE_ENV_DOC_LINKS.prepayExpiry,
|
|
4139
|
+
},
|
|
4140
|
+
confirmation_acknowledgement: {
|
|
4141
|
+
text: "我已知晓续费将延长当前套餐的到期时间",
|
|
4142
|
+
required: true,
|
|
4143
|
+
},
|
|
3605
4144
|
next_step: {
|
|
3606
4145
|
tool: "manageEnv",
|
|
3607
4146
|
action: "renew",
|
|
@@ -28973,6 +29512,22 @@ function buildLoginConfigNextStep(loginMethods) {
|
|
|
28973
29512
|
},
|
|
28974
29513
|
};
|
|
28975
29514
|
}
|
|
29515
|
+
/**
|
|
29516
|
+
* 短信验证码默认通道提示:开启 phone 登录后即可用云开发默认短信通道收发验证码,
|
|
29517
|
+
* 不需要配置短信签名/模板/自定义 Provider。仅当需要自定义模板、签名或更换短信服务商时,
|
|
29518
|
+
* 才需要配置 SmsVerificationConfig / 自定义短信通道。
|
|
29519
|
+
*/
|
|
29520
|
+
function buildSmsHint(loginMethods) {
|
|
29521
|
+
if (!loginMethods.phone) {
|
|
29522
|
+
return undefined;
|
|
29523
|
+
}
|
|
29524
|
+
return {
|
|
29525
|
+
defaultChannelReady: true,
|
|
29526
|
+
message: "短信验证码登录已开启,可直接使用云开发默认短信通道收发验证码,无需配置短信签名/模板/自定义 Provider。" +
|
|
29527
|
+
"前端调用 auth.getVerification({ phone_number }) 发送验证码、auth.signInWithSms({ verificationInfo, verificationCode, phoneNum }) 登录。" +
|
|
29528
|
+
"仅当需要自定义短信模板/签名或更换短信服务商时,才需要配置 SmsVerificationConfig 或接入自定义短信通道。",
|
|
29529
|
+
};
|
|
29530
|
+
}
|
|
28976
29531
|
function extractProviders(value) {
|
|
28977
29532
|
const payload = normalizePlainObject(value, "providersResult");
|
|
28978
29533
|
const providers = payload?.Providers ?? payload?.ProviderList ?? payload?.Data ?? [];
|
|
@@ -29089,6 +29644,31 @@ function registerAppAuthTools(server) {
|
|
|
29089
29644
|
return (0, json_content_js_1.jsonContent)(buildErrorEnvelope(error));
|
|
29090
29645
|
}
|
|
29091
29646
|
};
|
|
29647
|
+
// Best-effort inventory read used to verify whether CreateApiKey really produced a new key.
|
|
29648
|
+
// A failure here must not block key creation, so it returns null to mark "unknown"
|
|
29649
|
+
// rather than an empty list, which would be indistinguishable from "no keys exist".
|
|
29650
|
+
const listApiKeyRecords = async (cloudbase, keyType) => {
|
|
29651
|
+
try {
|
|
29652
|
+
const result = await cloudbase.env.describeApiKeyList({
|
|
29653
|
+
KeyType: keyType,
|
|
29654
|
+
PageNumber: 1,
|
|
29655
|
+
PageSize: 50,
|
|
29656
|
+
});
|
|
29657
|
+
return extractApiKeyList(result);
|
|
29658
|
+
}
|
|
29659
|
+
catch {
|
|
29660
|
+
return null;
|
|
29661
|
+
}
|
|
29662
|
+
};
|
|
29663
|
+
const listApiKeyIds = async (cloudbase, keyType) => {
|
|
29664
|
+
const records = await listApiKeyRecords(cloudbase, keyType);
|
|
29665
|
+
if (records === null) {
|
|
29666
|
+
return null;
|
|
29667
|
+
}
|
|
29668
|
+
return new Set(records
|
|
29669
|
+
.map((item) => (typeof item.KeyId === "string" ? item.KeyId : null))
|
|
29670
|
+
.filter((item) => item !== null));
|
|
29671
|
+
};
|
|
29092
29672
|
const describePublishableKey = async (cloudbase, envId) => {
|
|
29093
29673
|
const result = await cloudbase.env.describeApiKeyList({ KeyType: "publish_key", PageNumber: 1, PageSize: 10 });
|
|
29094
29674
|
return {
|
|
@@ -29128,12 +29708,14 @@ function registerAppAuthTools(server) {
|
|
|
29128
29708
|
const loginMethods = buildLoginMethods(result);
|
|
29129
29709
|
const nextStep = buildLoginConfigNextStep(loginMethods);
|
|
29130
29710
|
const webSdkHint = buildWebSdkHint(loginMethods);
|
|
29711
|
+
const smsHint = buildSmsHint(loginMethods);
|
|
29131
29712
|
return buildSupabaseLikeAuthResponse({
|
|
29132
29713
|
success: true,
|
|
29133
29714
|
envId,
|
|
29134
29715
|
loginMethods,
|
|
29135
29716
|
...(nextStep ? { next_step: nextStep } : {}),
|
|
29136
29717
|
...(webSdkHint ? { webSdkHint } : {}),
|
|
29718
|
+
...(smsHint ? { smsHint } : {}),
|
|
29137
29719
|
});
|
|
29138
29720
|
}
|
|
29139
29721
|
case "listProviders": {
|
|
@@ -29209,7 +29791,7 @@ function registerAppAuthTools(server) {
|
|
|
29209
29791
|
}));
|
|
29210
29792
|
server.registerTool?.("manageAppAuth", {
|
|
29211
29793
|
title: "管理 CloudBase 应用认证配置",
|
|
29212
|
-
description: "CloudBase 应用侧认证配置写入口。用于修改登录方式、provider、client 配置,确保 publishable key,以及创建或删除 API key、自定义登录密钥。⚠️ 本工具为管理端配置工具,不执行用户登录。当任务要求编写客户端登录代码时(例如「用 JS SDK 登录」),应先通过本工具完成配置(如启用 usernamePassword、获取 publishable key),再在项目代码中编写 @cloudbase/js-sdk 客户端登录代码(如 auth.signInWithPassword()),而非使用本工具完成登录。若前端要接受普通用户名样式标识符,应先执行 action=patchLoginStrategy 并传入 patch={ usernamePassword: true }
|
|
29794
|
+
description: "CloudBase 应用侧认证配置写入口。用于修改登录方式、provider、client 配置,确保 publishable key,以及创建或删除 API key、自定义登录密钥。⚠️ 本工具为管理端配置工具,不执行用户登录。当任务要求编写客户端登录代码时(例如「用 JS SDK 登录」),应先通过本工具完成配置(如启用 usernamePassword、获取 publishable key),再在项目代码中编写 @cloudbase/js-sdk 客户端登录代码(如 auth.signInWithPassword()),而非使用本工具完成登录。若前端要接受普通用户名样式标识符,应先执行 action=patchLoginStrategy 并传入 patch={ usernamePassword: true },再实现对应前端登录逻辑。⚠️ 短信验证码登录(patch={ phone: true })使用云开发默认短信通道,开启后即可收发验证码,不需要配置短信签名/模板/自定义 Provider;仅当需要自定义模板/签名或更换短信服务商时才需配置 SmsVerificationConfig 或自定义短信通道。⚠️ action=createApiKey 返回体中的 created 字段表示是否真正新建:created=false 说明复用了环境中已存在的 key,此时 keyName/expireIn 入参不会生效,返回的 keyName/expireAt 均为服务端真实值,并会附带 warnings,切勿把它当作临时凭证分发。",
|
|
29213
29795
|
inputSchema: {
|
|
29214
29796
|
action: zod_1.z.enum(MANAGE_APP_AUTH_ACTIONS),
|
|
29215
29797
|
patch: zod_1.z
|
|
@@ -29234,8 +29816,16 @@ function registerAppAuthTools(server) {
|
|
|
29234
29816
|
.enum(APP_AUTH_KEY_TYPES)
|
|
29235
29817
|
.optional()
|
|
29236
29818
|
.describe("createApiKey 时的 API key 类型,默认 publish_key"),
|
|
29237
|
-
keyName: zod_1.z
|
|
29238
|
-
|
|
29819
|
+
keyName: zod_1.z
|
|
29820
|
+
.string()
|
|
29821
|
+
.optional()
|
|
29822
|
+
.describe("createApiKey 时的 API key 名称;服务端可能忽略该值(如 publish_key 每环境唯一时),返回体中的 keyName 一律为服务端实际存储的名称"),
|
|
29823
|
+
expireIn: zod_1.z
|
|
29824
|
+
.number()
|
|
29825
|
+
.int()
|
|
29826
|
+
.min(0)
|
|
29827
|
+
.optional()
|
|
29828
|
+
.describe("createApiKey 时的有效期,单位秒;0 表示不过期。复用已有 key 时该值不生效,此时返回体会带 created=false 与 warnings"),
|
|
29239
29829
|
keyId: zod_1.z.string().optional().describe("deleteApiKey 时的 API key 唯一标识"),
|
|
29240
29830
|
},
|
|
29241
29831
|
annotations: {
|
|
@@ -29267,12 +29857,14 @@ function registerAppAuthTools(server) {
|
|
|
29267
29857
|
const loginMethods = buildLoginMethods(confirmed);
|
|
29268
29858
|
const nextStep = buildLoginConfigNextStep(loginMethods);
|
|
29269
29859
|
const webSdkHint = buildWebSdkHint(loginMethods);
|
|
29860
|
+
const smsHint = buildSmsHint(loginMethods);
|
|
29270
29861
|
return buildSupabaseLikeAuthResponse({
|
|
29271
29862
|
success: true,
|
|
29272
29863
|
envId,
|
|
29273
29864
|
loginMethods,
|
|
29274
29865
|
...(nextStep ? { next_step: nextStep } : {}),
|
|
29275
29866
|
...(webSdkHint ? { webSdkHint } : {}),
|
|
29867
|
+
...(smsHint ? { smsHint } : {}),
|
|
29276
29868
|
});
|
|
29277
29869
|
}
|
|
29278
29870
|
case "addProvider": {
|
|
@@ -29360,6 +29952,7 @@ function registerAppAuthTools(server) {
|
|
|
29360
29952
|
}
|
|
29361
29953
|
case "createApiKey": {
|
|
29362
29954
|
const effectiveKeyType = keyType ?? "publish_key";
|
|
29955
|
+
const before = await listApiKeyIds(cloudbase, effectiveKeyType);
|
|
29363
29956
|
const result = await cloudbase.env.createApiKey({
|
|
29364
29957
|
KeyType: effectiveKeyType,
|
|
29365
29958
|
...(keyName ? { KeyName: keyName } : {}),
|
|
@@ -29367,19 +29960,44 @@ function registerAppAuthTools(server) {
|
|
|
29367
29960
|
});
|
|
29368
29961
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
29369
29962
|
const createApiKeyResult = normalizePlainObject(result, "createApiKeyResult") ?? {};
|
|
29963
|
+
const resultKeyId = typeof createApiKeyResult.KeyId === "string" ? createApiKeyResult.KeyId : null;
|
|
29964
|
+
const created = before === null || resultKeyId === null ? null : !before.has(resultKeyId);
|
|
29965
|
+
// The backend is the only source of truth for the stored name / expiry:
|
|
29966
|
+
// re-read the key record instead of echoing back the request parameters.
|
|
29967
|
+
const serverRecord = resultKeyId
|
|
29968
|
+
? (await listApiKeyRecords(cloudbase, effectiveKeyType))?.find((item) => item.KeyId === resultKeyId) ?? null
|
|
29969
|
+
: null;
|
|
29970
|
+
const keyNameSource = serverRecord ?? createApiKeyResult;
|
|
29971
|
+
const resolvedKeyName = getNonEmptyString(keyNameSource.Name) ??
|
|
29972
|
+
getNonEmptyString(keyNameSource.KeyName) ??
|
|
29973
|
+
null;
|
|
29974
|
+
const resolvedExpireAt = getNonEmptyString(serverRecord?.ExpireAt) ??
|
|
29975
|
+
getNonEmptyString(createApiKeyResult.ExpireAt) ??
|
|
29976
|
+
null;
|
|
29977
|
+
const resolvedCreatedAt = getNonEmptyString(serverRecord?.CreateAt) ??
|
|
29978
|
+
getNonEmptyString(createApiKeyResult.CreateAt) ??
|
|
29979
|
+
null;
|
|
29980
|
+
const warnings = [];
|
|
29981
|
+
if (created === false) {
|
|
29982
|
+
warnings.push(`keyType=${effectiveKeyType} 未创建新的 key,返回的是环境中已存在的 key(keyId=${resultKeyId})。若该类型每个环境唯一,请改用 action=ensurePublishableKey 语义理解此结果。`);
|
|
29983
|
+
}
|
|
29984
|
+
if (keyName && resolvedKeyName !== null && resolvedKeyName !== keyName) {
|
|
29985
|
+
warnings.push(`请求的 keyName="${keyName}" 未生效,服务端实际存储的名称为 "${resolvedKeyName}"。`);
|
|
29986
|
+
}
|
|
29987
|
+
if (typeof expireIn === "number" && created === false) {
|
|
29988
|
+
warnings.push(`请求的 expireIn=${expireIn} 未生效,返回的 key 使用其原有过期时间 ${resolvedExpireAt ?? "unknown"}。请勿将其视为临时凭证。`);
|
|
29989
|
+
}
|
|
29370
29990
|
return {
|
|
29371
29991
|
success: true,
|
|
29372
29992
|
envId,
|
|
29373
29993
|
keyType: effectiveKeyType,
|
|
29374
|
-
keyId:
|
|
29375
|
-
keyName:
|
|
29376
|
-
|
|
29377
|
-
|
|
29378
|
-
|
|
29379
|
-
|
|
29380
|
-
|
|
29381
|
-
expireAt: typeof result.ExpireAt === "string" ? result.ExpireAt : null,
|
|
29382
|
-
createdAt: typeof result.CreateAt === "string" ? result.CreateAt : null,
|
|
29994
|
+
keyId: resultKeyId,
|
|
29995
|
+
keyName: resolvedKeyName,
|
|
29996
|
+
apiKey: typeof createApiKeyResult.ApiKey === "string" ? createApiKeyResult.ApiKey : null,
|
|
29997
|
+
expireAt: resolvedExpireAt,
|
|
29998
|
+
createdAt: resolvedCreatedAt,
|
|
29999
|
+
...(typeof created === "boolean" ? { created } : {}),
|
|
30000
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
29383
30001
|
};
|
|
29384
30002
|
}
|
|
29385
30003
|
case "deleteApiKey": {
|
|
@@ -34238,6 +34856,17 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
34238
34856
|
hasInitTcbError: !!newContext.initTcbError,
|
|
34239
34857
|
initTcbError: newContext.initTcbError
|
|
34240
34858
|
});
|
|
34859
|
+
// User-facing notice emitted at the entry: inform the user that an automatic
|
|
34860
|
+
// free-env creation is being attempted. Informational only — no confirm.
|
|
34861
|
+
// Built once up-front so every return path can surface the same notice.
|
|
34862
|
+
const userNotice = [
|
|
34863
|
+
"系统检测到您当前没有可用的 CloudBase 环境,将自动尝试为您创建一个免费环境。",
|
|
34864
|
+
"创建依据:通过 NewUser / ReturningUser / BaasFree 等优惠活动资格检查;",
|
|
34865
|
+
"环境别名:ai-native(默认);",
|
|
34866
|
+
"费用说明:该环境为免费活动赠送,创建过程不会产生费用;如不符合免费条件,将引导您前往购买页;",
|
|
34867
|
+
"流程说明:此为自动流程,无需您确认;创建成功后将自动绑定到当前会话。"
|
|
34868
|
+
].join("\n");
|
|
34869
|
+
(0, logger_js_1.debug)('[env-setup] User notice prepared:', { userNotice });
|
|
34241
34870
|
try {
|
|
34242
34871
|
// Step 1: Query promotional activities
|
|
34243
34872
|
(0, logger_js_1.debug)('[env-setup] Checking promotional activity eligibility...');
|
|
@@ -34277,7 +34906,8 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
34277
34906
|
};
|
|
34278
34907
|
return {
|
|
34279
34908
|
success: false,
|
|
34280
|
-
context: newContext
|
|
34909
|
+
context: newContext,
|
|
34910
|
+
userNotice
|
|
34281
34911
|
};
|
|
34282
34912
|
}
|
|
34283
34913
|
(0, logger_js_1.debug)('[env-setup] User is eligible for free environment, attempting to create...');
|
|
@@ -34342,7 +34972,8 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
34342
34972
|
};
|
|
34343
34973
|
return {
|
|
34344
34974
|
success: false,
|
|
34345
|
-
context: newContext
|
|
34975
|
+
context: newContext,
|
|
34976
|
+
userNotice
|
|
34346
34977
|
};
|
|
34347
34978
|
}
|
|
34348
34979
|
// Report create success
|
|
@@ -34356,7 +34987,8 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
34356
34987
|
return {
|
|
34357
34988
|
success: true,
|
|
34358
34989
|
envId: envId.trim(), // Ensure no whitespace
|
|
34359
|
-
context: newContext
|
|
34990
|
+
context: newContext,
|
|
34991
|
+
userNotice
|
|
34360
34992
|
};
|
|
34361
34993
|
}
|
|
34362
34994
|
catch (createErr) {
|
|
@@ -34415,7 +35047,8 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
34415
35047
|
});
|
|
34416
35048
|
return {
|
|
34417
35049
|
success: false,
|
|
34418
|
-
context: newContext
|
|
35050
|
+
context: newContext,
|
|
35051
|
+
userNotice
|
|
34419
35052
|
};
|
|
34420
35053
|
}
|
|
34421
35054
|
}
|
|
@@ -34436,7 +35069,8 @@ async function checkAndCreateFreeEnv(cloudbase, context) {
|
|
|
34436
35069
|
});
|
|
34437
35070
|
return {
|
|
34438
35071
|
success: false,
|
|
34439
|
-
context: newContext
|
|
35072
|
+
context: newContext,
|
|
35073
|
+
userNotice
|
|
34440
35074
|
};
|
|
34441
35075
|
}
|
|
34442
35076
|
}
|
|
@@ -40844,6 +41478,7 @@ const QUERY_ACTIONS = [
|
|
|
40844
41478
|
"describeTaskStatus",
|
|
40845
41479
|
"getInstanceInfo",
|
|
40846
41480
|
"describeInstance",
|
|
41481
|
+
"getConnectionInfo",
|
|
40847
41482
|
];
|
|
40848
41483
|
const MANAGE_ACTIONS = [
|
|
40849
41484
|
"provisionMySQL",
|
|
@@ -40851,6 +41486,17 @@ const MANAGE_ACTIONS = [
|
|
|
40851
41486
|
"runStatement",
|
|
40852
41487
|
"initializeSchema",
|
|
40853
41488
|
];
|
|
41489
|
+
function sanitizeInstanceInfo(instanceInfo) {
|
|
41490
|
+
return {
|
|
41491
|
+
exists: instanceInfo.exists,
|
|
41492
|
+
envId: instanceInfo.envId,
|
|
41493
|
+
instanceId: instanceInfo.instanceId,
|
|
41494
|
+
schema: instanceInfo.schema,
|
|
41495
|
+
rawStatus: instanceInfo.rawStatus,
|
|
41496
|
+
status: instanceInfo.status,
|
|
41497
|
+
...(instanceInfo.clusterId ? { clusterId: instanceInfo.clusterId } : {}),
|
|
41498
|
+
};
|
|
41499
|
+
}
|
|
40854
41500
|
function buildNextAction(tool, action, reason, suggestedArgs) {
|
|
40855
41501
|
return {
|
|
40856
41502
|
tool,
|
|
@@ -41299,11 +41945,12 @@ async function handleDescribeTaskStatus(args, context) {
|
|
|
41299
41945
|
}
|
|
41300
41946
|
async function handleGetInstanceInfo(context) {
|
|
41301
41947
|
const instanceInfo = await getSqlInstanceInfo(context);
|
|
41948
|
+
const sanitized = sanitizeInstanceInfo(instanceInfo);
|
|
41302
41949
|
return buildSqlToolResult({
|
|
41303
41950
|
success: true,
|
|
41304
|
-
data:
|
|
41951
|
+
data: sanitized,
|
|
41305
41952
|
message: instanceInfo.exists
|
|
41306
|
-
? "Resolved current SQL database instance context."
|
|
41953
|
+
? "Resolved current SQL database instance context (lifecycle only; connection credentials are omitted). Prefer SDK or runQuery/runStatement for app data access. Use getConnectionInfo only for explicit TCP migration."
|
|
41307
41954
|
: "No SQL database instance is currently available for this environment.",
|
|
41308
41955
|
nextActions: instanceInfo.exists
|
|
41309
41956
|
? undefined
|
|
@@ -41312,6 +41959,33 @@ async function handleGetInstanceInfo(context) {
|
|
|
41312
41959
|
],
|
|
41313
41960
|
});
|
|
41314
41961
|
}
|
|
41962
|
+
async function handleGetConnectionInfo(context) {
|
|
41963
|
+
const instanceInfo = await getSqlInstanceInfo(context);
|
|
41964
|
+
if (!instanceInfo.exists) {
|
|
41965
|
+
return buildSqlToolResult({
|
|
41966
|
+
success: false,
|
|
41967
|
+
errorCode: "MYSQL_NOT_CREATED",
|
|
41968
|
+
data: sanitizeInstanceInfo(instanceInfo),
|
|
41969
|
+
message: "No MySQL instance exists for the current environment, so connection details cannot be returned.",
|
|
41970
|
+
nextActions: [
|
|
41971
|
+
buildNextAction(MANAGE_MYSQL_DATABASE, "provisionMySQL", "Provision MySQL before requesting connection details.", { action: "provisionMySQL", confirm: true }),
|
|
41972
|
+
],
|
|
41973
|
+
});
|
|
41974
|
+
}
|
|
41975
|
+
return buildSqlToolResult({
|
|
41976
|
+
success: true,
|
|
41977
|
+
data: {
|
|
41978
|
+
...sanitizeInstanceInfo(instanceInfo),
|
|
41979
|
+
// Full backend passthrough for explicit TCP migration only (may include credentials).
|
|
41980
|
+
clusterDetail: instanceInfo.clusterDetail,
|
|
41981
|
+
createResult: instanceInfo.createResult,
|
|
41982
|
+
},
|
|
41983
|
+
message: "Returned raw MySQL connection/cluster payload for explicit TCP migration only. Prefer CloudBase SDK or queryMysqlDatabase(runQuery)/manageMysqlDatabase(runStatement) for normal app CRUD — do not treat this as the default data path.",
|
|
41984
|
+
nextActions: [
|
|
41985
|
+
buildNextAction(QUERY_MYSQL_DATABASE, "runQuery", "Prefer platform-delegated read-only SQL instead of embedding TCP credentials in app code.", { action: "runQuery", sql: "SELECT 1" }),
|
|
41986
|
+
],
|
|
41987
|
+
});
|
|
41988
|
+
}
|
|
41315
41989
|
async function handleProvisionMySQL(args, context) {
|
|
41316
41990
|
if (args.confirm !== true) {
|
|
41317
41991
|
return buildSqlToolResult({
|
|
@@ -41329,7 +42003,7 @@ async function handleProvisionMySQL(args, context) {
|
|
|
41329
42003
|
existing.status === "RUNNING") {
|
|
41330
42004
|
return buildSqlToolResult({
|
|
41331
42005
|
success: true,
|
|
41332
|
-
data: existing,
|
|
42006
|
+
data: sanitizeInstanceInfo(existing),
|
|
41333
42007
|
message: "A SQL database instance already exists for the current environment.",
|
|
41334
42008
|
nextActions: existing.status === "READY"
|
|
41335
42009
|
? [
|
|
@@ -41662,11 +42336,11 @@ function registerSQLDatabaseTools(server) {
|
|
|
41662
42336
|
};
|
|
41663
42337
|
server.registerTool?.(QUERY_MYSQL_DATABASE, {
|
|
41664
42338
|
title: "查询 CloudBase MySQL 数据库状态或执行只读 SQL",
|
|
41665
|
-
description: "查询 CloudBase MySQL 数据库信息。支持执行只读 SQL、查询 MySQL 开通结果、查询 MySQL
|
|
42339
|
+
description: "查询 CloudBase MySQL 数据库信息。支持执行只读 SQL、查询 MySQL 开通结果、查询 MySQL 任务状态,以及获取当前实例生命周期上下文。标准 getInstanceInfo/describeInstance 不返回连接凭据;仅 getConnectionInfo 透传原始连接/集群载荷(含可能的凭据),且仅用于显式 TCP 迁移。业务 CRUD 优先使用 SDK 或 runQuery/runStatement。",
|
|
41666
42340
|
inputSchema: {
|
|
41667
42341
|
action: zod_1.z
|
|
41668
42342
|
.enum(QUERY_ACTIONS)
|
|
41669
|
-
.describe("runQuery=execute read-only SQL; describeCreateResult=query CreateMySQL result; describeTaskStatus=query MySQL task status; getInstanceInfo=get
|
|
42343
|
+
.describe("runQuery=execute read-only SQL; describeCreateResult=query CreateMySQL result; describeTaskStatus=query MySQL task status; getInstanceInfo=get lifecycle context without connection credentials; describeInstance=alias of getInstanceInfo; getConnectionInfo=passthrough raw connection/cluster payload including possible credentials (TCP migration exception only)"),
|
|
41670
42344
|
sql: zod_1.z
|
|
41671
42345
|
.string()
|
|
41672
42346
|
.optional()
|
|
@@ -41702,6 +42376,8 @@ function registerSQLDatabaseTools(server) {
|
|
|
41702
42376
|
case "getInstanceInfo":
|
|
41703
42377
|
case "describeInstance":
|
|
41704
42378
|
return handleGetInstanceInfo(context);
|
|
42379
|
+
case "getConnectionInfo":
|
|
42380
|
+
return handleGetConnectionInfo(context);
|
|
41705
42381
|
default:
|
|
41706
42382
|
throw new Error(`Unsupported SQL query action: ${args.action}`);
|
|
41707
42383
|
}
|
|
@@ -90375,7 +91051,7 @@ const TRIGGER_SCHEMA = zod_1.z.object({
|
|
|
90375
91051
|
const CREATE_FUNCTION_SCHEMA = zod_1.z.object({
|
|
90376
91052
|
name: zod_1.z.string().describe("函数名称"),
|
|
90377
91053
|
type: zod_1.z.enum(["Event", "HTTP"]).optional().describe("函数类型"),
|
|
90378
|
-
protocolType: zod_1.z.enum(["
|
|
91054
|
+
protocolType: zod_1.z.enum(["WS"]).optional().describe("HTTP 函数访问协议,当前仅支持 WebSockets,取值为 WS(配合 protocolParams.wsParams 使用)。普通 HTTP 函数不要传此字段;传其他值(如 HTTP)会报 InvalidParameterValue.ProtocolType。"),
|
|
90379
91055
|
protocolParams: zod_1.z
|
|
90380
91056
|
.object({
|
|
90381
91057
|
wsParams: zod_1.z
|
|
@@ -137679,7 +138355,7 @@ class TelemetryReporter {
|
|
|
137679
138355
|
const nodeVersion = process.version; // Node.js版本
|
|
137680
138356
|
const arch = os_1.default.arch(); // 系统架构
|
|
137681
138357
|
// 从构建时注入的版本号获取MCP版本信息
|
|
137682
|
-
const mcpVersion = process.env.npm_package_version || "2.25.
|
|
138358
|
+
const mcpVersion = process.env.npm_package_version || "2.25.4" || 0;
|
|
137683
138359
|
return {
|
|
137684
138360
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
137685
138361
|
deviceId: this.deviceId,
|
|
@@ -161563,6 +162239,68 @@ function isSchemaDdlRisk(risk, sql) {
|
|
|
161563
162239
|
function buildLocalMigrationFileHint(version, name) {
|
|
161564
162240
|
return `migrations/${version}_${name}.sql`;
|
|
161565
162241
|
}
|
|
162242
|
+
/** How many recent migration records to scan when verifying a freshly pushed version. */
|
|
162243
|
+
const MIGRATION_VERIFY_LIMIT = 100;
|
|
162244
|
+
/**
|
|
162245
|
+
* Collect every migration version mentioned anywhere in a migration-list API response.
|
|
162246
|
+
* The response shape is not strongly typed, so walk it generically and pick up any
|
|
162247
|
+
* `Version` / `MigrationVersion` string field instead of guessing a single container key.
|
|
162248
|
+
*/
|
|
162249
|
+
function collectMigrationVersions(value, found = new Set()) {
|
|
162250
|
+
if (Array.isArray(value)) {
|
|
162251
|
+
for (const item of value) {
|
|
162252
|
+
collectMigrationVersions(item, found);
|
|
162253
|
+
}
|
|
162254
|
+
return found;
|
|
162255
|
+
}
|
|
162256
|
+
if (value && typeof value === "object") {
|
|
162257
|
+
for (const [key, child] of Object.entries(value)) {
|
|
162258
|
+
if ((key === "Version" || key === "MigrationVersion") && typeof child === "string" && child.trim()) {
|
|
162259
|
+
found.add(child.trim());
|
|
162260
|
+
}
|
|
162261
|
+
else {
|
|
162262
|
+
collectMigrationVersions(child, found);
|
|
162263
|
+
}
|
|
162264
|
+
}
|
|
162265
|
+
}
|
|
162266
|
+
return found;
|
|
162267
|
+
}
|
|
162268
|
+
/** Brief retries before concluding a pushed migration never landed (Push can be async). */
|
|
162269
|
+
const MIGRATION_VERIFY_ATTEMPTS = 4;
|
|
162270
|
+
const MIGRATION_VERIFY_DELAY_MS = 1500;
|
|
162271
|
+
/**
|
|
162272
|
+
* Confirm a pushed migration version is present in the remote migration history.
|
|
162273
|
+
*
|
|
162274
|
+
* `PushPGUserMigrations` can return a TaskId without the migration actually being
|
|
162275
|
+
* applied, so the raw API response is not sufficient evidence of success. Retries
|
|
162276
|
+
* briefly to tolerate async application, then returns `true`/`false` when the
|
|
162277
|
+
* history could be read, or `null` when verification itself failed (in which case
|
|
162278
|
+
* the caller must not claim the migration is applied).
|
|
162279
|
+
*/
|
|
162280
|
+
async function verifyMigrationApplied(context, version, cloudBaseOptions) {
|
|
162281
|
+
let lastError;
|
|
162282
|
+
let sawSuccessfulList = false;
|
|
162283
|
+
for (let attempt = 1; attempt <= MIGRATION_VERIFY_ATTEMPTS; attempt++) {
|
|
162284
|
+
try {
|
|
162285
|
+
const history = await callPgMigrationApi(context, "ListPGUserMigrations", { Limit: MIGRATION_VERIFY_LIMIT }, cloudBaseOptions);
|
|
162286
|
+
sawSuccessfulList = true;
|
|
162287
|
+
if (collectMigrationVersions(history).has(version)) {
|
|
162288
|
+
return { applied: true };
|
|
162289
|
+
}
|
|
162290
|
+
}
|
|
162291
|
+
catch (error) {
|
|
162292
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
162293
|
+
// Keep retrying: a transient list failure should not immediately fail verification.
|
|
162294
|
+
}
|
|
162295
|
+
if (attempt < MIGRATION_VERIFY_ATTEMPTS) {
|
|
162296
|
+
await sleep(MIGRATION_VERIFY_DELAY_MS);
|
|
162297
|
+
}
|
|
162298
|
+
}
|
|
162299
|
+
if (sawSuccessfulList) {
|
|
162300
|
+
return { applied: false };
|
|
162301
|
+
}
|
|
162302
|
+
return { applied: null, error: lastError };
|
|
162303
|
+
}
|
|
161566
162304
|
const MIGRATION_VERSION_REQUIRED_MESSAGE = "migrationVersion is required (14-digit UTC timestamp YYYYMMDDHHMMSS). " +
|
|
161567
162305
|
"Decide the version first, write local file migrations/<version>_<migrationName>.sql, " +
|
|
161568
162306
|
"then call planMigration/applyMigration with the same migrationVersion and migrationName.";
|
|
@@ -162435,6 +163173,49 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
|
|
|
162435
163173
|
}
|
|
162436
163174
|
try {
|
|
162437
163175
|
const result = await callPgMigrationApi(context, "PushPGUserMigrations", params, cloudBaseOptions);
|
|
163176
|
+
// PushPGUserMigrations may return a TaskId without the migration having taken
|
|
163177
|
+
// effect. Confirm against the remote history before reporting success, so callers
|
|
163178
|
+
// never get a false-positive success for a silently dropped migration.
|
|
163179
|
+
const verification = await verifyMigrationApplied(context, version, cloudBaseOptions);
|
|
163180
|
+
if (verification.applied === false) {
|
|
163181
|
+
return buildPgToolResult({
|
|
163182
|
+
success: false,
|
|
163183
|
+
errorCode: "MIGRATION_NOT_APPLIED",
|
|
163184
|
+
data: {
|
|
163185
|
+
migrationVersion: version,
|
|
163186
|
+
migrationName: args.migrationName,
|
|
163187
|
+
localFileHint,
|
|
163188
|
+
apiResult: result,
|
|
163189
|
+
verified: false,
|
|
163190
|
+
},
|
|
163191
|
+
message: `PushPGUserMigrations returned without error, but migrationVersion=${version} is not present in the remote migration history, ` +
|
|
163192
|
+
"so the migration did NOT take effect. Do not assume the schema change is applied. " +
|
|
163193
|
+
"Re-check the SQL and migrationVersion, inspect the migration with action=migrationDetail, and retry action=applyMigration. " +
|
|
163194
|
+
"If the schema change is urgent you may fall back to action=execute with allowDdlViaExecute=true, but that bypasses migration history.",
|
|
163195
|
+
nextActions: [
|
|
163196
|
+
buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Inspect the remote migration history to confirm the migration is missing.", { action: "listMigrations", limit: 20 }),
|
|
163197
|
+
buildNextAction(MANAGE_PG_DATABASE, "migrationDetail", "Inspect the backend record for this migrationVersion to find why it was not applied.", { action: "migrationDetail", migrationVersion: version }),
|
|
163198
|
+
],
|
|
163199
|
+
});
|
|
163200
|
+
}
|
|
163201
|
+
if (verification.applied === null) {
|
|
163202
|
+
return buildPgToolResult({
|
|
163203
|
+
success: false,
|
|
163204
|
+
errorCode: "MIGRATION_VERIFICATION_FAILED",
|
|
163205
|
+
data: {
|
|
163206
|
+
migrationVersion: version,
|
|
163207
|
+
migrationName: args.migrationName,
|
|
163208
|
+
localFileHint,
|
|
163209
|
+
apiResult: result,
|
|
163210
|
+
verified: null,
|
|
163211
|
+
},
|
|
163212
|
+
message: `PushPGUserMigrations was submitted, but verifying migrationVersion=${version} against the remote history failed: ${verification.error}. ` +
|
|
163213
|
+
"The migration may or may not have been applied — verify with action=listMigrations before running any dependent SQL.",
|
|
163214
|
+
nextActions: [
|
|
163215
|
+
buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Manually verify whether the remote migration history records this migrationVersion.", { action: "listMigrations", limit: 20 }),
|
|
163216
|
+
],
|
|
163217
|
+
});
|
|
163218
|
+
}
|
|
162438
163219
|
return buildPgToolResult({
|
|
162439
163220
|
success: true,
|
|
162440
163221
|
data: {
|
|
@@ -162442,11 +163223,9 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
|
|
|
162442
163223
|
migrationName: args.migrationName,
|
|
162443
163224
|
localFileHint,
|
|
162444
163225
|
apiResult: result,
|
|
163226
|
+
verified: true,
|
|
162445
163227
|
},
|
|
162446
|
-
message: `Migrations applied via PushPGUserMigrations. Ensure local file ${localFileHint} exists and matches.`,
|
|
162447
|
-
nextActions: [
|
|
162448
|
-
buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Verify the remote migration history records the same migrationVersion.", { action: "listMigrations", limit: 20 }),
|
|
162449
|
-
],
|
|
163228
|
+
message: `Migrations applied via PushPGUserMigrations and verified present in the remote migration history. Ensure local file ${localFileHint} exists and matches.`,
|
|
162450
163229
|
});
|
|
162451
163230
|
}
|
|
162452
163231
|
catch (error) {
|
|
@@ -162725,7 +163504,7 @@ function registerPGDatabaseTools(server, providedDeps) {
|
|
|
162725
163504
|
inputSchema: {
|
|
162726
163505
|
action: zod_1.z
|
|
162727
163506
|
.enum(MANAGE_ACTIONS)
|
|
162728
|
-
.describe("操作类型:execute=执行已确认的写入 SQL(DML/GRANT/RLS;schema DDL 默认拒绝,需 allowDdlViaExecute=true);dryRun=只分析 SQL 风险不执行;planMigration=预览迁移计划(需 migrationName + migrationVersion + sql);applyMigration=应用迁移,建表/改 schema 首选(需 migrationName + migrationVersion + sql + confirm=true);listMigrations=查询已应用的 Migration 列表(可传 limit/offset 分页);migrationDetail=查看单条 Migration 详情(需 migrationVersion);rollbackMigration=回滚最近 N 条 Migration(需 lastN + confirm=true);repairMigration=修复 Migration 历史记录(需 migrationVersion + migrationName + repairStatus + repairReason)"),
|
|
163507
|
+
.describe("操作类型:execute=执行已确认的写入 SQL(DML/GRANT/RLS;schema DDL 默认拒绝,需 allowDdlViaExecute=true);dryRun=只分析 SQL 风险不执行;planMigration=预览迁移计划(需 migrationName + migrationVersion + sql);applyMigration=应用迁移,建表/改 schema 首选(需 migrationName + migrationVersion + sql + confirm=true;成功返回前会自动校验该 migrationVersion 已落入远端迁移历史,未落库时返回 success=false 且 errorCode=MIGRATION_NOT_APPLIED);listMigrations=查询已应用的 Migration 列表(可传 limit/offset 分页);migrationDetail=查看单条 Migration 详情(需 migrationVersion);rollbackMigration=回滚最近 N 条 Migration(需 lastN + confirm=true);repairMigration=修复 Migration 历史记录(需 migrationVersion + migrationName + repairStatus + repairReason)"),
|
|
162729
163508
|
sql: zod_1.z
|
|
162730
163509
|
.string()
|
|
162731
163510
|
.optional()
|
|
@@ -197989,7 +198768,7 @@ ${envIdSection}
|
|
|
197989
198768
|
## 环境信息
|
|
197990
198769
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
197991
198770
|
- Node.js版本: ${process.version}
|
|
197992
|
-
- MCP 版本:${process.env.npm_package_version || "2.25.
|
|
198771
|
+
- MCP 版本:${process.env.npm_package_version || "2.25.4" || 0}
|
|
197993
198772
|
- 系统架构: ${os_1.default.arch()}
|
|
197994
198773
|
- 时间: ${new Date().toISOString()}
|
|
197995
198774
|
- 请求ID: ${requestId}
|
|
@@ -198070,7 +198849,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
198070
198849
|
requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
|
|
198071
198850
|
ide: server.ide || process.env.INTEGRATION_IDE || ''
|
|
198072
198851
|
});
|
|
198073
|
-
const mcpVersion = true ? "2.25.
|
|
198852
|
+
const mcpVersion = true ? "2.25.4" : 0;
|
|
198074
198853
|
const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
|
|
198075
198854
|
const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
|
|
198076
198855
|
// 创建新的错误对象,保持原有的错误类型但更新消息
|
|
@@ -214658,7 +215437,7 @@ function registerSetupTools(server) {
|
|
|
214658
215437
|
server.registerTool("downloadTemplate", {
|
|
214659
215438
|
title: "下载项目模板",
|
|
214660
215439
|
description: `自动下载并部署CloudBase项目模板。
|
|
214661
|
-
**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.
|
|
215440
|
+
**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)`,
|
|
214662
215441
|
inputSchema: {
|
|
214663
215442
|
template: zod_1.z
|
|
214664
215443
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|
|
@@ -231762,6 +232541,7 @@ const QUERY_GATEWAY_ACTIONS = [
|
|
|
231762
232541
|
"listRoutes",
|
|
231763
232542
|
"getRoute",
|
|
231764
232543
|
"listCustomDomains",
|
|
232544
|
+
"getPrivilege",
|
|
231765
232545
|
];
|
|
231766
232546
|
const MANAGE_GATEWAY_ACTIONS = [
|
|
231767
232547
|
"createRoute",
|
|
@@ -231769,7 +232549,11 @@ const MANAGE_GATEWAY_ACTIONS = [
|
|
|
231769
232549
|
"deleteRoute",
|
|
231770
232550
|
"bindCustomDomain",
|
|
231771
232551
|
"deleteCustomDomain",
|
|
232552
|
+
"enableService",
|
|
232553
|
+
"authSwitch",
|
|
231772
232554
|
];
|
|
232555
|
+
/** DomainType of the environment default HTTP service (gateway) domain. */
|
|
232556
|
+
const HTTP_SERVICE_DOMAIN_TYPE = "HTTPSERVICE";
|
|
231773
232557
|
const UPSTREAM_RESOURCE_TYPES = [
|
|
231774
232558
|
"SCF",
|
|
231775
232559
|
"WEB_SCF",
|
|
@@ -231806,21 +232590,19 @@ function registerGatewayTools(server) {
|
|
|
231806
232590
|
return (0, json_content_js_1.jsonContent)(buildErrorEnvelope(error));
|
|
231807
232591
|
}
|
|
231808
232592
|
};
|
|
231809
|
-
const listHttpServiceRoutes = async (
|
|
232593
|
+
const listHttpServiceRoutes = async (options) => {
|
|
232594
|
+
const filters = [];
|
|
232595
|
+
if (options?.domain) {
|
|
232596
|
+
filters.push({ Name: "Domain", Values: [options.domain] });
|
|
232597
|
+
}
|
|
232598
|
+
if (options?.domainType) {
|
|
232599
|
+
filters.push({ Name: "DomainType", Values: [options.domainType] });
|
|
232600
|
+
}
|
|
231810
232601
|
const cloudbase = await getManager();
|
|
231811
232602
|
const result = await cloudbase.env.describeHttpServiceRoute({
|
|
231812
232603
|
EnvId: await resolveEnvId(),
|
|
231813
232604
|
Limit: 1000,
|
|
231814
|
-
...(
|
|
231815
|
-
? {
|
|
231816
|
-
Filters: [
|
|
231817
|
-
{
|
|
231818
|
-
Name: "Domain",
|
|
231819
|
-
Values: [domain],
|
|
231820
|
-
},
|
|
231821
|
-
],
|
|
231822
|
-
}
|
|
231823
|
-
: {}),
|
|
232605
|
+
...(filters.length ? { Filters: filters } : {}),
|
|
231824
232606
|
});
|
|
231825
232607
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
231826
232608
|
return result;
|
|
@@ -231859,15 +232641,49 @@ function registerGatewayTools(server) {
|
|
|
231859
232641
|
: {}),
|
|
231860
232642
|
};
|
|
231861
232643
|
};
|
|
232644
|
+
const pickUsableDefaultDomain = (domains) => {
|
|
232645
|
+
const candidates = domains.filter((item) => item.IsDefault === true && item.Domain);
|
|
232646
|
+
return (candidates.find((item) => item.Enable !== false && item.Status === "SUCCESS") ??
|
|
232647
|
+
candidates.find((item) => item.Enable !== false) ??
|
|
232648
|
+
candidates[0]);
|
|
232649
|
+
};
|
|
232650
|
+
const getGatewayPrivilege = async () => {
|
|
232651
|
+
const cloudbase = await getManager();
|
|
232652
|
+
const result = await cloudbase
|
|
232653
|
+
.commonService("tcb", "2018-06-08")
|
|
232654
|
+
.call({
|
|
232655
|
+
Action: "DescribeCloudBaseGWPrivilege",
|
|
232656
|
+
Param: {
|
|
232657
|
+
ServiceId: await resolveEnvId(),
|
|
232658
|
+
},
|
|
232659
|
+
});
|
|
232660
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
232661
|
+
return result ?? {};
|
|
232662
|
+
};
|
|
232663
|
+
const buildPrivilegeDescription = (privilege) => {
|
|
232664
|
+
const serviceStatus = privilege.EnableService === true ? "已开启" : "未开启";
|
|
232665
|
+
const authStatus = privilege.EnableAuth === undefined
|
|
232666
|
+
? "未知"
|
|
232667
|
+
: privilege.EnableAuth === true
|
|
232668
|
+
? "已开启"
|
|
232669
|
+
: "未开启";
|
|
232670
|
+
return `HTTP 网关${serviceStatus},访问鉴权${authStatus}`;
|
|
232671
|
+
};
|
|
231862
232672
|
const resolveDefaultHttpDomain = async () => {
|
|
231863
|
-
|
|
231864
|
-
|
|
231865
|
-
|
|
231866
|
-
const
|
|
231867
|
-
|
|
231868
|
-
|
|
232673
|
+
// Match console DescribeHTTPServiceRoute usage: filter DomainType=HTTPSERVICE.
|
|
232674
|
+
// Envs often also expose an IsDefault STATIC_STORE domain (*.tcloudbaseapp.com);
|
|
232675
|
+
// that is NOT the default HTTP access entry (*.{region}.app.tcloudbase.com).
|
|
232676
|
+
const httpServiceRoutes = await listHttpServiceRoutes({
|
|
232677
|
+
domainType: HTTP_SERVICE_DOMAIN_TYPE,
|
|
232678
|
+
});
|
|
232679
|
+
// Always re-filter client-side: some SDKs/mocks may ignore Filters.
|
|
232680
|
+
let preferred = pickUsableDefaultDomain((httpServiceRoutes.Domains ?? []).filter((item) => item.DomainType === HTTP_SERVICE_DOMAIN_TYPE));
|
|
231869
232681
|
if (!preferred?.Domain) {
|
|
231870
|
-
|
|
232682
|
+
const allRoutes = await listHttpServiceRoutes();
|
|
232683
|
+
preferred = pickUsableDefaultDomain((allRoutes.Domains ?? []).filter((item) => item.DomainType === HTTP_SERVICE_DOMAIN_TYPE));
|
|
232684
|
+
}
|
|
232685
|
+
if (!preferred?.Domain) {
|
|
232686
|
+
throw new Error("环境默认 HTTP 访问域名未就绪或未开通。请先在控制台开通 HTTP 访问服务,或用 queryGateway(action=\"listRoutes\") 确认 Domains 中是否存在 DomainType=HTTPSERVICE 且 IsDefault=true 的域名(形如 *.{region}.app.tcloudbase.com);不要使用静态托管域名(*.tcloudbaseapp.com)。也可以显式传入 domain 后重试 createRoute/updateRoute/deleteRoute。");
|
|
231871
232687
|
}
|
|
231872
232688
|
return preferred.Domain;
|
|
231873
232689
|
};
|
|
@@ -231963,7 +232779,7 @@ function registerGatewayTools(server) {
|
|
|
231963
232779
|
}, `已获取 ${customDomains.length} 个自定义域名`);
|
|
231964
232780
|
}
|
|
231965
232781
|
case "listRoutes": {
|
|
231966
|
-
const result = await listHttpServiceRoutes(input.domain);
|
|
232782
|
+
const result = await listHttpServiceRoutes({ domain: input.domain });
|
|
231967
232783
|
const routes = flattenRoutes(result);
|
|
231968
232784
|
return buildEnvelope({
|
|
231969
232785
|
action: input.action,
|
|
@@ -231978,7 +232794,7 @@ function registerGatewayTools(server) {
|
|
|
231978
232794
|
if (!input.routeId && !input.targetName && !input.path) {
|
|
231979
232795
|
throw new Error("action=getRoute 时至少需要提供 routeId、targetName 或 path");
|
|
231980
232796
|
}
|
|
231981
|
-
const result = await listHttpServiceRoutes(input.domain);
|
|
232797
|
+
const result = await listHttpServiceRoutes({ domain: input.domain });
|
|
231982
232798
|
const normalizedPath = input.path
|
|
231983
232799
|
? normalizeAccessPath(input.path)
|
|
231984
232800
|
: undefined;
|
|
@@ -232025,6 +232841,28 @@ function registerGatewayTools(server) {
|
|
|
232025
232841
|
},
|
|
232026
232842
|
]);
|
|
232027
232843
|
}
|
|
232844
|
+
case "getPrivilege": {
|
|
232845
|
+
const privilege = await getGatewayPrivilege();
|
|
232846
|
+
const enableService = privilege.EnableService === true;
|
|
232847
|
+
const enableAuth = privilege.EnableAuth === true;
|
|
232848
|
+
return buildEnvelope({
|
|
232849
|
+
action: input.action,
|
|
232850
|
+
enableService,
|
|
232851
|
+
enableAuth,
|
|
232852
|
+
raw: privilege,
|
|
232853
|
+
}, buildPrivilegeDescription(privilege) +
|
|
232854
|
+
(enableService
|
|
232855
|
+
? ""
|
|
232856
|
+
: ";若路由创建成功但访问报 HTTPSERVICE_NONACTIVATED(403),请先开启 HTTP 网关"), enableService
|
|
232857
|
+
? undefined
|
|
232858
|
+
: [
|
|
232859
|
+
{
|
|
232860
|
+
tool: "manageGateway",
|
|
232861
|
+
action: "enableService",
|
|
232862
|
+
reason: "开启 HTTP 网关总开关(EnableService),开启后路由即可通过默认域访问",
|
|
232863
|
+
},
|
|
232864
|
+
]);
|
|
232865
|
+
}
|
|
232028
232866
|
default:
|
|
232029
232867
|
throw new Error(`不支持的操作类型: ${input.action}`);
|
|
232030
232868
|
}
|
|
@@ -232055,6 +232893,28 @@ function registerGatewayTools(server) {
|
|
|
232055
232893
|
throw err;
|
|
232056
232894
|
}
|
|
232057
232895
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
232896
|
+
// 探测 HTTP 网关总开关:未开启时提示 HTTPSERVICE_NONACTIVATED 风险并引导开启。
|
|
232897
|
+
// 探测失败不阻断路由创建结果,仅追加弱提示。
|
|
232898
|
+
let privilegeHint = "";
|
|
232899
|
+
let privilegeNextActions = [];
|
|
232900
|
+
try {
|
|
232901
|
+
const privilege = await getGatewayPrivilege();
|
|
232902
|
+
if (privilege.EnableService !== true) {
|
|
232903
|
+
privilegeHint =
|
|
232904
|
+
"⚠️ HTTP 网关总开关未开启,路由创建成功后访问仍将返回 HTTPSERVICE_NONACTIVATED(403);请先调用 manageGateway(action=\"enableService\", enable=true) 开启,再等待 30 秒到 3 分钟访问。";
|
|
232905
|
+
privilegeNextActions = [
|
|
232906
|
+
{
|
|
232907
|
+
tool: "manageGateway",
|
|
232908
|
+
action: "enableService",
|
|
232909
|
+
reason: "开启 HTTP 网关总开关(EnableService),否则访问路由会报 HTTPSERVICE_NONACTIVATED(403)",
|
|
232910
|
+
},
|
|
232911
|
+
];
|
|
232912
|
+
}
|
|
232913
|
+
}
|
|
232914
|
+
catch {
|
|
232915
|
+
privilegeHint =
|
|
232916
|
+
"(无法确认 HTTP 网关开关状态;若访问报 HTTPSERVICE_NONACTIVATED,请用 queryGateway(action=\"getPrivilege\") 查询后用 manageGateway(action=\"enableService\") 开启)";
|
|
232917
|
+
}
|
|
232058
232918
|
return buildEnvelope({
|
|
232059
232919
|
action: input.action,
|
|
232060
232920
|
model: "httpServiceRoute",
|
|
@@ -232077,7 +232937,12 @@ function registerGatewayTools(server) {
|
|
|
232077
232937
|
: payload.resolved.enablePathTransmission === false
|
|
232078
232938
|
? ";路径透传关闭(网关会剥掉触发路径前缀后再转发给后端)"
|
|
232079
232939
|
: ";路径透传未显式设置(平台默认 false,会剥掉触发路径前缀)") +
|
|
232080
|
-
`。注意:路由配置传播通常需要等待 30 秒到 3
|
|
232940
|
+
`。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。该操作只创建网关入口,不会自动放开上游权限;若上游是云函数且需要匿名或浏览器直接访问,请继续检查函数资源权限。` +
|
|
232941
|
+
(privilegeHint ? ` ${privilegeHint}` : ""), [
|
|
232942
|
+
...privilegeNextActions,
|
|
232943
|
+
...(routeMutationNextActions(payload.resolved.upstreamResourceName) ??
|
|
232944
|
+
[]),
|
|
232945
|
+
]);
|
|
232081
232946
|
}
|
|
232082
232947
|
case "updateRoute": {
|
|
232083
232948
|
const cloudbase = await getManager();
|
|
@@ -232168,6 +233033,60 @@ function registerGatewayTools(server) {
|
|
|
232168
233033
|
raw: result,
|
|
232169
233034
|
}, "自定义域名删除成功");
|
|
232170
233035
|
}
|
|
233036
|
+
case "enableService": {
|
|
233037
|
+
if (typeof input.enable !== "boolean") {
|
|
233038
|
+
throw new Error('action=enableService 时必须提供 enable 参数(boolean),如 enable=true 开启 HTTP 网关总开关、enable=false 关闭;禁止省略或传非布尔值。');
|
|
233039
|
+
}
|
|
233040
|
+
const cloudbase = await getManager();
|
|
233041
|
+
const result = await cloudbase.access.switchAuth(input.enable);
|
|
233042
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
233043
|
+
const serviceText = input.enable ? "开启" : "关闭";
|
|
233044
|
+
return buildEnvelope({
|
|
233045
|
+
action: input.action,
|
|
233046
|
+
enable: input.enable,
|
|
233047
|
+
raw: result,
|
|
233048
|
+
}, `HTTP 网关总开关${serviceText}成功`, [
|
|
233049
|
+
{
|
|
233050
|
+
tool: "queryGateway",
|
|
233051
|
+
action: "getPrivilege",
|
|
233052
|
+
reason: "复核 HTTP 网关总开关与访问鉴权状态",
|
|
233053
|
+
},
|
|
233054
|
+
]);
|
|
233055
|
+
}
|
|
233056
|
+
case "authSwitch": {
|
|
233057
|
+
if (typeof input.enable !== "boolean") {
|
|
233058
|
+
throw new Error('action=authSwitch 时必须提供 enable 参数(boolean),如 enable=true 开启访问鉴权、enable=false 关闭;禁止省略或传非布尔值。');
|
|
233059
|
+
}
|
|
233060
|
+
const cloudbase = await getManager();
|
|
233061
|
+
const result = await cloudbase
|
|
233062
|
+
.commonService("tcb", "2018-06-08")
|
|
233063
|
+
.call({
|
|
233064
|
+
Action: "ModifyCloudBaseGWPrivilege",
|
|
233065
|
+
Param: {
|
|
233066
|
+
ServiceId: await resolveEnvId(),
|
|
233067
|
+
EnableService: input.enable,
|
|
233068
|
+
Options: [
|
|
233069
|
+
{
|
|
233070
|
+
Key: "authswitch",
|
|
233071
|
+
Value: input.enable ? "true" : "false",
|
|
233072
|
+
},
|
|
233073
|
+
],
|
|
233074
|
+
},
|
|
233075
|
+
});
|
|
233076
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
233077
|
+
const serviceText = input.enable ? "开启" : "关闭";
|
|
233078
|
+
return buildEnvelope({
|
|
233079
|
+
action: input.action,
|
|
233080
|
+
enable: input.enable,
|
|
233081
|
+
raw: result,
|
|
233082
|
+
}, `HTTP 访问服务鉴权${serviceText}成功`, [
|
|
233083
|
+
{
|
|
233084
|
+
tool: "queryGateway",
|
|
233085
|
+
action: "getPrivilege",
|
|
233086
|
+
reason: "复核 HTTP 网关总开关与访问鉴权状态",
|
|
233087
|
+
},
|
|
233088
|
+
]);
|
|
233089
|
+
}
|
|
232171
233090
|
default:
|
|
232172
233091
|
throw new Error(`不支持的操作类型: ${input.action}`);
|
|
232173
233092
|
}
|
|
@@ -232176,11 +233095,15 @@ function registerGatewayTools(server) {
|
|
|
232176
233095
|
title: "查询 CloudBase 网关",
|
|
232177
233096
|
description: "CloudBase HTTP 网关统一只读入口(Domain/Route)。查询域名下路径路由及其上游:" +
|
|
232178
233097
|
"WEB_SCF/SCF=云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器。" +
|
|
232179
|
-
"主键为 Domain + Path;listRoutes / getRoute / listCustomDomains。"
|
|
233098
|
+
"主键为 Domain + Path;listRoutes / getRoute / listCustomDomains / getPrivilege。" +
|
|
233099
|
+
"getPrivilege 查询 HTTP 网关总开关(enableService)与访问鉴权(enableAuth)状态。" +
|
|
233100
|
+
"实现自定义域名访问前,先 listCustomDomains:若已有自定义域名,优先 createRoute 挂路由(无需证书 ID);仅在没有可用自定义域名时才 bindCustomDomain。",
|
|
232180
233101
|
inputSchema: {
|
|
232181
233102
|
action: zod_1.z
|
|
232182
233103
|
.enum(QUERY_GATEWAY_ACTIONS)
|
|
232183
|
-
.describe("只读操作类型:listRoutes、getRoute、listCustomDomains"
|
|
233104
|
+
.describe("只读操作类型:listRoutes、getRoute、listCustomDomains、getPrivilege。" +
|
|
233105
|
+
"getPrivilege 无需其他参数,直接返回 HTTP 网关总开关与访问鉴权状态。" +
|
|
233106
|
+
"自定义域名访问场景先 listCustomDomains 确认是否已有域名可复用。"),
|
|
232184
233107
|
targetName: zod_1.z
|
|
232185
233108
|
.string()
|
|
232186
233109
|
.optional()
|
|
@@ -232203,18 +233126,25 @@ function registerGatewayTools(server) {
|
|
|
232203
233126
|
}, async (input) => withEnvelope(() => handleQueryGateway(input)));
|
|
232204
233127
|
server.registerTool?.("manageGateway", {
|
|
232205
233128
|
title: "管理 CloudBase 网关",
|
|
232206
|
-
description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;未传 domain 时用 IsDefault
|
|
233129
|
+
description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;未传 domain 时用 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(形如 *.{region}.app.tcloudbase.com),不会使用静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE)。" +
|
|
233130
|
+
"这是网关默认域上的路径路由,不是 STATIC_STORE 上游绑定;STATIC_STORE 上游必须显式传 upstreamResourceType=STATIC_STORE。" +
|
|
233131
|
+
"创建后可用 queryGateway(action=\"listRoutes\") 核对 Domain / DomainType / Path / UpstreamResourceType。" +
|
|
232207
233132
|
"上游类型只用一个参数 upstreamResourceType(也可写在 route.upstreamResourceType,route 优先):" +
|
|
232208
233133
|
"WEB_SCF=HTTP云函数,SCF=Event云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器;" +
|
|
232209
233134
|
"配合 targetName 或 route.serviceName(云函数名/云托管服务名/静态托管实例名,常见 staticstore)。" +
|
|
232210
233135
|
"createRoute 只建网关入口,不改上游权限。" +
|
|
232211
233136
|
"enablePathTransmission:默认 false 剥触发路径前缀;true 透传完整路径(CBR 多路由、WEB_SCF 自管子路径常需 true;STATIC_STORE 自定义触发路径映射站点根通常 false)。" +
|
|
232212
|
-
"⚠️
|
|
233137
|
+
"⚠️ 自定义域名访问:若环境已有自定义域名(先 queryGateway listCustomDomains),优先 createRoute 并显式传入该 domain,无需 certificateId;" +
|
|
233138
|
+
"仅首次绑定全新自定义域名时用 bindCustomDomain(需 certificateId)。CORS/安全域名用 envDomainManagement。" +
|
|
233139
|
+
"enableService/authSwitch:HTTP 网关总开关与访问鉴权开关;createRoute 后若访问报 HTTPSERVICE_NONACTIVATED,通常是总开关未开启(用 queryGateway getPrivilege 查询、enableService 开启)。",
|
|
232213
233140
|
inputSchema: {
|
|
232214
233141
|
action: zod_1.z
|
|
232215
233142
|
.enum(MANAGE_GATEWAY_ACTIONS)
|
|
232216
|
-
.describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;bindCustomDomain/deleteCustomDomain
|
|
232217
|
-
"
|
|
233143
|
+
.describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;bindCustomDomain/deleteCustomDomain 管理自定义域名;" +
|
|
233144
|
+
"enableService/authSwitch 开关 HTTP 网关总开关与访问鉴权(需配合 enable 参数)。" +
|
|
233145
|
+
"createRoute/updateRoute 必须提供 upstreamResourceType。" +
|
|
233146
|
+
"已有自定义域名时优先 createRoute(domain=已有域名) 实现访问,不必再次 bindCustomDomain / 传入 certificateId;" +
|
|
233147
|
+
"bindCustomDomain 仅用于首次绑定新域名。"),
|
|
232218
233148
|
targetName: zod_1.z
|
|
232219
233149
|
.string()
|
|
232220
233150
|
.optional()
|
|
@@ -232268,11 +233198,20 @@ function registerGatewayTools(server) {
|
|
|
232268
233198
|
domain: zod_1.z
|
|
232269
233199
|
.string()
|
|
232270
233200
|
.optional()
|
|
232271
|
-
.describe("域名。省略时自动使用环境 IsDefault 默认 HTTP
|
|
233201
|
+
.describe("域名。省略时自动使用环境 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(*.{region}.app.tcloudbase.com),不会回退到静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE);也不是 STATIC_STORE 上游绑定。" +
|
|
233202
|
+
"可用 queryGateway(action=\"listRoutes\") 核对实际 Domain / DomainType。" +
|
|
233203
|
+
"已有自定义域名时请显式传入该域名并 createRoute/updateRoute/deleteRoute,即可实现自定义域名访问且无需证书 ID;" +
|
|
233204
|
+
"仅 bindCustomDomain 时表示要新绑定的域名。"),
|
|
232272
233205
|
certificateId: zod_1.z
|
|
232273
233206
|
.string()
|
|
232274
233207
|
.optional()
|
|
232275
|
-
.describe("证书 ID
|
|
233208
|
+
.describe("证书 ID。仅首次 bindCustomDomain 必填。" +
|
|
233209
|
+
"在已有自定义域名上 createRoute / updateRoute / deleteRoute 不需要 certificateId。"),
|
|
233210
|
+
enable: zod_1.z
|
|
233211
|
+
.boolean()
|
|
233212
|
+
.optional()
|
|
233213
|
+
.describe("开关目标状态(仅 enableService / authSwitch 使用,必填):" +
|
|
233214
|
+
"enable=true 开启,enable=false 关闭。省略或非布尔值会返回参数错误。"),
|
|
232276
233215
|
},
|
|
232277
233216
|
annotations: {
|
|
232278
233217
|
readOnlyHint: false,
|