@cloudbase/cloudbase-mcp 2.25.10 → 2.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import * as __WEBPACK_EXTERNAL_MODULE_https__ from "https";
13
13
  import * as __WEBPACK_EXTERNAL_MODULE_net__ from "net";
14
14
  import * as __WEBPACK_EXTERNAL_MODULE_fs__ from "fs";
15
15
  import * as __WEBPACK_EXTERNAL_MODULE__modelcontextprotocol_sdk_server_mcp_js_45c326f0__ from "@modelcontextprotocol/sdk/server/mcp.js";
16
+ import * as __WEBPACK_EXTERNAL_MODULE_node_path_02319fef__ from "node:path";
16
17
  import * as __WEBPACK_EXTERNAL_MODULE_child_process__ from "child_process";
17
18
  import * as __WEBPACK_EXTERNAL_MODULE_ws__ from "ws";
18
19
  import * as __WEBPACK_EXTERNAL_MODULE__cloudbase_manager_node_6a52ea40__ from "@cloudbase/manager-node";
@@ -23,6 +24,7 @@ import * as __WEBPACK_EXTERNAL_MODULE_crypto__ from "crypto";
23
24
  import * as __WEBPACK_EXTERNAL_MODULE_fs_promises_f8dae9d1__ from "fs/promises";
24
25
  import * as __WEBPACK_EXTERNAL_MODULE_os__ from "os";
25
26
  import * as __WEBPACK_EXTERNAL_MODULE_winston__ from "winston";
27
+ import * as __WEBPACK_EXTERNAL_MODULE_node_fs_75ed2103__ from "node:fs";
26
28
  /******/ var __webpack_modules__ = ({
27
29
 
28
30
  /***/ 622:
@@ -41,6 +43,7 @@ const auth_js_1 = __webpack_require__(7291);
41
43
  const cloudbase_manager_js_1 = __webpack_require__(3431);
42
44
  const logger_js_1 = __webpack_require__(3039);
43
45
  const tool_result_js_1 = __webpack_require__(9835);
46
+ const gateway_access_urls_js_1 = __webpack_require__(2435);
44
47
  const env_setup_js_1 = __webpack_require__(6978);
45
48
  /**
46
49
  * Resources accepted by CreateEnv. Matches Cloud API / Manager SDK contract.
@@ -793,6 +796,89 @@ async function enrichEnvInfoWithMissingFields(manager, result, envId) {
793
796
  return result;
794
797
  }
795
798
  }
799
+ /**
800
+ * Project gateway Route.Enable onto envQuery(info) without mutating StaticDomain.
801
+ *
802
+ * StaticStorages[].StaticDomain stays the cloud-API nominal hostname.
803
+ * Sibling field staticDomainRouteEnabled (and EnvInfo-level mirrors for the
804
+ * primary store) tell callers whether the default hosting domain route is
805
+ * actually reachable (Enable !== false). Matches queryHosting websiteConfig.
806
+ */
807
+ async function enrichEnvInfoWithStaticDomainRouteEnabled(manager, result, envId) {
808
+ const envInfo = result?.EnvInfo;
809
+ if (!envInfo || typeof envInfo !== "object") {
810
+ return result;
811
+ }
812
+ const staticStorages = Array.isArray(envInfo.StaticStorages)
813
+ ? envInfo.StaticStorages
814
+ : null;
815
+ if (!staticStorages || staticStorages.length === 0) {
816
+ return result;
817
+ }
818
+ if (typeof manager?.env?.describeHttpServiceRoute !== "function") {
819
+ return result;
820
+ }
821
+ const resolvedEnvId = (typeof envId === "string" && envId.trim()) ||
822
+ (typeof envInfo.EnvId === "string" && envInfo.EnvId.trim()) ||
823
+ undefined;
824
+ if (!resolvedEnvId) {
825
+ return result;
826
+ }
827
+ try {
828
+ const routeResult = await manager.env.describeHttpServiceRoute({
829
+ EnvId: resolvedEnvId,
830
+ Limit: 1000,
831
+ });
832
+ const routes = (0, gateway_access_urls_js_1.flattenHttpServiceRoutes)(routeResult);
833
+ let primaryRouteEnabled = null;
834
+ const enrichedStorages = staticStorages.map((store, index) => {
835
+ if (!store || typeof store !== "object" || Array.isArray(store)) {
836
+ return store;
837
+ }
838
+ const record = store;
839
+ const domain = typeof record.StaticDomain === "string" ? record.StaticDomain : "";
840
+ if (!domain) {
841
+ return store;
842
+ }
843
+ const reachable = (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(routes, domain, "/");
844
+ if (index === 0) {
845
+ primaryRouteEnabled = reachable;
846
+ }
847
+ return {
848
+ ...record,
849
+ staticDomainRouteEnabled: reachable,
850
+ };
851
+ });
852
+ const enrichment = {
853
+ StaticStorages: enrichedStorages,
854
+ };
855
+ if (primaryRouteEnabled !== null) {
856
+ enrichment.staticDomainRouteEnabled = primaryRouteEnabled;
857
+ enrichment.accessUrlReachable = primaryRouteEnabled !== false;
858
+ if (primaryRouteEnabled === false) {
859
+ const primaryDomain = typeof enrichedStorages[0]
860
+ ?.StaticDomain === "string"
861
+ ? String(enrichedStorages[0].StaticDomain)
862
+ : "";
863
+ enrichment.routeDisabled = true;
864
+ if (primaryDomain) {
865
+ enrichment.disabledAccessUrls = [`https://${primaryDomain}/`];
866
+ }
867
+ }
868
+ }
869
+ return {
870
+ ...result,
871
+ EnvInfo: {
872
+ ...envInfo,
873
+ ...enrichment,
874
+ },
875
+ };
876
+ }
877
+ catch {
878
+ // Gateway route lookup is best-effort enrichment only.
879
+ return result;
880
+ }
881
+ }
796
882
  function normalizeOptionalToolString(value) {
797
883
  return typeof value === "string" && value.trim().length > 0
798
884
  ? value.trim()
@@ -1274,8 +1360,9 @@ function registerEnvTools(server) {
1274
1360
  if (action === "status") {
1275
1361
  const loginState = await (0, auth_js_1.peekLoginState)();
1276
1362
  const authFlowState = await (0, auth_js_1.getAuthProgressState)();
1277
- // 判断是否为 API Key 模式
1278
- const isApiKeyMode = !!(process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID);
1363
+ // Detect API Key mode (CLOUDBASE_API_KEY preferred, CLOUDBASE_APIKEY fallback)
1364
+ const apiKeyFromEnv = (0, auth_js_1.getCloudBaseApiKeyFromEnv)();
1365
+ const isApiKeyMode = !!(apiKeyFromEnv && process.env.CLOUDBASE_ENV_ID);
1279
1366
  const authStatus = loginState
1280
1367
  ? "READY"
1281
1368
  : authFlowState.status === "PENDING"
@@ -1323,8 +1410,9 @@ function registerEnvTools(server) {
1323
1410
  });
1324
1411
  }
1325
1412
  if (action === "start_auth") {
1326
- // API Key 模式:尝试换取临时密钥
1327
- if (process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID) {
1413
+ // API Key mode: exchange for temporary credentials
1414
+ const apiKeyFromEnv = (0, auth_js_1.getCloudBaseApiKeyFromEnv)();
1415
+ if (apiKeyFromEnv && process.env.CLOUDBASE_ENV_ID) {
1328
1416
  try {
1329
1417
  const existingLoginState = await (0, auth_js_1.peekLoginState)();
1330
1418
  if (existingLoginState) {
@@ -1338,22 +1426,21 @@ function registerEnvTools(server) {
1338
1426
  }
1339
1427
  }
1340
1428
  catch (e) {
1341
- // peekLoginState 内部异常,记录后 fall through
1429
+ // peekLoginState threw; log and fall through to diagnostic
1342
1430
  (0, logger_js_1.debug)("start_auth: peekLoginState threw", { error: e instanceof Error ? e.message : String(e) });
1343
1431
  }
1344
- // API Key 换取失败:返回详细诊断信息
1345
- // 尝试获取更详细的错误信息
1432
+ // API Key exchange failed: return diagnostic details
1346
1433
  let diagMessage = "当前配置了 API Key 认证模式,但换取临时密钥失败。";
1347
1434
  const endpoint = process.env.CLOUDBASE_API_ENDPOINT || `https://${process.env.CLOUDBASE_ENV_ID}.ap-shanghai.tcb-api.tencentcloudapi.com`;
1348
1435
  diagMessage += `\n\n诊断信息:`;
1349
1436
  diagMessage += `\n- CLOUDBASE_ENV_ID: ${process.env.CLOUDBASE_ENV_ID}`;
1350
- diagMessage += `\n- CLOUDBASE_API_KEY: ${process.env.CLOUDBASE_API_KEY.slice(0, 20)}...(已截断)`;
1437
+ diagMessage += `\n- CLOUDBASE_API_KEY: ${apiKeyFromEnv.slice(0, 20)}...(已截断)`;
1351
1438
  diagMessage += `\n- Endpoint: ${endpoint}`;
1352
1439
  diagMessage += `\n\n可能原因:`;
1353
1440
  diagMessage += `\n1. API Key 已过期或被删除`;
1354
1441
  diagMessage += `\n2. Endpoint 不可达(网络/DNS 问题)`;
1355
1442
  diagMessage += `\n3. CLOUDBASE_ENV_ID 与 API Key 所属环境不匹配`;
1356
- diagMessage += `\n\n建议:检查 MCP 配置中的 CLOUDBASE_API_KEY 和 CLOUDBASE_ENV_ID 环境变量是否正确。`;
1443
+ diagMessage += `\n\n建议:检查 MCP 配置中的 CLOUDBASE_API_KEY(或兼容的 CLOUDBASE_APIKEY)和 CLOUDBASE_ENV_ID 环境变量是否正确。`;
1357
1444
  return (0, tool_result_js_1.buildJsonToolResult)({
1358
1445
  ok: false,
1359
1446
  code: "API_KEY_AUTH_FAILED",
@@ -1639,12 +1726,12 @@ function registerEnvTools(server) {
1639
1726
  });
1640
1727
  }
1641
1728
  if (action === "logout") {
1642
- // API Key 模式下不允许 logout
1643
- if (process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID) {
1729
+ // Disallow logout in API Key mode
1730
+ if ((0, auth_js_1.getCloudBaseApiKeyFromEnv)() && process.env.CLOUDBASE_ENV_ID) {
1644
1731
  return (0, tool_result_js_1.buildJsonToolResult)({
1645
1732
  ok: false,
1646
1733
  code: "LOGOUT_NOT_ALLOWED",
1647
- message: "当前使用 API Key 认证模式,不支持退出登录。如需切换认证方式,请移除 CLOUDBASE_API_KEY 环境变量后重启。",
1734
+ message: "当前使用 API Key 认证模式,不支持退出登录。如需切换认证方式,请移除 CLOUDBASE_API_KEY(或兼容的 CLOUDBASE_APIKEY)环境变量后重启。",
1648
1735
  auth_mode: "api_key",
1649
1736
  });
1650
1737
  }
@@ -1866,6 +1953,8 @@ function registerEnvTools(server) {
1866
1953
  if (envId) {
1867
1954
  result = await enrichEnvInfoWithMissingFields(cloudbaseInfo, result, envId);
1868
1955
  }
1956
+ // Project gateway Route.Enable next to StaticDomain (do not rewrite StaticDomain).
1957
+ result = await enrichEnvInfoWithStaticDomainRouteEnabled(cloudbaseInfo, result, envId);
1869
1958
  result = await enrichEnvInfoWithRuntimeMode(result, cloudbaseInfo);
1870
1959
  break;
1871
1960
  case "domains":
@@ -1934,7 +2023,7 @@ function registerEnvTools(server) {
1934
2023
  // Register primary tool name (queryEnv)
1935
2024
  const queryEnvToolSchema = {
1936
2025
  title: "CloudBase 环境查询",
1937
- description: "查询 CloudBase 环境相关信息,支持查询环境列表、指定环境详情和安全域名。(曾用名:envQuery、listEnvs、getEnvInfo、getEnvAuthDomains)当 action=list 时,会按 DescribeEnvs 语义做列表/筛选,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知 EnvId 对应环境的详细信息(包括资源字段和计费信息),必须使用 action=info 并传入目标环境的 envId 参数。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。\n\n🔍 action=info 还会派生三个用于后端选型的字段:\n- `EnvInfo.RuntimeMode`:'postgresql' 或 'nosql',表示新业务建议默认使用的后端(PG 已开通时为 postgresql,否则为 nosql)。\n- `EnvInfo.RuntimeBackends`:`{postgresql, nosql, mysql}` 三个布尔值,描述当前环境实际并存的后端。\n- `EnvInfo.RuntimeModeHints`:每个后端对应的 API/工具/skill 提示。\n\nAI 在写业务/权限/存储代码前必须先看这三项:PG 模式下新业务推荐 `app.rdb()` + RLS(`managePgDatabase action=execute` 跑 `CREATE POLICY`)+ pgstore;已存在的 NoSQL 集合 / 旧 storage / `managePermissions(resourceType=\"noSqlDatabase\")` 在 PG 环境下仍然有效。真正不适用的是 MySQL:当 `RuntimeBackends.mysql === false` 时,`manageMysqlDatabase` / `queryMysqlDatabase` / `relational-database-mcp-cloudbase` skill 都不该使用。",
2026
+ description: "查询 CloudBase 环境相关信息,支持查询环境列表、指定环境详情和安全域名。(曾用名:envQuery、listEnvs、getEnvInfo、getEnvAuthDomains)当 action=list 时,会按 DescribeEnvs 语义做列表/筛选,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知 EnvId 对应环境的详细信息(包括资源字段和计费信息),必须使用 action=info 并传入目标环境的 envId 参数。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。\n\n🔍 action=info 还会派生三个用于后端选型的字段:\n- `EnvInfo.RuntimeMode`:'postgresql' 或 'nosql',表示新业务建议默认使用的后端(PG 已开通时为 postgresql,否则为 nosql)。\n- `EnvInfo.RuntimeBackends`:`{postgresql, nosql, mysql}` 三个布尔值,描述当前环境实际并存的后端。\n- `EnvInfo.RuntimeModeHints`:每个后端对应的 API/工具/skill 提示。\n\n🌐 action=info 还会在不改写 `StaticStorages[].StaticDomain`(云 API 名义域名)的前提下,投影网关路由 Enable 状态:`StaticStorages[].staticDomainRouteEnabled` 与 `EnvInfo.staticDomainRouteEnabled`(与 queryHosting websiteConfig 同源)。`false` 表示默认静态域名根路由已禁用(访问会返回 GATEWAY_ROUTE_DISABLED),勿把名义域名当成可达 URL。\n\nAI 在写业务/权限/存储代码前必须先看这三项:PG 模式下新业务推荐 `app.rdb()` + RLS(`managePgDatabase action=execute` 跑 `CREATE POLICY`)+ pgstore;已存在的 NoSQL 集合 / 旧 storage / `managePermissions(resourceType=\"noSqlDatabase\")` 在 PG 环境下仍然有效。真正不适用的是 MySQL:当 `RuntimeBackends.mysql === false` 时,`manageMysqlDatabase` / `queryMysqlDatabase` / `relational-database-mcp-cloudbase` skill 都不该使用。",
1938
2027
  inputSchema: {
1939
2028
  action: zod_1.z
1940
2029
  .enum(["list", "info", "domains"])
@@ -2463,6 +2552,7 @@ function registerEnvTools(server) {
2463
2552
 
2464
2553
 
2465
2554
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2555
+ exports.assertTcbCloudRunActionAllowed = assertTcbCloudRunActionAllowed;
2466
2556
  exports.removeEmptyStringParams = removeEmptyStringParams;
2467
2557
  exports.buildCapiErrorMessage = buildCapiErrorMessage;
2468
2558
  exports.registerCapiTools = registerCapiTools;
@@ -2474,6 +2564,7 @@ const CLOUDBASE_CONTROL_PLANE_DOC_URL = "https://cloud.tencent.com/document/prod
2474
2564
  const CLOUDBASE_DEPENDENCY_API_DOC_URL = "https://cloud.tencent.com/document/product/876/34808";
2475
2565
  const ALLOWED_SERVICES = [
2476
2566
  "tcb",
2567
+ "tcbr",
2477
2568
  "scf",
2478
2569
  "sts",
2479
2570
  "cam",
@@ -2481,6 +2572,31 @@ const ALLOWED_SERVICES = [
2481
2572
  "cdn",
2482
2573
  "vpc",
2483
2574
  ];
2575
+ /**
2576
+ * tcb 旧版小租户云托管(CloudBase Run)API 族,统一禁用。
2577
+ *
2578
+ * 云托管一律走 tcbr 新逻辑(CreateCloudRunEnv / CreateCloudRunServer)。tcb 的
2579
+ * CreateCloudBaseRunResource 是 2018 旧小租户开通接口:在无大租户记录的环境上调用
2580
+ * 会把服务创建到小租户,产生错误的服务与版本(2026-08-13 用户实测,ATO 调研
2581
+ * efec7cc5 结论)。同族 Describe/Delete 一并拦截,避免 AI 误以为与 tcbr 环境相关。
2582
+ */
2583
+ const TCB_CLOUDRUN_FORBIDDEN_ACTIONS = [
2584
+ "CreateCloudBaseRunResource",
2585
+ "DescribeCloudBaseRunResource",
2586
+ "DeleteCloudBaseRunResource",
2587
+ ];
2588
+ const TCB_CLOUDRUN_FORBIDDEN_HINT = `云托管(CloudBase Run)统一走 tcbr 新逻辑(CreateCloudRunEnv / CreateCloudRunServer),` +
2589
+ `不要使用 tcb 旧小租户接口(${TCB_CLOUDRUN_FORBIDDEN_ACTIONS.join(" / ")})。` +
2590
+ `新环境请先初始化云托管:callCloudApi(service="tcbr", version="2022-02-17", action="CreateCloudRunEnv", params={EnvId:"..."}),` +
2591
+ `再通过 manageCloudRun(action="deploy") 创建服务;查询单个环境基础信息/是否已开通云托管用 callCloudApi(service="tcbr", version="2022-02-17", action="DescribeEnvBaseInfo", params={EnvId:"..."}),查询环境列表/资源信息用 DescribeCloudRunEnvs。`;
2592
+ /**
2593
+ * 拒绝 tcb 旧版小租户云托管 API。命中黑名单时抛出带 tcbr 引导的错误。
2594
+ */
2595
+ function assertTcbCloudRunActionAllowed(service, action) {
2596
+ if (service === "tcb" && TCB_CLOUDRUN_FORBIDDEN_ACTIONS.includes(action)) {
2597
+ throw new Error(`[${service}/${action}] 已禁用:${TCB_CLOUDRUN_FORBIDDEN_HINT}`);
2598
+ }
2599
+ }
2484
2600
  function levenshteinDistance(left, right) {
2485
2601
  const rows = left.length + 1;
2486
2602
  const cols = right.length + 1;
@@ -2556,7 +2672,7 @@ function formatTcbParamsTypeHint(action) {
2556
2672
  return `参数类型参考:\n\`\`\`ts\n${entry.paramsType}\n\`\`\``;
2557
2673
  }
2558
2674
  function buildCapiDocGuidance(service) {
2559
- if (service === "tcb" || service === "lowcode" || service === "scf") {
2675
+ if (service === "tcb" || service === "tcbr" || service === "lowcode" || service === "scf") {
2560
2676
  return `优先查阅 CloudBase API 概览 ${CLOUDBASE_CONTROL_PLANE_DOC_URL} 与云开发依赖资源接口指引 ${CLOUDBASE_DEPENDENCY_API_DOC_URL}。`;
2561
2677
  }
2562
2678
  return `请优先核对对应官方云 API 文档;若你的场景其实是通过 HTTP 协议直接集成 auth/functions/cloudrun/storage/mysqldb 等 CloudBase 业务 API,请优先使用 OpenAPI / Swagger 或 searchKnowledgeBase(mode="openapi"),不要继续猜测管控面 Action。`;
@@ -2646,15 +2762,21 @@ function registerCapiTools(server) {
2646
2762
  **云函数**: \`DescribeFunctions\`、\`CreateFunction\`、\`UpdateFunctionCode\`、\`DeleteFunction\`
2647
2763
  **数据库**: \`CreateMySQLInstance\`、\`DescribeMySQLInstances\`、\`DestroyMySQLInstance\`
2648
2764
 
2765
+ ⚠️ 云托管(CloudBase Run)统一走 tcbr service(CreateCloudRunEnv / CreateCloudRunServer / DescribeEnvBaseInfo / DescribeCloudRunEnvs,version="2022-02-17"),tcb 旧小租户接口 CreateCloudBaseRunResource 等已被禁用;部署请用 manageCloudRun。查询单个环境基础信息/是否已开通云托管用 DescribeEnvBaseInfo(EnvId 必填),查询环境列表及资源信息用 DescribeCloudRunEnvs(EnvId 可选过滤)。
2766
+
2649
2767
  销毁环境时,常见做法是至少带上 \`EnvId\` 和 \`BypassCheck: true\`,如果环境已经处于隔离期再按文档补 \`IsForce: true\`。`,
2650
2768
  inputSchema: {
2651
2769
  service: zod_1.z
2652
2770
  .enum(ALLOWED_SERVICES)
2653
- .describe("选择要访问的服务。可选:tcb、scf、sts、cam、lowcode、cdn、vpc。对于 tcb / scf / lowcode 等 CloudBase 管控面 Action,请优先查官方文档,不要直接猜测 Action。"),
2771
+ .describe("选择要访问的服务。可选:tcb、tcbr、scf、sts、cam、lowcode、cdn、vpc。对于 tcb / scf / lowcode 等 CloudBase 管控面 Action,请优先查官方文档,不要直接猜测 Action。云托管统一走 tcbr(version 需传 2022-02-17)。"),
2654
2772
  action: zod_1.z
2655
2773
  .string()
2656
2774
  .min(1)
2657
- .describe("具体 Action 名称,需符合对应服务的官方 API 定义。若不确定正确 Action,请先查官方文档;不要用近义词或历史命名进行猜测。tcb 常用 Action:环境管理 CreateEnv/ModifyEnv/DescribeEnvs/DestroyEnv、用户管理 CreateUser/ModifyUser/DescribeUserList/DeleteUsers、认证配置 EditAuthConfig、云函数 DescribeFunctions/CreateFunction、数据库 CreateMySQLInstance 等。"),
2775
+ .describe("具体 Action 名称,需符合对应服务的官方 API 定义。若不确定正确 Action,请先查官方文档;不要用近义词或历史命名进行猜测。tcb 常用 Action:环境管理 CreateEnv/ModifyEnv/DescribeEnvs/DestroyEnv、用户管理 CreateUser/ModifyUser/DescribeUserList/DeleteUsers、认证配置 EditAuthConfig、云函数 DescribeFunctions/CreateFunction、数据库 CreateMySQLInstance 等。tcbr 常用 Action:CreateCloudRunEnv(初始化云托管)、DescribeEnvBaseInfo(查询单个环境基础信息,EnvId 必填)、DescribeCloudRunEnvs(查询环境列表/资源信息,EnvId 可选过滤)、CreateCloudRunServer/DescribeCloudRunServers。"),
2776
+ version: zod_1.z
2777
+ .string()
2778
+ .optional()
2779
+ .describe("API 版本(可选)。缺省时按 service 使用 SDK 内置默认版本;tcbr 必须传 \"2022-02-17\"(否则请求缺少 X-TC-Version 会失败)。示例:service=\"tcbr\", version=\"2022-02-17\", action=\"CreateCloudRunEnv\", params={EnvId:\"env-xxx\",PackageType:\"Standard\"}。"),
2658
2780
  params: zod_1.z
2659
2781
  .record(zod_1.z.any())
2660
2782
  .optional()
@@ -2667,10 +2789,11 @@ function registerCapiTools(server) {
2667
2789
  openWorldHint: true,
2668
2790
  category: CATEGORY,
2669
2791
  },
2670
- }, async ({ service, action, params, }) => {
2792
+ }, async ({ service, action, params, version, }) => {
2671
2793
  if (!ALLOWED_SERVICES.includes(service)) {
2672
2794
  throw new Error(`Service ${service} is not allowed. Allowed services: ${ALLOWED_SERVICES.join(", ")}`);
2673
2795
  }
2796
+ assertTcbCloudRunActionAllowed(service, action);
2674
2797
  const cloudbase = await getManager();
2675
2798
  if (['1', 'true'].includes(process.env.CLOUDBASE_EVALUATE_MODE ?? '')) {
2676
2799
  if (service === 'lowcode') {
@@ -2699,7 +2822,7 @@ function registerCapiTools(server) {
2699
2822
  let result;
2700
2823
  try {
2701
2824
  const cleanedParams = params ? removeEmptyStringParams(params) : {};
2702
- result = await cloudbase.commonService(service).call({
2825
+ result = await cloudbase.commonService(service, version).call({
2703
2826
  Action: action,
2704
2827
  Param: cleanedParams,
2705
2828
  });
@@ -2816,7 +2939,7 @@ ${envIdSection}
2816
2939
  ## 环境信息
2817
2940
  - 操作系统: ${os_1.default.type()} ${os_1.default.release()}
2818
2941
  - Node.js版本: ${process.version}
2819
- - MCP 版本:${process.env.npm_package_version || ( true ? "2.25.10" : 0)}
2942
+ - MCP 版本:${process.env.npm_package_version || ( true ? "2.27.0" : 0)}
2820
2943
  - 系统架构: ${os_1.default.arch()}
2821
2944
  - 时间: ${new Date().toISOString()}
2822
2945
  - 请求ID: ${requestId}
@@ -2897,7 +3020,7 @@ function createWrappedHandler(name, handler, server) {
2897
3020
  requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
2898
3021
  ide: server.ide || process.env.INTEGRATION_IDE || ''
2899
3022
  });
2900
- const mcpVersion = true ? "2.25.10" : 0;
3023
+ const mcpVersion = true ? "2.27.0" : 0;
2901
3024
  const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
2902
3025
  const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
2903
3026
  // 创建新的错误对象,保持原有的错误类型但更新消息
@@ -3052,7 +3175,6 @@ const mcp_js_1 = __webpack_require__(4619);
3052
3175
  const databaseNoSQL_js_1 = __webpack_require__(3903);
3053
3176
  const databasePG_js_1 = __webpack_require__(6473);
3054
3177
  const databaseSQL_js_1 = __webpack_require__(9782);
3055
- const download_js_1 = __webpack_require__(9097);
3056
3178
  const env_js_1 = __webpack_require__(622);
3057
3179
  const functions_js_1 = __webpack_require__(5936);
3058
3180
  const hosting_js_1 = __webpack_require__(7279);
@@ -3073,7 +3195,7 @@ const logs_js_1 = __webpack_require__(2322);
3073
3195
  const permissions_js_1 = __webpack_require__(6485);
3074
3196
  const cloud_mode_js_1 = __webpack_require__(9684);
3075
3197
  const logger_js_1 = __webpack_require__(3039);
3076
- const tencent_cloud_js_1 = __webpack_require__(1454);
3198
+ const site_map_js_1 = __webpack_require__(8107);
3077
3199
  const tool_result_js_1 = __webpack_require__(9835);
3078
3200
  const tool_wrapper_js_1 = __webpack_require__(1363);
3079
3201
  // 默认插件列表
@@ -3095,13 +3217,15 @@ const DEFAULT_PLUGINS = [
3095
3217
  "permissions",
3096
3218
  "logs",
3097
3219
  "agents",
3098
- "download",
3099
3220
  "capi",
3100
3221
  ];
3101
3222
  function registerDatabase(server) {
3102
- // Skip NoSQL database tools for international region (Singapore) as it doesn't support NoSQL DB
3103
- const region = server.cloudBaseOptions?.region || process.env.TCB_REGION;
3104
- if (!(0, tencent_cloud_js_1.isInternationalRegion)(region)) {
3223
+ // NoSQL 数据库按站点能力集合注册(国际站默认不支持 NoSQL)
3224
+ const { site } = (0, site_map_js_1.resolveSiteAndRegion)({
3225
+ site: server.cloudBaseOptions?.site,
3226
+ region: server.cloudBaseOptions?.region,
3227
+ });
3228
+ if (site_map_js_1.SITE_REGION_MAP[site].capabilities.noSql) {
3105
3229
  (0, databaseNoSQL_js_1.registerDatabaseTools)(server);
3106
3230
  }
3107
3231
  (0, dataModel_js_1.registerDataModelTools)(server);
@@ -3110,8 +3234,11 @@ function registerMysqlDatabase(server) {
3110
3234
  (0, databaseSQL_js_1.registerSQLDatabaseTools)(server);
3111
3235
  }
3112
3236
  function registerNoSQLDatabase(server) {
3113
- const region = server.cloudBaseOptions?.region || process.env.TCB_REGION;
3114
- if (!(0, tencent_cloud_js_1.isInternationalRegion)(region)) {
3237
+ const { site } = (0, site_map_js_1.resolveSiteAndRegion)({
3238
+ site: server.cloudBaseOptions?.site,
3239
+ region: server.cloudBaseOptions?.region,
3240
+ });
3241
+ if (site_map_js_1.SITE_REGION_MAP[site].capabilities.noSql) {
3115
3242
  (0, databaseNoSQL_js_1.registerDatabaseTools)(server);
3116
3243
  }
3117
3244
  }
@@ -3130,7 +3257,6 @@ const AVAILABLE_PLUGINS = {
3130
3257
  storage: { name: "storage", register: storage_js_1.registerStorageTools },
3131
3258
  setup: { name: "setup", register: setup_js_1.registerSetupTools },
3132
3259
  rag: { name: "rag", register: rag_js_1.registerRagTools },
3133
- download: { name: "download", register: download_js_1.registerDownloadTools },
3134
3260
  gateway: { name: "gateway", register: gateway_js_1.registerGatewayTools },
3135
3261
  "app-auth": { name: "app-auth", register: app_auth_js_1.registerAppAuthTools },
3136
3262
  permissions: { name: "permissions", register: permissions_js_1.registerPermissionTools },
@@ -3317,26 +3443,6 @@ Object.defineProperty(exports, "reportToolkitLifecycle", ({ enumerable: true, ge
3317
3443
  Object.defineProperty(exports, "telemetryReporter", ({ enumerable: true, get: function () { return telemetry_js_1.telemetryReporter; } }));
3318
3444
 
3319
3445
 
3320
- /***/ }),
3321
-
3322
- /***/ 1454:
3323
- /***/ ((__unused_webpack_module, exports) => {
3324
-
3325
-
3326
- Object.defineProperty(exports, "__esModule", ({ value: true }));
3327
- exports.isInternationalRegion = void 0;
3328
- exports.isValidRegion = isValidRegion;
3329
- const REGION = {
3330
- SHANGHAI: 'ap-shanghai',
3331
- SINGAPORE: 'ap-singapore',
3332
- };
3333
- const isInternationalRegion = (region) => region === REGION.SINGAPORE;
3334
- exports.isInternationalRegion = isInternationalRegion;
3335
- function isValidRegion(region) {
3336
- return Object.values(REGION).includes(region);
3337
- }
3338
-
3339
-
3340
3446
  /***/ }),
3341
3447
 
3342
3448
  /***/ 1622:
@@ -3499,12 +3605,19 @@ module.exports = __WEBPACK_EXTERNAL_MODULE__modelcontextprotocol_sdk_types_js_70
3499
3605
 
3500
3606
 
3501
3607
  Object.defineProperty(exports, "__esModule", ({ value: true }));
3608
+ exports.isGatewayRouteEnabled = isGatewayRouteEnabled;
3609
+ exports.isDomainPathReachableViaGateway = isDomainPathReachableViaGateway;
3502
3610
  exports.rankGatewayAccessUrls = rankGatewayAccessUrls;
3503
3611
  exports.flattenHttpServiceRoutes = flattenHttpServiceRoutes;
3504
3612
  exports.filterRoutesByUpstream = filterRoutesByUpstream;
3505
3613
  exports.toAccessUrlEnvelope = toAccessUrlEnvelope;
3506
3614
  exports.resolveGatewayAccessUrls = resolveGatewayAccessUrls;
3615
+ exports.resolveAllGatewayRoutes = resolveAllGatewayRoutes;
3507
3616
  exports.preferGatewayOrFallback = preferGatewayOrFallback;
3617
+ /** Route is reachable unless Enable is explicitly false. */
3618
+ function isGatewayRouteEnabled(route) {
3619
+ return route.Enable !== false;
3620
+ }
3508
3621
  function normalizeAccessPath(path) {
3509
3622
  if (!path) {
3510
3623
  return "/";
@@ -3514,13 +3627,63 @@ function normalizeAccessPath(path) {
3514
3627
  function buildHttpsUrl(domain, path) {
3515
3628
  return `https://${domain}${normalizeAccessPath(path)}`;
3516
3629
  }
3630
+ function extractHostname(urlOrHost) {
3631
+ const trimmed = urlOrHost.trim();
3632
+ if (!trimmed) {
3633
+ return "";
3634
+ }
3635
+ try {
3636
+ if (trimmed.includes("://")) {
3637
+ return new URL(trimmed).hostname.toLowerCase();
3638
+ }
3639
+ }
3640
+ catch {
3641
+ // Fall through to treat as bare hostname.
3642
+ }
3643
+ return trimmed.replace(/\/.*$/, "").toLowerCase();
3644
+ }
3645
+ /**
3646
+ * Whether a hostname has an enabled covering gateway route for the given path.
3647
+ * Longest matching Path prefix wins. Missing route info → unknown (null).
3648
+ */
3649
+ function isDomainPathReachableViaGateway(routes, domainOrUrl, requestPath) {
3650
+ const hostname = extractHostname(domainOrUrl);
3651
+ if (!hostname) {
3652
+ return null;
3653
+ }
3654
+ const normalizedRequest = normalizeAccessPath(requestPath);
3655
+ const domainRoutes = routes.filter((route) => Boolean(route.Domain) &&
3656
+ route.Domain.toLowerCase() === hostname);
3657
+ if (domainRoutes.length === 0) {
3658
+ return null;
3659
+ }
3660
+ const covering = domainRoutes
3661
+ .map((route) => ({
3662
+ route,
3663
+ path: normalizeAccessPath(route.Path),
3664
+ }))
3665
+ .filter(({ path }) => {
3666
+ if (path === "/") {
3667
+ return true;
3668
+ }
3669
+ return (normalizedRequest === path ||
3670
+ normalizedRequest.startsWith(path.endsWith("/") ? path : `${path}/`));
3671
+ })
3672
+ .sort((a, b) => b.path.length - a.path.length);
3673
+ if (covering.length === 0) {
3674
+ return null;
3675
+ }
3676
+ return isGatewayRouteEnabled(covering[0].route);
3677
+ }
3517
3678
  /**
3518
3679
  * Rank gateway route URLs: custom domains (IsDefault !== true) before default domain.
3519
- * Stable within each group; dedupe by full URL.
3680
+ * By default skips Enable === false routes. Stable within each group; dedupe by full URL.
3520
3681
  */
3521
- function rankGatewayAccessUrls(routes) {
3682
+ function rankGatewayAccessUrls(routes, options) {
3683
+ const includeDisabled = options?.includeDisabled === true;
3522
3684
  const mapped = routes
3523
3685
  .filter((route) => Boolean(route.Domain))
3686
+ .filter((route) => includeDisabled || isGatewayRouteEnabled(route))
3524
3687
  .map((route) => {
3525
3688
  const path = normalizeAccessPath(route.Path);
3526
3689
  const isDefault = route.IsDefault === true;
@@ -3529,6 +3692,7 @@ function rankGatewayAccessUrls(routes) {
3529
3692
  domain: route.Domain,
3530
3693
  path,
3531
3694
  isDefault,
3695
+ enabled: isGatewayRouteEnabled(route),
3532
3696
  source: (isDefault
3533
3697
  ? "gateway.default"
3534
3698
  : "gateway.custom"),
@@ -3556,6 +3720,7 @@ function flattenHttpServiceRoutes(result) {
3556
3720
  Domain: domainItem.Domain ?? "",
3557
3721
  IsDefault: domainItem.IsDefault,
3558
3722
  Path: typeof route.Path === "string" ? route.Path : undefined,
3723
+ Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
3559
3724
  UpstreamResourceType: typeof route.UpstreamResourceType === "string"
3560
3725
  ? route.UpstreamResourceType
3561
3726
  : undefined,
@@ -3578,64 +3743,116 @@ function filterRoutesByUpstream(routes, input) {
3578
3743
  types.includes(String(route.UpstreamResourceType)));
3579
3744
  });
3580
3745
  }
3581
- function toAccessUrlEnvelope(ranked) {
3582
- if (ranked.length === 0) {
3583
- return { accessUrls: [], routes: [] };
3746
+ function toAccessUrlEnvelope(ranked, disabledAccessUrls = []) {
3747
+ const enabled = ranked.filter((item) => item.enabled);
3748
+ if (enabled.length === 0) {
3749
+ return {
3750
+ accessUrls: [],
3751
+ routes: [],
3752
+ disabledAccessUrls,
3753
+ };
3584
3754
  }
3585
3755
  return {
3586
- accessUrl: ranked[0].url,
3587
- accessUrls: ranked.map((item) => item.url),
3588
- accessUrlSource: ranked[0].source,
3589
- routes: ranked,
3756
+ accessUrl: enabled[0].url,
3757
+ accessUrls: enabled.map((item) => item.url),
3758
+ accessUrlSource: enabled[0].source,
3759
+ routes: enabled,
3760
+ disabledAccessUrls,
3590
3761
  };
3591
3762
  }
3763
+ async function loadHttpServiceRoutes(getManager, envId) {
3764
+ const manager = await getManager();
3765
+ const result = await manager.env.describeHttpServiceRoute({
3766
+ EnvId: envId,
3767
+ Limit: 1000,
3768
+ });
3769
+ return flattenHttpServiceRoutes(result);
3770
+ }
3592
3771
  /**
3593
3772
  * Best-effort lookup of gateway access URLs for an upstream resource.
3594
3773
  * Never throws — returns empty lists on failure.
3774
+ * Disabled routes (Enable === false) are excluded from accessUrl(s).
3595
3775
  */
3596
3776
  async function resolveGatewayAccessUrls(input) {
3597
3777
  try {
3598
- const manager = await input.getManager();
3599
- const result = await manager.env.describeHttpServiceRoute({
3600
- EnvId: input.envId,
3601
- Limit: 1000,
3602
- });
3603
- const flattened = flattenHttpServiceRoutes(result);
3778
+ const flattened = await loadHttpServiceRoutes(input.getManager, input.envId);
3604
3779
  const matched = filterRoutesByUpstream(flattened, {
3605
3780
  upstreamResourceName: input.upstreamResourceName,
3606
3781
  upstreamResourceTypes: input.upstreamResourceTypes,
3607
3782
  });
3608
- return toAccessUrlEnvelope(rankGatewayAccessUrls(matched));
3783
+ const disabledAccessUrls = rankGatewayAccessUrls(matched, {
3784
+ includeDisabled: true,
3785
+ })
3786
+ .filter((item) => !item.enabled)
3787
+ .map((item) => item.url);
3788
+ return toAccessUrlEnvelope(rankGatewayAccessUrls(matched), disabledAccessUrls);
3789
+ }
3790
+ catch {
3791
+ return { accessUrls: [], routes: [], disabledAccessUrls: [] };
3792
+ }
3793
+ }
3794
+ /**
3795
+ * Load all flattened HTTP service routes (best-effort). Used by hosting to
3796
+ * judge whether the static-domain fallback itself is gateway-disabled.
3797
+ */
3798
+ async function resolveAllGatewayRoutes(input) {
3799
+ try {
3800
+ return await loadHttpServiceRoutes(input.getManager, input.envId);
3609
3801
  }
3610
3802
  catch {
3611
- return { accessUrls: [], routes: [] };
3803
+ return [];
3612
3804
  }
3613
3805
  }
3614
3806
  /**
3615
3807
  * Merge ranked gateway URLs with a resource-native fallback URL.
3616
3808
  * Gateway custom/default always win over the fallback when present.
3809
+ * Unreachable fallbacks are omitted from accessUrl(s) and listed under
3810
+ * disabledAccessUrls instead.
3617
3811
  */
3618
3812
  function preferGatewayOrFallback(input) {
3813
+ const disabledAccessUrls = [
3814
+ ...(input.gateway.disabledAccessUrls ?? []),
3815
+ ];
3816
+ const fallbackReachable = input.fallbackReachable !== false;
3619
3817
  if (input.gateway.accessUrl) {
3620
3818
  const urls = [...input.gateway.accessUrls];
3621
3819
  if (input.fallbackUrl &&
3820
+ fallbackReachable &&
3622
3821
  !urls.includes(input.fallbackUrl)) {
3623
3822
  urls.push(input.fallbackUrl);
3624
3823
  }
3824
+ else if (input.fallbackUrl &&
3825
+ !fallbackReachable &&
3826
+ !disabledAccessUrls.includes(input.fallbackUrl)) {
3827
+ disabledAccessUrls.push(input.fallbackUrl);
3828
+ }
3625
3829
  return {
3626
3830
  accessUrl: input.gateway.accessUrl,
3627
3831
  accessUrls: urls,
3628
3832
  accessUrlSource: input.gateway.accessUrlSource,
3833
+ accessUrlReachable: true,
3834
+ disabledAccessUrls,
3629
3835
  };
3630
3836
  }
3631
- if (input.fallbackUrl) {
3837
+ if (input.fallbackUrl && fallbackReachable) {
3632
3838
  return {
3633
3839
  accessUrl: input.fallbackUrl,
3634
3840
  accessUrls: [input.fallbackUrl],
3635
3841
  accessUrlSource: input.fallbackSource,
3842
+ accessUrlReachable: true,
3843
+ disabledAccessUrls,
3636
3844
  };
3637
3845
  }
3638
- return { accessUrls: [] };
3846
+ if (input.fallbackUrl && !fallbackReachable) {
3847
+ if (!disabledAccessUrls.includes(input.fallbackUrl)) {
3848
+ disabledAccessUrls.push(input.fallbackUrl);
3849
+ }
3850
+ }
3851
+ return {
3852
+ accessUrls: [],
3853
+ accessUrlReachable: false,
3854
+ disabledAccessUrls,
3855
+ };
3639
3856
  }
3640
3857
 
3641
3858
 
@@ -4676,6 +4893,7 @@ const manager_node_1 = __importDefault(__webpack_require__(6665));
4676
4893
  const auth_js_1 = __webpack_require__(7291);
4677
4894
  const logger_js_1 = __webpack_require__(3039);
4678
4895
  const tool_result_js_1 = __webpack_require__(9835);
4896
+ const site_map_js_1 = __webpack_require__(8107);
4679
4897
  // Timeout for envId auto-resolution flow.
4680
4898
  // 10 minutes (600 seconds) - matches InteractiveServer timeout
4681
4899
  const ENV_ID_TIMEOUT = 600000;
@@ -4700,7 +4918,7 @@ function createManagerFromLoginState(loginState, region) {
4700
4918
  envId: loginState.envId,
4701
4919
  token: loginState.token,
4702
4920
  proxy: process.env.http_proxy,
4703
- region: region ?? process.env.TCB_REGION ?? 'ap-shanghai',
4921
+ region: region ?? (0, site_map_js_1.resolveSiteAndRegion)().region,
4704
4922
  });
4705
4923
  }
4706
4924
  async function listAvailableEnvCandidates(options) {
@@ -4728,7 +4946,7 @@ async function listAvailableEnvCandidates(options) {
4728
4946
  if (!loginState?.secretId || !loginState?.secretKey) {
4729
4947
  return [];
4730
4948
  }
4731
- const region = cloudBaseOptions?.region ?? process.env.TCB_REGION ?? 'ap-shanghai';
4949
+ const region = (0, site_map_js_1.resolveSiteAndRegion)(cloudBaseOptions ?? {}).region;
4732
4950
  cloudbase = createManagerFromLoginState(loginState, region);
4733
4951
  }
4734
4952
  try {
@@ -4929,7 +5147,10 @@ function getCachedEnvId() {
4929
5147
  const databaseInstanceIdCache = new Map();
4930
5148
  function buildDatabaseInstanceIdCacheKey(options) {
4931
5149
  const envId = options?.envId ?? options?.cloudBaseOptions?.envId ?? process.env.CLOUDBASE_ENV_ID ?? 'unknown';
4932
- const region = options?.region ?? options?.cloudBaseOptions?.region ?? process.env.TCB_REGION ?? 'ap-shanghai';
5150
+ const region = (0, site_map_js_1.resolveSiteAndRegion)({
5151
+ site: options?.cloudBaseOptions?.site,
5152
+ region: options?.region ?? options?.cloudBaseOptions?.region,
5153
+ }).region;
4933
5154
  return `${region}:${envId}`;
4934
5155
  }
4935
5156
  function resetDatabaseInstanceIdCache() {
@@ -5035,11 +5256,11 @@ async function getCloudBaseManager(options = {}) {
5035
5256
  });
5036
5257
  }
5037
5258
  try {
5038
- // Region priority: explicit option > env var > ap-shanghai default
5039
- const fallbackRegion = cloudBaseOptions?.region ?? process.env.TCB_REGION ?? 'ap-shanghai';
5259
+ // Region priority: explicit option > TCB_REGION/TCB_SITE env > .cloudbase/project.json > ap-shanghai default
5260
+ const { region: fallbackRegion, site } = (0, site_map_js_1.resolveSiteAndRegion)(cloudBaseOptions ?? {});
5040
5261
  const loginState = authStrategy === 'ensure'
5041
- ? await (0, auth_js_1.getLoginState)({ region: fallbackRegion })
5042
- : await (0, auth_js_1.peekLoginState)();
5262
+ ? await (0, auth_js_1.getLoginState)({ region: fallbackRegion, site })
5263
+ : await (0, auth_js_1.peekLoginState)({ region: fallbackRegion, site });
5043
5264
  if (!loginState) {
5044
5265
  const authState = await (0, auth_js_1.getAuthProgressState)();
5045
5266
  if (authState.status === 'PENDING') {
@@ -5087,7 +5308,7 @@ async function getCloudBaseManager(options = {}) {
5087
5308
  }
5088
5309
  // envId priority: explicit option > envManager cache > loginState.envId
5089
5310
  const resolvedEnvId = finalEnvId || loginEnvId;
5090
- // region 直接使用 fallbackRegion(来自 cloudBaseOptions?.region ?? TCB_REGION ?? 'ap-shanghai')
5311
+ // region 直接使用 fallbackRegion(resolveSiteAndRegion 统一解析)
5091
5312
  // 不再通过 DescribeEnvs 查询 region,以兼容 STS 临时密钥场景
5092
5313
  const region = fallbackRegion;
5093
5314
  const manager = new manager_node_1.default({
@@ -5119,8 +5340,8 @@ function createCloudBaseManagerWithOptions(cloudBaseOptions) {
5119
5340
  hasSecretKey: !!cloudBaseOptions.secretKey,
5120
5341
  hasToken: !!cloudBaseOptions.token,
5121
5342
  });
5122
- // Region priority: explicit option > env var > ap-shanghai default
5123
- const region = cloudBaseOptions.region ?? process.env.TCB_REGION ?? 'ap-shanghai';
5343
+ // Region priority: explicit option > TCB_REGION/TCB_SITE env > .cloudbase/project.json > ap-shanghai default
5344
+ const region = (0, site_map_js_1.resolveSiteAndRegion)(cloudBaseOptions).region;
5124
5345
  const manager = new manager_node_1.default({
5125
5346
  ...cloudBaseOptions,
5126
5347
  proxy: cloudBaseOptions.proxy || process.env.http_proxy,
@@ -7892,6 +8113,8 @@ const MANAGE_GATEWAY_ACTIONS = [
7892
8113
  "createRoute",
7893
8114
  "updateRoute",
7894
8115
  "deleteRoute",
8116
+ "enableRoute",
8117
+ "disableRoute",
7895
8118
  "bindCustomDomain",
7896
8119
  "deleteCustomDomain",
7897
8120
  "enableService",
@@ -7964,6 +8187,7 @@ function registerGatewayTools(server) {
7964
8187
  Domain: route.Domain,
7965
8188
  Path: route.Path,
7966
8189
  IsDefault: route.IsDefault,
8190
+ Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
7967
8191
  UpstreamResourceType: route.UpstreamResourceType,
7968
8192
  UpstreamResourceName: route.UpstreamResourceName,
7969
8193
  }));
@@ -7974,16 +8198,26 @@ function registerGatewayTools(server) {
7974
8198
  Domain: route.Domain,
7975
8199
  Path: route.Path,
7976
8200
  IsDefault: route.IsDefault,
8201
+ Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
7977
8202
  UpstreamResourceType: route.UpstreamResourceType,
7978
8203
  UpstreamResourceName: route.UpstreamResourceName,
7979
8204
  }));
7980
- const envelope = (0, gateway_access_urls_js_1.toAccessUrlEnvelope)((0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates));
8205
+ const disabledAccessUrls = (0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates, {
8206
+ includeDisabled: true,
8207
+ })
8208
+ .filter((item) => !item.enabled)
8209
+ .map((item) => item.url);
8210
+ const envelope = (0, gateway_access_urls_js_1.toAccessUrlEnvelope)((0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates), disabledAccessUrls);
7981
8211
  return {
7982
8212
  ...(envelope.accessUrl ? { accessUrl: envelope.accessUrl } : {}),
7983
8213
  accessUrls: envelope.accessUrls,
7984
8214
  ...(envelope.accessUrlSource
7985
8215
  ? { accessUrlSource: envelope.accessUrlSource }
7986
8216
  : {}),
8217
+ accessUrlReachable: Boolean(envelope.accessUrl),
8218
+ ...(disabledAccessUrls.length > 0
8219
+ ? { disabledAccessUrls, routeDisabled: true }
8220
+ : {}),
7987
8221
  };
7988
8222
  };
7989
8223
  const pickUsableDefaultDomain = (domains) => {
@@ -8073,6 +8307,15 @@ function registerGatewayTools(server) {
8073
8307
  : input.enablePathTransmission !== undefined
8074
8308
  ? input.enablePathTransmission
8075
8309
  : undefined;
8310
+ // Route enable: prefer route.enable. For updateRoute only, top-level
8311
+ // enable also maps to Route.Enable (enableService/authSwitch use the
8312
+ // same field name but different actions).
8313
+ const routeEnable = input.route?.enable !== undefined
8314
+ ? input.route.enable
8315
+ : input.action === "updateRoute" &&
8316
+ typeof input.enable === "boolean"
8317
+ ? input.enable
8318
+ : undefined;
8076
8319
  const route = {
8077
8320
  Path: path,
8078
8321
  UpstreamResourceType: upstreamResourceType,
@@ -8082,6 +8325,9 @@ function registerGatewayTools(server) {
8082
8325
  if (enablePathTransmission !== undefined) {
8083
8326
  route.EnablePathTransmission = enablePathTransmission;
8084
8327
  }
8328
+ if (routeEnable !== undefined) {
8329
+ route.Enable = routeEnable;
8330
+ }
8085
8331
  return {
8086
8332
  EnvId: await resolveEnvId(),
8087
8333
  Domain: {
@@ -8095,9 +8341,46 @@ function registerGatewayTools(server) {
8095
8341
  upstreamResourceName,
8096
8342
  enableAuth,
8097
8343
  enablePathTransmission,
8344
+ enable: routeEnable,
8098
8345
  },
8099
8346
  };
8100
8347
  };
8348
+ const resolveExistingRouteForToggle = async (input) => {
8349
+ const routePath = input.routePath ?? input.path;
8350
+ if (!routePath) {
8351
+ throw new Error("action=enableRoute/disableRoute 时必须提供 path 或 route.path(例如 \"/\" 或 \"/api\")");
8352
+ }
8353
+ const normalizedPath = normalizeAccessPath(routePath);
8354
+ const preferredDomain = input.domain;
8355
+ const result = await listHttpServiceRoutes(preferredDomain ? { domain: preferredDomain } : undefined);
8356
+ const upstreamName = input.routeServiceName ?? input.targetName;
8357
+ const matches = flattenRoutes(result).filter((item) => {
8358
+ if (preferredDomain &&
8359
+ item.Domain !== preferredDomain) {
8360
+ return false;
8361
+ }
8362
+ if (normalizeAccessPath(String(item.Path ?? "")) !== normalizedPath) {
8363
+ return false;
8364
+ }
8365
+ if (upstreamName && item.UpstreamResourceName !== upstreamName) {
8366
+ return false;
8367
+ }
8368
+ return true;
8369
+ });
8370
+ if (matches.length === 0) {
8371
+ throw new Error(`未找到路径 ${normalizedPath} 的路由` +
8372
+ (preferredDomain ? `(域名 ${preferredDomain})` : "") +
8373
+ (upstreamName ? `(上游 ${upstreamName})` : "") +
8374
+ "。请先用 queryGateway(action=\"listRoutes\") 确认 Domain / Path,再调用 enableRoute/disableRoute。" +
8375
+ "关闭静态托管默认域名(*.tcloudbaseapp.com)时,请显式传 domain=该 STATIC_STORE IsDefault 域名,并通常 path=\"/\"。");
8376
+ }
8377
+ if (matches.length > 1) {
8378
+ const domains = [...new Set(matches.map((item) => item.Domain))];
8379
+ throw new Error(`路径 ${normalizedPath} 匹配到 ${matches.length} 条路由(域名:${domains.join(", ")})。` +
8380
+ "请补充 domain(必要时再加 targetName/route.serviceName)精确定位后再 enableRoute/disableRoute。");
8381
+ }
8382
+ return matches[0];
8383
+ };
8101
8384
  const routeMutationNextActions = (targetName) => [
8102
8385
  {
8103
8386
  tool: "queryGateway",
@@ -8301,6 +8584,10 @@ function registerGatewayTools(server) {
8301
8584
  Domain: payload.Domain,
8302
8585
  });
8303
8586
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
8587
+ const routeEnabled = payload.resolved.enable !== false;
8588
+ const accessUrl = routeEnabled
8589
+ ? `https://${payload.resolved.domain}${payload.resolved.path}`
8590
+ : undefined;
8304
8591
  return buildEnvelope({
8305
8592
  action: input.action,
8306
8593
  model: "httpServiceRoute",
@@ -8310,13 +8597,31 @@ function registerGatewayTools(server) {
8310
8597
  upstreamResourceName: payload.resolved.upstreamResourceName,
8311
8598
  auth: payload.resolved.enableAuth ?? null,
8312
8599
  enablePathTransmission: payload.resolved.enablePathTransmission ?? null,
8313
- accessUrl: `https://${payload.resolved.domain}${payload.resolved.path}`,
8314
- accessUrls: [`https://${payload.resolved.domain}${payload.resolved.path}`],
8600
+ enable: payload.resolved.enable ?? null,
8601
+ ...(accessUrl
8602
+ ? {
8603
+ accessUrl,
8604
+ accessUrls: [accessUrl],
8605
+ accessUrlReachable: true,
8606
+ }
8607
+ : {
8608
+ accessUrls: [],
8609
+ accessUrlReachable: false,
8610
+ routeDisabled: true,
8611
+ disabledAccessUrls: [
8612
+ `https://${payload.resolved.domain}${payload.resolved.path}`,
8613
+ ],
8614
+ }),
8315
8615
  accessUrlSource: input.domain && input.domain === payload.resolved.domain
8316
8616
  ? "gateway.custom"
8317
8617
  : "gateway.default",
8318
8618
  raw: result,
8319
8619
  }, `HTTP 路由更新成功(${payload.resolved.domain}${payload.resolved.path}` +
8620
+ (payload.resolved.enable === false
8621
+ ? ",路由已禁用 Enable=false"
8622
+ : payload.resolved.enable === true
8623
+ ? ",路由已启用 Enable=true"
8624
+ : "") +
8320
8625
  (payload.resolved.enablePathTransmission === true
8321
8626
  ? ",路径透传=开启"
8322
8627
  : payload.resolved.enablePathTransmission === false
@@ -8324,6 +8629,102 @@ function registerGatewayTools(server) {
8324
8629
  : "") +
8325
8630
  `)`, routeMutationNextActions(payload.resolved.upstreamResourceName));
8326
8631
  }
8632
+ case "enableRoute":
8633
+ case "disableRoute": {
8634
+ const enable = input.action === "enableRoute";
8635
+ const existing = await resolveExistingRouteForToggle({
8636
+ path: input.path,
8637
+ routePath: input.route?.path,
8638
+ domain: input.domain,
8639
+ targetName: input.targetName,
8640
+ routeServiceName: input.route?.serviceName,
8641
+ });
8642
+ const upstreamResourceType = resolveUpstreamResourceType({
8643
+ upstreamResourceType: input.upstreamResourceType ??
8644
+ existing.UpstreamResourceType,
8645
+ routeUpstreamResourceType: input.route?.upstreamResourceType,
8646
+ });
8647
+ const upstreamResourceName = input.route?.serviceName ??
8648
+ input.targetName ??
8649
+ String(existing.UpstreamResourceName ?? "");
8650
+ if (!upstreamResourceName) {
8651
+ throw new Error(`action=${input.action} 无法解析上游资源名;请用 queryGateway(listRoutes) 确认后传入 targetName 或 route.serviceName。`);
8652
+ }
8653
+ const normalizedPath = normalizeAccessPath(String(existing.Path ?? input.route?.path ?? input.path ?? "/"));
8654
+ const domain = existing.Domain;
8655
+ const enableAuth = input.route?.auth !== undefined
8656
+ ? input.route.auth
8657
+ : input.auth !== undefined
8658
+ ? input.auth
8659
+ : existing.EnableAuth;
8660
+ const enablePathTransmission = input.route?.enablePathTransmission !== undefined
8661
+ ? input.route.enablePathTransmission
8662
+ : input.enablePathTransmission !== undefined
8663
+ ? input.enablePathTransmission
8664
+ : existing.EnablePathTransmission;
8665
+ const route = {
8666
+ Path: normalizedPath,
8667
+ UpstreamResourceType: upstreamResourceType,
8668
+ UpstreamResourceName: upstreamResourceName,
8669
+ Enable: enable,
8670
+ };
8671
+ if (enableAuth !== undefined) {
8672
+ route.EnableAuth = enableAuth;
8673
+ }
8674
+ if (enablePathTransmission !== undefined) {
8675
+ route.EnablePathTransmission = enablePathTransmission;
8676
+ }
8677
+ const cloudbase = await getManager();
8678
+ const result = await cloudbase.env.modifyHttpServiceRoute({
8679
+ EnvId: await resolveEnvId(),
8680
+ Domain: {
8681
+ Domain: domain,
8682
+ Routes: [route],
8683
+ },
8684
+ });
8685
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
8686
+ const verb = enable ? "启用" : "禁用";
8687
+ const accessUrl = enable
8688
+ ? `https://${domain}${normalizedPath}`
8689
+ : undefined;
8690
+ return buildEnvelope({
8691
+ action: input.action,
8692
+ model: "httpServiceRoute",
8693
+ domain,
8694
+ path: normalizedPath,
8695
+ upstreamResourceType,
8696
+ upstreamResourceName,
8697
+ enable,
8698
+ auth: enableAuth ?? null,
8699
+ enablePathTransmission: enablePathTransmission ?? null,
8700
+ ...(accessUrl
8701
+ ? {
8702
+ accessUrl,
8703
+ accessUrls: [accessUrl],
8704
+ accessUrlReachable: true,
8705
+ }
8706
+ : {
8707
+ accessUrls: [],
8708
+ accessUrlReachable: false,
8709
+ routeDisabled: true,
8710
+ disabledAccessUrls: [`https://${domain}${normalizedPath}`],
8711
+ }),
8712
+ accessUrlSource: existing.IsDefault
8713
+ ? "gateway.default"
8714
+ : "gateway.custom",
8715
+ raw: result,
8716
+ }, `HTTP 路由已${verb}(${domain}${normalizedPath},Enable=${enable})。` +
8717
+ (enable
8718
+ ? "启用后通常数秒到约 30 秒内可访问;请用 queryGateway(getRoute) 或探测 accessUrl 确认。"
8719
+ : "禁用后该路径将不可公网访问(GATEWAY_ROUTE_DISABLED);关闭静态托管默认域名(*.tcloudbaseapp.com)时,请确认 DomainType=STATIC_STORE 且 IsDefault=true。") +
8720
+ " 底层对应 tcb ModifyHTTPServiceRoute(不是 ModifyGatewayRoute)。", [
8721
+ {
8722
+ tool: "queryGateway",
8723
+ action: "getRoute",
8724
+ reason: `复核路由 Enable=${enable} 是否已生效`,
8725
+ },
8726
+ ]);
8727
+ }
8327
8728
  case "deleteRoute": {
8328
8729
  const routePath = input.route?.path ?? input.path;
8329
8730
  if (!routePath) {
@@ -8531,8 +8932,11 @@ function registerGatewayTools(server) {
8531
8932
  }, async (input) => withEnvelope(() => handleQueryGateway(input)));
8532
8933
  server.registerTool?.("manageGateway", {
8533
8934
  title: "管理 CloudBase 网关",
8534
- description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;未传 domain 时用 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(形如 *.{region}.app.tcloudbase.com),不会使用静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE)。" +
8935
+ description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;" +
8936
+ "enableRoute/disableRoute 启用或禁用已有路由(底层 ModifyHTTPServiceRoute 的 Routes[].Enable,不是 ModifyGatewayRoute)。" +
8937
+ "未传 domain 时用 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(形如 *.{region}.app.tcloudbase.com),不会使用静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE)。" +
8535
8938
  "这是网关默认域上的路径路由,不是 STATIC_STORE 上游绑定;STATIC_STORE 上游必须显式传 upstreamResourceType=STATIC_STORE。" +
8939
+ "关闭静态托管默认域名(*.tcloudbaseapp.com):先 queryGateway(listRoutes) 找到 DomainType=STATIC_STORE 且 IsDefault=true 的 domain,再 manageGateway(action=\"disableRoute\", domain=该域名, path=\"/\");勿用 manageHosting。" +
8536
8940
  "创建后可用 queryGateway(action=\"listRoutes\") 核对 Domain / DomainType / Path / UpstreamResourceType。" +
8537
8941
  "上游类型只用一个参数 upstreamResourceType(也可写在 route.upstreamResourceType,route 优先):" +
8538
8942
  "WEB_SCF=HTTP云函数,SCF=Event云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器;" +
@@ -8545,9 +8949,12 @@ function registerGatewayTools(server) {
8545
8949
  inputSchema: {
8546
8950
  action: zod_1.z
8547
8951
  .enum(MANAGE_GATEWAY_ACTIONS)
8548
- .describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;bindCustomDomain/deleteCustomDomain 管理自定义域名;" +
8952
+ .describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;enableRoute/disableRoute 启用或禁用已有路由(需 path,建议显式传 domain);" +
8953
+ "bindCustomDomain/deleteCustomDomain 管理自定义域名;" +
8549
8954
  "enableService/authSwitch 开关 HTTP 网关总开关与访问鉴权(需配合 enable 参数)。" +
8550
- "createRoute/updateRoute 必须提供 upstreamResourceType。" +
8955
+ "createRoute/updateRoute 必须提供 upstreamResourceType;enableRoute/disableRoute 会先 listRoutes 定位已有路由,通常不必重填上游。" +
8956
+ "updateRoute 也可传 enable/route.enable 直接改 Routes[].Enable。" +
8957
+ "关闭 *.tcloudbaseapp.com 默认静态托管域:disableRoute + domain=该 STATIC_STORE IsDefault 域名 + path=\"/\"。" +
8551
8958
  "已有自定义域名时优先 createRoute(domain=已有域名) 实现访问,不必再次 bindCustomDomain / 传入 certificateId;" +
8552
8959
  "bindCustomDomain 仅用于首次绑定新域名(需 certificateId;可选 accessType=DIRECT|CDN|CUSTOM,CUSTOM 需 customCname;普通场景用默认 DIRECT)。" +
8553
8960
  "接入说明:https://docs.cloudbase.net/service/custom-domain"),
@@ -8596,16 +9003,25 @@ function registerGatewayTools(server) {
8596
9003
  .boolean()
8597
9004
  .optional()
8598
9005
  .describe("同顶层 enablePathTransmission。route 内设置时优先于顶层。"),
9006
+ enable: zod_1.z
9007
+ .boolean()
9008
+ .optional()
9009
+ .describe("路由级开关(Routes[].Enable / Route.Enable)。createRoute/updateRoute 可用:" +
9010
+ "enable=false 禁用该 Domain+Path(访问返回 GATEWAY_ROUTE_DISABLED);" +
9011
+ "enable=true 重新启用。updateRoute 也可用顶层 enable 表达同一语义;" +
9012
+ "也可用专用 action enableRoute/disableRoute。route.enable 优先于顶层 enable。"),
8599
9013
  })
8600
9014
  .optional()
8601
9015
  .describe("路由对象(可选写法)。例:云函数 {upstreamResourceType:\"WEB_SCF\",serviceName:\"fn\",path:\"/api\"};" +
8602
9016
  "云托管 {upstreamResourceType:\"CBR\",serviceName:\"svc\",path:\"/api\"};" +
8603
- "静态托管 {upstreamResourceType:\"STATIC_STORE\",serviceName:\"staticstore\",path:\"/\"}。"),
9017
+ "静态托管 {upstreamResourceType:\"STATIC_STORE\",serviceName:\"staticstore\",path:\"/\"};" +
9018
+ "禁用路由 {path:\"/\",enable:false}(配合 updateRoute,或直接用 disableRoute)。"),
8604
9019
  domain: zod_1.z
8605
9020
  .string()
8606
9021
  .optional()
8607
9022
  .describe("域名。省略时自动使用环境 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(*.{region}.app.tcloudbase.com),不会回退到静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE);也不是 STATIC_STORE 上游绑定。" +
8608
9023
  "可用 queryGateway(action=\"listRoutes\") 核对实际 Domain / DomainType。" +
9024
+ "enableRoute/disableRoute 操作 *.tcloudbaseapp.com 时必须显式传入该域名。" +
8609
9025
  "已有自定义域名时请显式传入该域名并 createRoute/updateRoute/deleteRoute,即可实现自定义域名访问且无需证书 ID;" +
8610
9026
  "仅 bindCustomDomain 时表示要新绑定的域名。"),
8611
9027
  certificateId: zod_1.z
@@ -8628,9 +9044,11 @@ function registerGatewayTools(server) {
8628
9044
  enable: zod_1.z
8629
9045
  .boolean()
8630
9046
  .optional()
8631
- .describe("开关目标状态(enableService / authSwitch 必填):enable=true 开启,enable=false 关闭。" +
8632
- "bindCustomDomain 可选:enable=false 表示绑定后禁用域名(默认启用)。" +
8633
- "省略或非布尔值会返回参数错误。"),
9047
+ .describe("开关目标状态:enableService / authSwitch 必填(true 开启 / false 关闭);" +
9048
+ "bindCustomDomain 可选:enable=false 表示绑定后禁用域名(默认启用);" +
9049
+ "updateRoute 可选:映射到 Routes[].Enable(也可用 route.enable,route 优先;" +
9050
+ "也可用专用 action enableRoute/disableRoute)。" +
9051
+ "省略或非布尔值在 enableService/authSwitch 会返回参数错误。"),
8634
9052
  },
8635
9053
  annotations: {
8636
9054
  readOnlyHint: false,
@@ -9212,8 +9630,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
9212
9630
  return (mod && mod.__esModule) ? mod : { "default": mod };
9213
9631
  };
9214
9632
  Object.defineProperty(exports, "__esModule", ({ value: true }));
9215
- exports.CLOUDRUN_ACCESS_TYPES = exports.CLOUDRUN_SERVICE_TYPES = void 0;
9633
+ exports.CLOUDRUN_PACKAGE_TYPES = exports.CLOUDRUN_ACCESS_TYPES = exports.CLOUDRUN_SERVICE_TYPES = void 0;
9634
+ exports.queryCloudRunEnvStatus = queryCloudRunEnvStatus;
9635
+ exports.describeCloudRunEnvStatus = describeCloudRunEnvStatus;
9636
+ exports.ensureCloudRunEnvInitialized = ensureCloudRunEnvInitialized;
9216
9637
  exports.detectCloudRunDbNetworkRisk = detectCloudRunDbNetworkRisk;
9638
+ exports.extractCloudRunImageInfo = extractCloudRunImageInfo;
9217
9639
  exports.registerCloudRunTools = registerCloudRunTools;
9218
9640
  const child_process_1 = __webpack_require__(5814);
9219
9641
  const fs_1 = __importDefault(__webpack_require__(4421));
@@ -9228,24 +9650,31 @@ const cloudrun_config_js_1 = __webpack_require__(8266);
9228
9650
  exports.CLOUDRUN_SERVICE_TYPES = ['function', 'container'];
9229
9651
  // CloudRun access types
9230
9652
  exports.CLOUDRUN_ACCESS_TYPES = ['OA', 'PUBLIC', 'MINIAPP', 'VPC'];
9653
+ // CloudRun env package types (CreateCloudRunEnv.PackageType)
9654
+ exports.CLOUDRUN_PACKAGE_TYPES = ['Trial', 'Standard', 'Professional', 'Enterprise'];
9231
9655
  // Input schema for queryCloudRun tool
9232
9656
  const queryCloudRunInputSchema = {
9233
- action: zod_1.z.enum(['list', 'detail', 'templates', 'getDeployLog']).describe('查询操作类型:list=获取云托管服务列表(支持分页和筛选),detail=查询指定服务的详细信息(包含服务配置和最新部署状态),templates=获取可用的项目模板列表(用于初始化新项目),getDeployLog=获取指定服务最近一次或指定构建的部署日志'),
9657
+ action: zod_1.z.enum(['list', 'detail', 'templates', 'getDeployLog', 'envStatus']).describe('查询操作类型:list=获取云托管服务列表(支持分页和筛选),detail=查询指定服务的详细信息(包含服务配置和最新部署状态),templates=获取可用的项目模板列表(用于初始化新项目),getDeployLog=获取指定服务最近一次或指定构建的部署日志,envStatus=查询当前环境云托管是否已开通及开通状态(Status=creating开通中/normal已开通),用于initEnv之后轮询进度或deploy之前确认环境是否就绪'),
9234
9658
  // List operation parameters
9235
9659
  pageSize: zod_1.z.number().min(1).max(100).optional().default(10).describe('分页大小,控制每页返回的服务数量。取值范围:1-100,默认值:10。建议根据网络性能和显示需求调整'),
9236
9660
  pageNum: zod_1.z.number().min(1).optional().default(1).describe('页码,用于分页查询。从1开始,默认值:1。配合pageSize使用可实现分页浏览'),
9237
9661
  serverName: zod_1.z.string().optional().describe('服务名称筛选条件,支持模糊匹配。例如:输入"test"可匹配"test-service"、"my-test-app"等服务名称。留空则查询所有服务'),
9238
9662
  serverType: zod_1.z.enum(exports.CLOUDRUN_SERVICE_TYPES).optional().describe('服务类型筛选条件:function=函数型云托管(仅支持Node.js,有特殊的开发要求和限制,适合简单的API服务),container=容器型服务(推荐使用,支持任意语言和框架如Java/Go/Python/PHP/.NET等,适合大多数应用场景)'),
9663
+ envId: zod_1.z.string().optional().describe('环境 ID(action=envStatus 时使用;不传则使用当前配置的环境)。格式如 env-xxxxxx'),
9239
9664
  // Detail and log operation parameters
9240
9665
  detailServerName: zod_1.z.string().optional().describe('要查询详细信息或部署日志的服务名称。当action为detail或getDeployLog时建议提供,必须是已存在的服务名称。可通过list操作获取可用的服务名称列表'),
9241
9666
  buildId: zod_1.z.number().optional().describe('构建ID,仅在action=getDeployLog时使用。不传时默认返回最近一次部署的构建日志'),
9242
9667
  };
9243
9668
  // Input schema for manageCloudRun tool
9244
9669
  const ManageCloudRunInputSchema = {
9245
- action: zod_1.z.enum(['init', 'download', 'run', 'deploy', 'delete', 'createAgent', 'updateConfig']).describe('云托管服务管理操作类型:init=从模板初始化新的云托管项目代码(在targetPath目录下创建以serverName命名的子目录,支持多种语言和框架模板),download=从云端下载现有服务的代码到本地进行开发,run=在本地运行函数型云托管服务(用于开发和调试,仅支持函数型服务),deploy=将本地代码部署到云端云托管服务(支持函数型和容器型;已存在服务会 Read-Merge-Write 保留远程 VpcConf/EnvParams/OpenAccessTypes),updateConfig=仅更新服务配置不重新上传代码(对齐控制台服务设置,走 SubmitServerConfigChangeDiff;不需要 targetPath),delete=删除指定的云托管服务(不可恢复,需要确认),createAgent=创建函数型Agent(基于函数型云托管开发AI智能体)'),
9246
- serverName: zod_1.z.string().describe('云托管服务名称,用于标识和管理服务。命名规则:支持大小写字母、数字、连字符和下划线,必须以字母开头,长度3-45个字符。在init操作中会作为在targetPath下创建的子目录名,在其他操作中作为目标服务名'),
9670
+ action: zod_1.z.enum(['init', 'download', 'run', 'deploy', 'delete', 'createAgent', 'updateConfig', 'initEnv']).describe('云托管服务管理操作类型:init=从模板初始化新的云托管项目代码(在targetPath目录下创建以serverName命名的子目录,支持多种语言和框架模板),download=从云端下载现有服务的代码到本地进行开发,run=在本地运行函数型云托管服务(用于开发和调试,仅支持函数型服务),deploy=将本地代码部署到云端云托管服务(支持函数型和容器型;传 imageUrl 时改为已有镜像部署,走 DeployType=image 容器型,targetPath 可省略;已存在服务会 Read-Merge-Write 保留远程 VpcConf/EnvParams/OpenAccessTypes),updateConfig=仅更新服务配置不重新上传代码(对齐控制台服务设置,走 SubmitServerConfigChangeDiff;不需要 targetPath),delete=删除指定的云托管服务(不可恢复,需要确认),createAgent=创建函数型Agent(基于函数型云托管开发AI智能体),initEnv=开通当前环境的云托管(异步创建云托管环境,幂等:已开通直接返回;适合新环境首次部署前使用)'),
9671
+ serverName: zod_1.z.string().describe('云托管服务名称,用于标识和管理服务。命名规则:支持大小写字母、数字、连字符和下划线,必须以字母开头,长度3-45个字符。在init操作中会作为在targetPath下创建的子目录名,在其他操作中作为目标服务名。initEnv 操作不需要此参数'),
9672
+ // InitEnv operation parameters
9673
+ envId: zod_1.z.string().optional().describe('环境 ID(action=initEnv 时使用;不传则使用当前配置的环境)。格式如 env-xxxxxx'),
9674
+ packageType: zod_1.z.enum(exports.CLOUDRUN_PACKAGE_TYPES).optional().default('Trial').describe('云托管环境套餐类型(action=initEnv 时使用):Trial=试用,Standard=标准,Professional=专业,Enterprise=企业。默认 Trial'),
9247
9675
  // Deploy operation parameters
9248
- targetPath: zod_1.z.string().optional().describe('本地代码路径,必须是绝对路径。在deploy操作中指定要部署的代码目录,在download操作中指定下载目标目录,在init操作中指定云托管服务的上级目录(会在该目录下创建以serverName命名的子目录)。updateConfig 不需要此参数。建议约定:项目根目录下的cloudrun/目录,例如:/Users/username/projects/my-project/cloudrun'),
9676
+ targetPath: zod_1.z.string().optional().describe('本地代码路径,必须是绝对路径。在deploy操作中指定要部署的代码目录,在download操作中指定下载目标目录,在init操作中指定云托管服务的上级目录(会在该目录下创建以serverName命名的子目录)。updateConfig 不需要此参数。建议约定:项目根目录下的cloudrun/目录,例如:/Users/username/projects/my-project/cloudrun。使用 imageUrl 部署已有镜像时此参数可省略'),
9677
+ imageUrl: zod_1.z.string().optional().describe('已有镜像部署(action=deploy 时使用):直接指定容器镜像地址,如 ccr.ccs.tencentyun.com/ns/img:v1 或公网 registry 地址。传入后走 DeployType="image"(容器型)部署,无需本地源码目录(targetPath 可省略)。支持:1) 公网匿名可拉取的镜像直填地址;2) 私有/需登录的镜像(如 ghcr.io)需先在本地 docker pull → docker tag/push 到腾讯云 CCR → 填入 CCR 地址。不传则维持源码构建(本地代码打包上传)。注意:无论哪种部署方式,环境都需先开通云托管(未开通时先调用 initEnv,Status=normal 后再部署)'),
9249
9678
  envParamsReplaceAll: zod_1.z.boolean().optional().default(false).describe('EnvParams 合并策略(deploy / updateConfig):false(默认)= 与远程按 key 合并(输入覆盖同名 key,远程其余 key 保留);true= 用输入 EnvParams 整包替换远程。仅当显式传入 EnvParams 时生效'),
9250
9679
  serverConfig: zod_1.z.object({
9251
9680
  OpenAccessTypes: zod_1.z.array(zod_1.z.enum(exports.CLOUDRUN_ACCESS_TYPES)).optional().describe('公网访问类型配置,控制服务的访问权限:OA=办公网访问,PUBLIC=公网访问(默认,可通过HTTPS域名访问),MINIAPP=小程序访问,VPC=VPC访问(仅同VPC内可访问)。可配置多个类型'),
@@ -9391,6 +9820,88 @@ function buildManageCloudRunErrorMessage(action, serverName, error) {
9391
9820
  }
9392
9821
  const CLOUDRUN_DB_ENV_KEY_PATTERN = /^(DATABASE_URL|DB_HOST|DB_PORT|DB_USER|DB_PASSWORD|DB_NAME|MYSQL_|POSTGRES_|PGHOST|PGPORT|PGUSER|PGPASSWORD|PGDATABASE|PG_|REDIS_|MONGO_|MONGODB_|SQLALCHEMY_DATABASE_URI|SPRING_DATASOURCE_)/i;
9393
9822
  const CLOUDRUN_DB_URL_PATTERN = /(mysql|mariadb|postgres|postgresql|mongodb(\+srv)?|redis|rediss):\/\//i;
9823
+ async function queryCloudRunEnvStatus(options) {
9824
+ const manager = await (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions: options.cloudBaseOptions });
9825
+ if (!manager?.commonService) {
9826
+ throw new Error("Current CloudBase Manager does not support commonService; cannot query CloudRun env status.");
9827
+ }
9828
+ return describeCloudRunEnvStatus(manager, options.envId);
9829
+ }
9830
+ /**
9831
+ * 核心实现:用已获取的 manager 查询云托管开通状态(供 initEnv/envStatus 及
9832
+ * ensureCloudRunEnvInitialized 复用,避免重复 getCloudBaseManager)。
9833
+ */
9834
+ async function describeCloudRunEnvStatus(manager, envId) {
9835
+ const result = await manager
9836
+ .commonService("tcbr", "2022-02-17")
9837
+ .call({
9838
+ Action: "DescribeEnvBaseInfo",
9839
+ Param: { EnvId: envId },
9840
+ });
9841
+ const data = (result ?? {});
9842
+ const baseInfo = (data.EnvBaseInfo ?? {}) ?? {};
9843
+ if (data.IsExist !== true) {
9844
+ return { isExist: false, status: "unopened", baseInfo };
9845
+ }
9846
+ const rawStatus = typeof baseInfo.Status === "string" ? baseInfo.Status : "";
9847
+ const status = rawStatus === "creating" || rawStatus === "normal"
9848
+ ? rawStatus
9849
+ : "unknown";
9850
+ return { isExist: true, status, baseInfo };
9851
+ }
9852
+ /**
9853
+ * 探测当前环境云托管(大租户)是否已初始化。
9854
+ *
9855
+ * 背景(2026-08-13 用户实测):新环境未调 CreateCloudRunEnv 初始化云托管时,
9856
+ * 直接 CreateCloudRunServer 会因"无大租户记录"被默认转入小租户,创建出小租户的
9857
+ * 服务和版本——这是错误路径。本函数在 deploy 创建新服务前用 tcbr DescribeEnvBaseInfo
9858
+ * 探测环境是否已开通云托管;未开通则抛错引导先初始化,而不是默默走到小租户路径。
9859
+ *
9860
+ * 实测(2026-08-13 真实凭据):tcbr 不存在 DescribeCloudRunEnv(单数)Action,调用
9861
+ * 恒返回 InvalidAction;正确的探测接口是 DescribeEnvBaseInfo。未开通云托管的环境返回
9862
+ * IsExist=false 且 EnvBaseInfo 为空结构(不抛错);已开通的环境(实测 ai-share-
9863
+ * d2guukyxybb63b206)返回 IsExist=true 且 EnvBaseInfo 含完整字段(Status="normal"、
9864
+ * PackageType/Region/EnvType/CreateTime 等已填充),两分支可明确区分。
9865
+ *
9866
+ * 双验证:DescribeCloudRunServers 未初始化与已初始化但无服务均返回 ServerList=[],
9867
+ * 无法区分,故不作为初始化判定依据。
9868
+ *
9869
+ * @returns 已初始化返回 true;探测到未初始化抛出带引导信息的 Error。
9870
+ */
9871
+ async function ensureCloudRunEnvInitialized(options) {
9872
+ const manager = await (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions: options.cloudBaseOptions });
9873
+ if (!manager?.commonService) {
9874
+ // 老 SDK 无 commonService 时退化为不拦截(保持既有行为,避免误伤)。
9875
+ return true;
9876
+ }
9877
+ try {
9878
+ const status = await describeCloudRunEnvStatus(manager, options.envId);
9879
+ if (!status.isExist) {
9880
+ throwCloudRunEnvNotInitialized(options.envId);
9881
+ }
9882
+ return true;
9883
+ }
9884
+ catch (error) {
9885
+ const msg = error instanceof Error ? error.message : String(error);
9886
+ // 错误兜底:未初始化/未开通类错误码。
9887
+ // 注意:裸 InvalidParameter 可能是普通参数错误,不拦截;仅当其带 Env/CloudRun
9888
+ // 上下文(疑似 "EnvironmentId not found / CloudRun Env 未开通")时才按未初始化处理。
9889
+ if (/ResourceNotFound|not.?initialized|未开通|未初始化/i.test(msg) ||
9890
+ /InvalidParameter.*(?:Env|CloudRun)/i.test(msg)) {
9891
+ throwCloudRunEnvNotInitialized(options.envId);
9892
+ }
9893
+ // 其他错误(网络/权限等)不拦截,让上层按原逻辑处理。
9894
+ return true;
9895
+ }
9896
+ }
9897
+ function throwCloudRunEnvNotInitialized(envId) {
9898
+ throw new Error(`当前环境(${envId})尚未初始化云托管(CloudRun Env)。` +
9899
+ `不能直接创建服务(CreateCloudRunServer 在无大租户记录时会默认创建到小租户,产生错误的小租户服务与版本)。\n` +
9900
+ `请先开通云托管环境,再重试部署:\n` +
9901
+ `- MCP:manageCloudRun(action="initEnv", envId="${envId}")(异步开通,幂等;开通完成后可用 queryCloudRun(action="envStatus", envId="${envId}") 查询 Status=normal)\n` +
9902
+ `- 或控制台:环境 → 云托管 → 开通(https://tcb.cloud.tencent.com/dev?envId=${envId}#/platform-run)\n` +
9903
+ `初始化完成后重新调用 manageCloudRun(action="deploy")。`);
9904
+ }
9394
9905
  /**
9395
9906
  * Detect likely TCP database/cache env usage without VpcConf.
9396
9907
  * Soft signal for AI agents — does not block deploy.
@@ -9487,6 +9998,36 @@ function normalizeCloudRunDomainUrl(input) {
9487
9998
  ? raw
9488
9999
  : `https://${raw}`;
9489
10000
  }
10001
+ /**
10002
+ * 从服务详情 / 最新部署记录中提取镜像信息(镜像部署时才有)。
10003
+ * 镜像部署(DeployType=image)的服务详情与部署记录通常会携带 ImageUrl 等字段;
10004
+ * 源码构建的服务无此字段,返回 undefined 表示无镜像信息。
10005
+ */
10006
+ function extractCloudRunImageInfo(serviceDetail, latestDeploy) {
10007
+ const candidates = [
10008
+ latestDeploy,
10009
+ serviceDetail?.ServerConfig,
10010
+ serviceDetail?.BaseInfo,
10011
+ serviceDetail,
10012
+ ];
10013
+ for (const source of candidates) {
10014
+ if (!source || typeof source !== "object")
10015
+ continue;
10016
+ const imageUrl = source.ImageUrl ??
10017
+ source.imageUrl ??
10018
+ (typeof source.ImageInfo === "string" ? source.ImageInfo : undefined) ??
10019
+ (typeof source.ImageInfo?.ImageUrl === "string" ? source.ImageInfo.ImageUrl : undefined);
10020
+ if (typeof imageUrl === "string" && imageUrl.trim()) {
10021
+ const deployType = typeof source.DeployType === "string"
10022
+ ? source.DeployType
10023
+ : typeof source.deployType === "string"
10024
+ ? source.deployType
10025
+ : undefined;
10026
+ return { imageUrl: imageUrl.trim(), ...(deployType ? { deployType } : {}) };
10027
+ }
10028
+ }
10029
+ return undefined;
10030
+ }
9490
10031
  function resolveCloudRunFallbackAccess(details) {
9491
10032
  const custom = normalizeCloudRunDomainUrl(details?.BaseInfo?.CustomDomainName);
9492
10033
  if (custom)
@@ -9517,7 +10058,7 @@ function registerCloudRunTools(server) {
9517
10058
  // Tool 1: Get CloudRun service information (read operations)
9518
10059
  server.registerTool("queryCloudRun", {
9519
10060
  title: "查询 CloudRun 服务信息",
9520
- description: "查询云托管服务信息,支持获取服务列表、查询服务详情、获取可用模板列表和部署日志。返回的服务信息包括服务名称、状态、访问类型、配置详情以及最近部署上下文。",
10061
+ description: "查询云托管服务信息,支持获取服务列表、查询服务详情、获取可用模板列表、获取部署日志以及查询环境云托管开通状态(envStatus)。返回的服务信息包括服务名称、状态、访问类型、配置详情以及最近部署上下文。",
9521
10062
  inputSchema: queryCloudRunInputSchema,
9522
10063
  annotations: {
9523
10064
  readOnlyHint: true,
@@ -9629,6 +10170,10 @@ function registerCloudRunTools(server) {
9629
10170
  data: {
9630
10171
  service: result,
9631
10172
  latestDeploy,
10173
+ // 若最新部署记录带镜像信息(镜像部署),透出便于展示
10174
+ ...(extractCloudRunImageInfo(result, latestDeploy)
10175
+ ? { imageInfo: extractCloudRunImageInfo(result, latestDeploy) }
10176
+ : {}),
9632
10177
  ...(deployRecordsWarning ? { deployRecordsWarning } : {})
9633
10178
  },
9634
10179
  message
@@ -9727,6 +10272,65 @@ function registerCloudRunTools(server) {
9727
10272
  ]
9728
10273
  };
9729
10274
  }
10275
+ case 'envStatus': {
10276
+ const envId = input.envId?.trim() || (await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions));
10277
+ if (!manager.commonService) {
10278
+ throw new Error("Current CloudBase Manager does not support commonService; cannot query CloudRun env status.");
10279
+ }
10280
+ let status;
10281
+ try {
10282
+ status = await describeCloudRunEnvStatus(manager, envId);
10283
+ }
10284
+ catch (error) {
10285
+ const baseMessage = error instanceof Error ? error.message : String(error);
10286
+ if (/ResourceNotFound|not.?initialized|未开通|未初始化/i.test(baseMessage) ||
10287
+ /InvalidParameter.*(?:Env|CloudRun)/i.test(baseMessage)) {
10288
+ status = { isExist: false, status: "unopened", baseInfo: {} };
10289
+ }
10290
+ else {
10291
+ throw new Error(`[queryCloudRun/envStatus] ${baseMessage}`);
10292
+ }
10293
+ }
10294
+ let message;
10295
+ if (!status.isExist) {
10296
+ message = `环境 ${envId} 尚未开通云托管。请先调用 manageCloudRun(action="initEnv", envId="${envId}") 开通(异步、幂等),或前往控制台 环境 → 云托管 → 开通;Status=normal 后即可 deploy。`;
10297
+ }
10298
+ else if (status.status === "creating") {
10299
+ message = `环境 ${envId} 云托管正在开通中(Status=creating)。请稍后重试 manageCloudRun(action="deploy"),或用本 action 再次查询直到 Status=normal。`;
10300
+ }
10301
+ else if (status.status === "normal") {
10302
+ message = `环境 ${envId} 云托管已开通(Status=normal),可直接 manageCloudRun(action="deploy")。`;
10303
+ }
10304
+ else {
10305
+ message = `环境 ${envId} 云托管状态未知(Status=${status.status ?? "unknown"}),请稍后重试。`;
10306
+ }
10307
+ return {
10308
+ content: [
10309
+ {
10310
+ type: "text",
10311
+ text: JSON.stringify({
10312
+ success: true,
10313
+ data: {
10314
+ envId,
10315
+ status: status.status,
10316
+ isExist: status.isExist,
10317
+ ...(status.isExist
10318
+ ? {
10319
+ envBaseInfo: {
10320
+ Status: status.baseInfo.Status ?? null,
10321
+ PackageType: status.baseInfo.PackageType ?? null,
10322
+ Region: status.baseInfo.Region ?? null,
10323
+ EnvType: status.baseInfo.EnvType ?? null,
10324
+ },
10325
+ }
10326
+ : {}),
10327
+ },
10328
+ message
10329
+ }, null, 2)
10330
+ }
10331
+ ]
10332
+ };
10333
+ }
9730
10334
  default:
9731
10335
  throw new Error(`Unsupported action: ${input.action}`);
9732
10336
  }
@@ -9736,7 +10340,7 @@ function registerCloudRunTools(server) {
9736
10340
  // Tool 2: Manage CloudRun services (write operations)
9737
10341
  server.registerTool("manageCloudRun", {
9738
10342
  title: "管理 CloudRun 服务",
9739
- description: "管理云托管服务,按开发顺序支持:初始化项目(可从模板开始,模板列表可通过 queryCloudRun 查询)、下载服务代码、本地运行(仅函数型服务)、部署代码、仅更新配置(updateConfig,无需重新上传代码)、删除服务。deploy 对已存在服务会先读取远程配置再合并(保留 VpcConf/EnvParams/OpenAccessTypes)。updateConfig 对齐控制台服务设置页。删除操作需要确认,建议设置force=true。",
10343
+ description: "管理云托管服务,按开发顺序支持:开通云托管环境(initEnv)、初始化项目(可从模板开始,模板列表可通过 queryCloudRun 查询)、下载服务代码、本地运行(仅函数型服务)、部署代码、仅更新配置(updateConfig,无需重新上传代码)、删除服务。deploy 支持两种方式:1) 源码构建(传入 targetPath,本地代码打包上传,默认路径);2) 已有镜像部署(传入 imageUrl,如 ccr.ccs.tencentyun.com/ns/img:v1,走 DeployType=image 容器型部署,targetPath 可省略)。deploy 对已存在服务会先读取远程配置再合并(保留 VpcConf/EnvParams/OpenAccessTypes)。updateConfig 对齐控制台服务设置页。删除操作需要确认,建议设置force=true。新环境首次部署前若提示未开通云托管,先调用 initEnv 开通(异步、幂等)。",
9740
10344
  inputSchema: ManageCloudRunInputSchema,
9741
10345
  annotations: {
9742
10346
  readOnlyHint: false,
@@ -9757,29 +10361,122 @@ function registerCloudRunTools(server) {
9757
10361
  targetPath = validateAndNormalizePath(input.targetPath);
9758
10362
  }
9759
10363
  switch (input.action) {
9760
- case 'createAgent': {
9761
- if (!targetPath) {
9762
- throw new Error("targetPath is required for createAgent operation");
10364
+ case 'initEnv': {
10365
+ const envId = input.envId?.trim() || (await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions));
10366
+ const packageType = input.packageType || 'Trial';
10367
+ if (!manager.commonService) {
10368
+ throw new Error("Current CloudBase Manager does not support commonService; cannot initialize CloudRun env.");
9763
10369
  }
9764
- if (!input.agentConfig) {
9765
- throw new Error("agentConfig is required for createAgent operation");
10370
+ // 幂等:先查当前开通状态,已开通 / 开通中不重复创建。
10371
+ let current;
10372
+ try {
10373
+ current = await describeCloudRunEnvStatus(manager, envId);
9766
10374
  }
9767
- const { agentName, botTag, description, template = 'blank' } = input.agentConfig;
9768
- // Generate BotId
9769
- const botId = botTag ? `ibot-${agentName}-${botTag}` : `ibot-${agentName}-${Date.now()}`;
9770
- // Create Agent using CloudBase Manager
9771
- const agentResult = await manager.agent.createFunctionAgent(targetPath, {
9772
- Name: agentName,
9773
- BotId: botId,
9774
- Introduction: description || `Agent created by ${agentName}`,
9775
- Avatar: undefined
9776
- });
9777
- // Create project directory
9778
- const projectDir = path_1.default.join(targetPath, input.serverName);
9779
- if (!fs_1.default.existsSync(projectDir)) {
9780
- fs_1.default.mkdirSync(projectDir, { recursive: true });
10375
+ catch (error) {
10376
+ const baseMessage = error instanceof Error ? error.message : String(error);
10377
+ if (/ResourceNotFound|not.?initialized|未开通|未初始化/i.test(baseMessage) ||
10378
+ /InvalidParameter.*(?:Env|CloudRun)/i.test(baseMessage)) {
10379
+ current = { isExist: false, status: "unopened", baseInfo: {} };
10380
+ }
10381
+ else {
10382
+ throw new Error(`[manageCloudRun/initEnv] ${baseMessage}`);
10383
+ }
9781
10384
  }
9782
- // Generate package.json
10385
+ if (current.isExist && current.status === "normal") {
10386
+ return {
10387
+ content: [
10388
+ {
10389
+ type: "text",
10390
+ text: JSON.stringify({
10391
+ success: true,
10392
+ data: {
10393
+ envId,
10394
+ status: "normal",
10395
+ packageType: current.baseInfo.PackageType ?? packageType,
10396
+ created: false
10397
+ },
10398
+ message: `环境 ${envId} 已开通云托管(Status=normal),无需重复开通。可直接 manageCloudRun(action="deploy")。`
10399
+ }, null, 2)
10400
+ }
10401
+ ]
10402
+ };
10403
+ }
10404
+ if (current.isExist && current.status === "creating") {
10405
+ return {
10406
+ content: [
10407
+ {
10408
+ type: "text",
10409
+ text: JSON.stringify({
10410
+ success: true,
10411
+ data: {
10412
+ envId,
10413
+ status: "creating",
10414
+ created: false
10415
+ },
10416
+ message: `环境 ${envId} 云托管正在开通中(Status=creating),无需重复开通。请稍后用 queryCloudRun(action="envStatus", envId="${envId}") 查询,Status=normal 后即可 deploy。`
10417
+ }, null, 2)
10418
+ }
10419
+ ]
10420
+ };
10421
+ }
10422
+ // 未开通 → 发起异步开通(CreateCloudRunEnv 异步,不阻塞等待)。
10423
+ let createResult;
10424
+ try {
10425
+ createResult = await manager
10426
+ .commonService("tcbr", "2022-02-17")
10427
+ .call({
10428
+ Action: "CreateCloudRunEnv",
10429
+ Param: { EnvId: envId, PackageType: packageType },
10430
+ });
10431
+ }
10432
+ catch (error) {
10433
+ throw new Error(buildManageCloudRunErrorMessage('initEnv', envId, error));
10434
+ }
10435
+ const tranId = createResult?.TranId ??
10436
+ createResult?.Response?.TranId ??
10437
+ createResult?.tranId;
10438
+ return {
10439
+ content: [
10440
+ {
10441
+ type: "text",
10442
+ text: JSON.stringify({
10443
+ success: true,
10444
+ data: {
10445
+ envId,
10446
+ status: "creating",
10447
+ packageType,
10448
+ created: true,
10449
+ ...(tranId ? { tranId } : {})
10450
+ },
10451
+ message: `已发起云托管开通(异步,Status=creating)。请稍后用 queryCloudRun(action="envStatus", envId="${envId}") 查询状态,Status=normal 后即可 manageCloudRun(action="deploy")。`
10452
+ }, null, 2)
10453
+ }
10454
+ ]
10455
+ };
10456
+ }
10457
+ case 'createAgent': {
10458
+ if (!targetPath) {
10459
+ throw new Error("targetPath is required for createAgent operation");
10460
+ }
10461
+ if (!input.agentConfig) {
10462
+ throw new Error("agentConfig is required for createAgent operation");
10463
+ }
10464
+ const { agentName, botTag, description, template = 'blank' } = input.agentConfig;
10465
+ // Generate BotId
10466
+ const botId = botTag ? `ibot-${agentName}-${botTag}` : `ibot-${agentName}-${Date.now()}`;
10467
+ // Create Agent using CloudBase Manager
10468
+ const agentResult = await manager.agent.createFunctionAgent(targetPath, {
10469
+ Name: agentName,
10470
+ BotId: botId,
10471
+ Introduction: description || `Agent created by ${agentName}`,
10472
+ Avatar: undefined
10473
+ });
10474
+ // Create project directory
10475
+ const projectDir = path_1.default.join(targetPath, input.serverName);
10476
+ if (!fs_1.default.existsSync(projectDir)) {
10477
+ fs_1.default.mkdirSync(projectDir, { recursive: true });
10478
+ }
10479
+ // Generate package.json
9783
10480
  const packageJson = {
9784
10481
  name: input.serverName,
9785
10482
  version: "1.0.0",
@@ -9922,14 +10619,18 @@ for await (let x of res.textStream) {
9922
10619
  };
9923
10620
  }
9924
10621
  case 'deploy': {
9925
- if (!targetPath) {
9926
- throw new Error("targetPath is required for deploy operation");
10622
+ if (!targetPath && !input.imageUrl) {
10623
+ throw new Error("targetPath (source build) or imageUrl (existing image) is required for deploy operation");
9927
10624
  }
9928
10625
  // Determine service type - use input.serverType if provided, otherwise auto-detect
9929
10626
  let serverType;
9930
10627
  let remoteServerConfig = null;
9931
10628
  let existingService = false;
9932
- if (input.serverType) {
10629
+ if (input.imageUrl) {
10630
+ // Image deploy is always container type (SDK: DeployInfo={DeployType:"image", ImageUrl}).
10631
+ serverType = 'container';
10632
+ }
10633
+ else if (input.serverType) {
9933
10634
  serverType = input.serverType;
9934
10635
  }
9935
10636
  else {
@@ -9983,6 +10684,17 @@ for await (let x of res.textStream) {
9983
10684
  // New service create path
9984
10685
  }
9985
10686
  }
10687
+ // 新环境必须先初始化云托管(大租户),否则 CreateCloudRunServer 会默认
10688
+ // 创建小租户服务。仅创建新服务(非 existing)前探测一次。
10689
+ // (2026-08-13 用户实测:luapi-v2 因未初始化云托管被创建到小租户)
10690
+ if (!existingService) {
10691
+ const currentEnvId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
10692
+ await ensureCloudRunEnvInitialized({
10693
+ cloudBaseOptions,
10694
+ envId: currentEnvId,
10695
+ serverName: input.serverName,
10696
+ });
10697
+ }
9986
10698
  let mergedFromRemote = [];
9987
10699
  let effectiveServerConfig = input.serverConfig;
9988
10700
  if (existingService) {
@@ -10000,6 +10712,9 @@ for await (let x of res.textStream) {
10000
10712
  force: input.force,
10001
10713
  serverType: serverType,
10002
10714
  };
10715
+ if (input.imageUrl) {
10716
+ deployParams.imageUrl = input.imageUrl;
10717
+ }
10003
10718
  if (effectiveServerConfig && Object.keys(effectiveServerConfig).length > 0) {
10004
10719
  deployParams.serverConfig = effectiveServerConfig;
10005
10720
  }
@@ -10020,22 +10735,26 @@ for await (let x of res.textStream) {
10020
10735
  catch (error) {
10021
10736
  throw new Error(buildManageCloudRunErrorMessage('deploy', input.serverName, error));
10022
10737
  }
10023
- // Generate cloudbaserc.json configuration file
10738
+ // Generate cloudbaserc.json configuration file (source-build only; image deploy has no local project dir)
10024
10739
  const currentEnvId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
10025
- const cloudbasercPath = path_1.default.join(targetPath, 'cloudbaserc.json');
10026
- const cloudbasercContent = {
10027
- envId: currentEnvId,
10028
- cloudrun: {
10029
- name: input.serverName
10740
+ let cloudbasercGenerated = false;
10741
+ if (targetPath) {
10742
+ const cloudbasercPath = path_1.default.join(targetPath, 'cloudbaserc.json');
10743
+ const cloudbasercContent = {
10744
+ envId: currentEnvId,
10745
+ cloudrun: {
10746
+ name: input.serverName
10747
+ }
10748
+ };
10749
+ try {
10750
+ fs_1.default.writeFileSync(cloudbasercPath, JSON.stringify(cloudbasercContent, null, 2));
10751
+ cloudbasercGenerated = true;
10752
+ }
10753
+ catch (error) {
10754
+ (0, logger_js_1.debug)('cloudbaserc.json creation skipped:', error instanceof Error ? error : new Error(String(error)));
10030
10755
  }
10031
- };
10032
- const consoleUrl = `https://tcb.cloud.tencent.com/dev?envId=${currentEnvId}#/platform-run/service/detail?serverName=${input.serverName}&tabId=overview&envId=${currentEnvId}`;
10033
- try {
10034
- fs_1.default.writeFileSync(cloudbasercPath, JSON.stringify(cloudbasercContent, null, 2));
10035
- }
10036
- catch (error) {
10037
- (0, logger_js_1.debug)('cloudbaserc.json creation skipped:', error instanceof Error ? error : new Error(String(error)));
10038
10756
  }
10757
+ const consoleUrl = `https://tcb.cloud.tencent.com/dev?envId=${currentEnvId}#/platform-run/service/detail?serverName=${input.serverName}&tabId=overview&envId=${currentEnvId}`;
10039
10758
  let preferredAccessUrl;
10040
10759
  let preferredAccessUrls = [];
10041
10760
  let preferredAccessSource;
@@ -10072,7 +10791,7 @@ for await (let x of res.textStream) {
10072
10791
  }
10073
10792
  // Send deployment notification to CodeBuddy IDE
10074
10793
  try {
10075
- const projectName = path_1.default.basename(targetPath);
10794
+ const projectName = targetPath ? path_1.default.basename(targetPath) : input.serverName;
10076
10795
  await (0, notification_js_1.sendDeployNotification)(server, {
10077
10796
  deployType: 'cloudrun',
10078
10797
  url: preferredAccessUrl ?? "",
@@ -10102,9 +10821,11 @@ for await (let x of res.textStream) {
10102
10821
  data: {
10103
10822
  serviceName: input.serverName,
10104
10823
  status: 'deploying',
10105
- deployPath: targetPath,
10824
+ deployType: input.imageUrl ? 'image' : 'source',
10825
+ ...(targetPath ? { deployPath: targetPath } : {}),
10826
+ ...(input.imageUrl ? { imageUrl: input.imageUrl } : {}),
10106
10827
  serverType: serverType,
10107
- cloudbasercGenerated: true,
10828
+ cloudbasercGenerated,
10108
10829
  consoleUrl,
10109
10830
  ...(existingService
10110
10831
  ? {
@@ -10127,7 +10848,7 @@ for await (let x of res.textStream) {
10127
10848
  : {}),
10128
10849
  ...(warnings.length > 0 ? { warnings } : {}),
10129
10850
  },
10130
- message: `Triggered deployment for ${serverType} service '${input.serverName}' from ${targetPath}. You can follow the progress in ${consoleUrl}.${warningSuffix}`
10851
+ message: `Triggered deployment for ${serverType} service '${input.serverName}' ${input.imageUrl ? `from image ${input.imageUrl}` : `from ${targetPath}`}. You can follow the progress in ${consoleUrl}.${warningSuffix}`
10131
10852
  }, null, 2)
10132
10853
  }
10133
10854
  ]
@@ -11183,6 +11904,13 @@ function registerAppAuthTools(server) {
11183
11904
  }
11184
11905
 
11185
11906
 
11907
+ /***/ }),
11908
+
11909
+ /***/ 5663:
11910
+ /***/ ((module) => {
11911
+
11912
+ module.exports = __WEBPACK_EXTERNAL_MODULE_node_path_02319fef__;
11913
+
11186
11914
  /***/ }),
11187
11915
 
11188
11916
  /***/ 5814:
@@ -11255,7 +11983,7 @@ class TelemetryReporter {
11255
11983
  const arch = os_1.default.arch(); // 系统架构
11256
11984
  // Build-time injected version; guard for vitest / non-webpack runtimes.
11257
11985
  const mcpVersion = process.env.npm_package_version ||
11258
- ( true ? "2.25.10" : 0);
11986
+ ( true ? "2.27.0" : 0);
11259
11987
  return {
11260
11988
  userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
11261
11989
  deviceId: this.deviceId,
@@ -11589,8 +12317,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11589
12317
  return (mod && mod.__esModule) ? mod : { "default": mod };
11590
12318
  };
11591
12319
  Object.defineProperty(exports, "__esModule", ({ value: true }));
11592
- exports.CUSTOM_IMAGE_RUNTIME = exports.MANAGE_FUNCTION_ACTIONS = exports.QUERY_FUNCTION_ACTIONS = exports.TRIGGER_CONFIG_EXAMPLES = exports.SUPPORTED_TRIGGER_TYPES = exports.DEFAULT_NODEJS_RUNTIME = exports.SUPPORTED_NODEJS_RUNTIMES = exports.RECOMMENDED_RUNTIMES = exports.DEFAULT_RUNTIME = exports.ALL_SUPPORTED_RUNTIMES = exports.SUPPORTED_RUNTIMES = void 0;
12320
+ exports.CUSTOM_IMAGE_RUNTIME = exports.LAYER_SOFT_WARN = exports.MANAGE_FUNCTION_ACTIONS = exports.QUERY_FUNCTION_ACTIONS = exports.TRIGGER_CONFIG_EXAMPLES = exports.SUPPORTED_TRIGGER_TYPES = exports.DEFAULT_NODEJS_RUNTIME = exports.SUPPORTED_NODEJS_RUNTIMES = exports.RECOMMENDED_RUNTIMES = exports.DEFAULT_RUNTIME = exports.ALL_SUPPORTED_RUNTIMES = exports.SUPPORTED_RUNTIMES = void 0;
11593
12321
  exports.formatRuntimeList = formatRuntimeList;
12322
+ exports.layerNameIncludesEnvId = layerNameIncludesEnvId;
12323
+ exports.buildCreateLayerNameWarning = buildCreateLayerNameWarning;
11594
12324
  exports.validateTimerCron = validateTimerCron;
11595
12325
  exports.pickManageFunctionName = pickManageFunctionName;
11596
12326
  exports.shouldInstallDependencyForFunction = shouldInstallDependencyForFunction;
@@ -11695,6 +12425,24 @@ exports.MANAGE_FUNCTION_ACTIONS = [
11695
12425
  "updateFunctionLayers",
11696
12426
  "incrementalDeployFunction", // 增量部署,需通过 pluginOptions.functions 注入实现
11697
12427
  ];
12428
+ /** Layer soft-warn copy — account-scoped SCF LayerName guidance (no hard fail). */
12429
+ exports.LAYER_SOFT_WARN = {
12430
+ createNameFormat: (envId) => `建议使用 {layerName}_${envId} 格式,当前名称可能与其他环境共享版本序列`,
12431
+ deleteVersion: "该层为账号级共享资源,删除版本会影响所有绑定该版本的环境的函数,请确认",
12432
+ bindShared: "层为账号级共享,绑定/解绑影响所有引用该层名的环境",
12433
+ accountLevelView: "返回账号级视图,含其他环境创建的层",
12434
+ };
12435
+ function layerNameIncludesEnvId(layerName, envId) {
12436
+ if (!envId)
12437
+ return true;
12438
+ return layerName.includes(envId);
12439
+ }
12440
+ function buildCreateLayerNameWarning(layerName, envId) {
12441
+ if (!envId || layerNameIncludesEnvId(layerName, envId)) {
12442
+ return undefined;
12443
+ }
12444
+ return exports.LAYER_SOFT_WARN.createNameFormat(envId);
12445
+ }
11698
12446
  const VPC_SCHEMA = zod_1.z.object({
11699
12447
  vpcId: zod_1.z
11700
12448
  .string()
@@ -11936,11 +12684,12 @@ function registerFunctionTools(server) {
11936
12684
  const cloudBaseOptions = server.cloudBaseOptions;
11937
12685
  const deployOverrides = server.pluginOptions?.functions;
11938
12686
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
11939
- const buildEnvelope = (data, message, nextActions) => ({
12687
+ const buildEnvelope = (data, message, nextActions, warnings) => ({
11940
12688
  success: true,
11941
12689
  data,
11942
12690
  message,
11943
12691
  ...(nextActions?.length ? { nextActions } : {}),
12692
+ ...(warnings?.length ? { warnings } : {}),
11944
12693
  });
11945
12694
  const buildErrorEnvelope = (error, errorCode) => ({
11946
12695
  success: false,
@@ -12191,7 +12940,7 @@ function registerFunctionTools(server) {
12191
12940
  action: "createLayerVersion",
12192
12941
  reason: "发布新的层版本",
12193
12942
  },
12194
- ]);
12943
+ ], [exports.LAYER_SOFT_WARN.accountLevelView]);
12195
12944
  }
12196
12945
  case "listLayerVersions": {
12197
12946
  if (!input.layerName) {
@@ -12219,7 +12968,7 @@ function registerFunctionTools(server) {
12219
12968
  action: "attachLayer",
12220
12969
  reason: "将某个层版本绑定到函数",
12221
12970
  },
12222
- ]);
12971
+ ], [exports.LAYER_SOFT_WARN.accountLevelView]);
12223
12972
  }
12224
12973
  case "getLayerVersionDetail": {
12225
12974
  if (!input.layerName) {
@@ -12252,7 +13001,7 @@ function registerFunctionTools(server) {
12252
13001
  action: "deleteLayerVersion",
12253
13002
  reason: "删除该层版本",
12254
13003
  },
12255
- ]);
13004
+ ], [exports.LAYER_SOFT_WARN.accountLevelView]);
12256
13005
  }
12257
13006
  case "listFunctionTriggers": {
12258
13007
  if (!input.functionName) {
@@ -12768,6 +13517,8 @@ function registerFunctionTools(server) {
12768
13517
  if (!input.contentPath && !input.base64Content) {
12769
13518
  throw new Error("createLayerVersion 操作时,contentPath 和 base64Content 至少需要提供一个");
12770
13519
  }
13520
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
13521
+ const nameWarning = buildCreateLayerNameWarning(input.layerName, envId);
12771
13522
  const cloudbase = await getManager();
12772
13523
  const result = await cloudbase.functions.createLayer({
12773
13524
  name: input.layerName,
@@ -12790,7 +13541,7 @@ function registerFunctionTools(server) {
12790
13541
  action: "listLayerVersions",
12791
13542
  reason: "查看该层的全部版本",
12792
13543
  },
12793
- ]);
13544
+ ], nameWarning ? [nameWarning] : undefined);
12794
13545
  }
12795
13546
  case "deleteLayerVersion": {
12796
13547
  if (!input.layerName) {
@@ -12817,7 +13568,7 @@ function registerFunctionTools(server) {
12817
13568
  action: "listLayerVersions",
12818
13569
  reason: "确认剩余层版本",
12819
13570
  },
12820
- ]);
13571
+ ], [exports.LAYER_SOFT_WARN.deleteVersion]);
12821
13572
  }
12822
13573
  case "attachLayer":
12823
13574
  case "detachLayer":
@@ -12827,6 +13578,7 @@ function registerFunctionTools(server) {
12827
13578
  }
12828
13579
  const cloudbase = await getManager();
12829
13580
  const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
13581
+ const bindWarnings = [exports.LAYER_SOFT_WARN.bindShared];
12830
13582
  if (input.action === "attachLayer") {
12831
13583
  if (!input.layerName) {
12832
13584
  throw new Error("attachLayer 操作时,layerName 参数是必需的");
@@ -12855,7 +13607,7 @@ function registerFunctionTools(server) {
12855
13607
  action: "listFunctionLayers",
12856
13608
  reason: "确认函数当前绑定层列表",
12857
13609
  },
12858
- ]);
13610
+ ], bindWarnings);
12859
13611
  }
12860
13612
  if (input.action === "detachLayer") {
12861
13613
  if (!input.layerName) {
@@ -12886,7 +13638,7 @@ function registerFunctionTools(server) {
12886
13638
  action: "listFunctionLayers",
12887
13639
  reason: "确认解绑后的层列表",
12888
13640
  },
12889
- ]);
13641
+ ], bindWarnings);
12890
13642
  }
12891
13643
  const normalizedLayers = normalizeManageLayers(input.layers);
12892
13644
  if (!normalizedLayers.length) {
@@ -12911,7 +13663,7 @@ function registerFunctionTools(server) {
12911
13663
  action: "listFunctionLayers",
12912
13664
  reason: "确认最新层顺序和绑定结果",
12913
13665
  },
12914
- ]);
13666
+ ], bindWarnings);
12915
13667
  }
12916
13668
  default:
12917
13669
  // incrementalDeployFunction:无默认实现,必须通过 pluginOptions 注入
@@ -12941,6 +13693,10 @@ function registerFunctionTools(server) {
12941
13693
  "\n- 示例:`queryFunctions(action=\"listFunctionLogs\", functionName=\"my-function\")`" +
12942
13694
  "\n- 如需查看日志详情:`queryFunctions(action=\"getFunctionLogDetail\", requestId=\"xxx\")`" +
12943
13695
  "\n\n**定时任务 / cron / 定时跑**:使用 `listFunctionTriggers` 查询函数的 timer 触发器配置。" +
13696
+ "\n\n**层(Layer)说明**:" +
13697
+ "\n- 层为 SCF 账号级共享命名空间:不同环境创建同名层会共享同一层的版本序列;删除某版本会影响所有绑定该版本的环境的函数" +
13698
+ "\n- 创建层必须用带环境标识的唯一层名,固定格式:`{layerName}_{当前envId}`(如 `common_cloud1-d9ghadgak3edf6b36`)。不要在不同环境使用相同裸层名,创建前先 `listLayers` 查重" +
13699
+ "\n- `listLayers` / `listLayerVersions` / `getLayerVersionDetail` 返回账号级视图,可能含其他环境创建的层" +
12944
13700
  "\n\n**区分 `queryLogs` 工具**:" +
12945
13701
  "\n- 本工具用于查询特定 CloudBase 云函数的执行日志" +
12946
13702
  "\n- `queryLogs` 工具用于搜索 CLS 日志服务(跨服务日志聚合)",
@@ -12953,9 +13709,9 @@ function registerFunctionTools(server) {
12953
13709
  "\n- `listFunctionLogs`: 查询 CloudBase 云函数执行日志(需要 functionName)" +
12954
13710
  "\n- `getFunctionLogDetail`: 获取日志详情(需要 requestId)" +
12955
13711
  "\n- `listFunctionLayers`: 列出函数绑定的层" +
12956
- "\n- `listLayers`: 列出所有层" +
12957
- "\n- `listLayerVersions`: 列出层的版本(注意:是 Versions 不是 Version)" +
12958
- "\n- `getLayerVersionDetail`: 获取层版本详情" +
13712
+ "\n- `listLayers`: 列出所有层(账号级视图,含其他环境创建的层)" +
13713
+ "\n- `listLayerVersions`: 列出层的版本(注意:是 Versions 不是 Version;账号级视图)" +
13714
+ "\n- `getLayerVersionDetail`: 获取层版本详情(账号级视图)" +
12959
13715
  "\n- `listFunctionTriggers`: 列出函数触发器(用于查看定时任务 / cron / timer 配置)" +
12960
13716
  "\n- `getFunctionDownloadUrl`: 获取函数代码下载地址"),
12961
13717
  functionName: zod_1.z
@@ -12982,7 +13738,11 @@ function registerFunctionTools(server) {
12982
13738
  qualifier: zod_1.z.string().optional().describe("函数版本别名,如 $LATEST、$DEFAULT。日志查询时可选"),
12983
13739
  runtime: zod_1.z.string().optional().describe("层查询的运行时筛选,如 Nodejs18.15"),
12984
13740
  searchKey: zod_1.z.string().optional().describe("层名称搜索关键字"),
12985
- layerName: zod_1.z.string().optional().describe("层名称。`listLayerVersions`、`getLayerVersionDetail` 操作必填"),
13741
+ layerName: zod_1.z
13742
+ .string()
13743
+ .optional()
13744
+ .describe("层名称。`listLayerVersions`、`getLayerVersionDetail` 操作必填。" +
13745
+ "层为账号级共享命名空间;推荐固定格式 `{layerName}_{当前envId}`(如 common_cloud1-d9ghadgak3edf6b36)"),
12986
13746
  layerVersion: zod_1.z.number().optional().describe("层版本号。`getLayerVersionDetail` 操作必填"),
12987
13747
  },
12988
13748
  annotations: {
@@ -12997,12 +13757,18 @@ function registerFunctionTools(server) {
12997
13757
  "如果要创建 cron 定时任务,先用 createFunction 创建函数,再用 createFunctionTrigger 创建 timer 触发器(支持7段cron表达式),deleteFunctionTrigger 删除触发器。" +
12998
13758
  "镜像部署(Runtime=CustomImage):先把代码经 zip→COS→CloudApp custom 构建→TCR 推镜像(这一阶段为裸腾讯云 API,本工具不覆盖)," +
12999
13759
  "再用 createFunction(func.runtime=\"CustomImage\", imageConfig) 基于 TCR 镜像创建 HTTP 函数;后续迭代用 updateFunctionCode + imageConfig 换镜像 tag。" +
13000
- "危险操作需要显式 confirm=true。",
13760
+ "危险操作需要显式 confirm=true。" +
13761
+ "\n\n**层(Layer)说明**:" +
13762
+ "\n- 层为 SCF 账号级共享命名空间:不同环境创建同名层会共享同一层的版本序列;删除某版本会影响所有绑定该版本的环境的函数" +
13763
+ "\n- 创建层必须用带环境标识的唯一层名,固定格式:`{layerName}_{当前envId}`(如 `common_cloud1-d9ghadgak3edf6b36`)。不要在不同环境使用相同裸层名,创建前先 `listLayers` 查重" +
13764
+ "\n- 相关 action:`createLayerVersion` / `deleteLayerVersion` / `attachLayer` / `detachLayer` / `updateFunctionLayers`(只读查询见 queryFunctions 的 listLayers / listLayerVersions / getLayerVersionDetail)",
13001
13765
  inputSchema: {
13002
13766
  action: zod_1.z
13003
13767
  .enum(exports.MANAGE_FUNCTION_ACTIONS)
13004
13768
  .describe("写操作类型,例如 createFunction、updateFunctionCode、incrementalDeployFunction、invokeFunction、deleteFunction、" +
13005
- "createFunctionTrigger(定时任务 / cron / timer)、deleteFunctionTrigger、attachLayer、detachLayer"),
13769
+ "createFunctionTrigger(定时任务 / cron / timer)、deleteFunctionTrigger、" +
13770
+ "createLayerVersion、deleteLayerVersion、attachLayer、detachLayer、updateFunctionLayers。" +
13771
+ "层名推荐固定格式 `{layerName}_{当前envId}`(如 common_cloud1-d9ghadgak3edf6b36)"),
13006
13772
  func: CREATE_FUNCTION_SCHEMA.optional().describe("createFunction 操作的函数配置"),
13007
13773
  functionRootPath: zod_1.z.string().optional().describe("创建或更新函数代码时默认推荐的本地目录方式。" +
13008
13774
  "必须是直接包含函数文件夹的目录绝对路径(如 /abs/path/cloudfunctions 或 /abs/path/functions)," +
@@ -13032,7 +13798,11 @@ function registerFunctionTools(server) {
13032
13798
  .describe("createFunctionTrigger 的触发器列表,用于定时跑 / 定时任务 / scheduled job。timer 触发器使用7段 cron 表达式(秒 分 时 日 月 星期 年)," +
13033
13799
  '如 "0 */5 * * * * *" 表示每5分钟执行一次'),
13034
13800
  triggerName: zod_1.z.string().optional().describe("deleteFunctionTrigger 的目标触发器名称"),
13035
- layerName: zod_1.z.string().optional().describe("层名称"),
13801
+ layerName: zod_1.z
13802
+ .string()
13803
+ .optional()
13804
+ .describe("层名称。创建层推荐固定格式 `{layerName}_{当前envId}`(如 common_cloud1-d9ghadgak3edf6b36);" +
13805
+ "不要跨环境复用裸层名。层为账号级共享命名空间"),
13036
13806
  layerVersion: zod_1.z.number().optional().describe("层版本号"),
13037
13807
  contentPath: zod_1.z.string().optional().describe("层内容路径,可为目录或 ZIP 文件"),
13038
13808
  base64Content: zod_1.z.string().optional().describe("层内容的 base64 编码"),
@@ -16790,7 +17560,7 @@ function registerSetupTools(server) {
16790
17560
  server.registerTool("downloadTemplate", {
16791
17561
  title: "下载项目模板",
16792
17562
  description: `自动下载并部署CloudBase项目模板。
16793
- **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.10" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
17563
+ **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.27.0" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
16794
17564
  inputSchema: {
16795
17565
  template: zod_1.z
16796
17566
  .enum(["react", "vue", "miniprogram", "uniapp", "rules"])
@@ -17245,7 +18015,8 @@ function registerAppTools(server) {
17245
18015
  .optional()
17246
18016
  .describe("自定义部署命令。通常无需填写,默认自动生成 tcb hosting deploy 命令。" +
17247
18017
  "有 buildPath 时远端已 cd 到该目录,默认用 . 作为源码路径;无 buildPath 时默认用 dist。"),
17248
- ignore: zod_1.z.array(zod_1.z.string()).optional().describe("上传时忽略的文件/目录 glob 模式,例如 **/node_modules/**。"),
18018
+ ignore: zod_1.z.array(zod_1.z.string()).optional().describe("上传时忽略的文件/目录 glob 模式,例如 **/node_modules/**。\n" +
18019
+ "⚠️ 打包的是项目根目录(filePath)而非 buildPath 产物目录:若项目根含 target/(Rust)、.next/、dist-old/、build/ 等大构建产物,必须加进 ignore(如 **/target/**),否则整个目录被打进上传 zip(实证 54GB target → 34GB zip)。默认已排除 node_modules/.git/.DS_Store/**/target/**/.next/**/.next.bak/**。"),
17249
18020
  versionName: zod_1.z
17250
18021
  .string()
17251
18022
  .optional()
@@ -17265,6 +18036,17 @@ function registerAppTools(server) {
17265
18036
  if (!appService) {
17266
18037
  throw new Error("当前 manager 未提供 cloudAppService");
17267
18038
  }
18039
+ // 默认排除的大目录(2026-08-14 实证:ato 项目 target/ 54GB 被整个打进 zip)
18040
+ // tcb app deploy 打包的是项目根目录(localPath)而非 outputDir,必须排除构建产物
18041
+ const defaultPackIgnore = [
18042
+ "node_modules/**",
18043
+ ".git/**",
18044
+ ".DS_Store",
18045
+ "**/.DS_Store",
18046
+ "**/target/**",
18047
+ "**/.next/**",
18048
+ "**/.next.bak/**",
18049
+ ];
17268
18050
  // getUploadUrl — 获取预签名上传 URL(cloud mode 专用)
17269
18051
  if (action === "getUploadUrl") {
17270
18052
  if (!serviceName) {
@@ -17275,9 +18057,9 @@ function registerAppTools(server) {
17275
18057
  serviceName,
17276
18058
  });
17277
18059
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, cosInfoResult);
17278
- const defaultIgnore = ["node_modules/**", ".git/**", ".DS_Store", "**/.DS_Store"];
18060
+ const defaultIgnore = defaultPackIgnore;
17279
18061
  // eslint-disable-next-line max-len
17280
- const zipCmd = "zip -r upload.zip . -x 'node_modules/**' -x '.git/**' -x '.DS_Store' -x '**/.DS_Store'";
18062
+ const zipCmd = "zip -r upload.zip . -x 'node_modules/**' -x '.git/**' -x '.DS_Store' -x '**/.DS_Store' -x '**/target/**' -x '**/.next/**' -x '**/.next.bak/**'";
17281
18063
  const followupArgs = {
17282
18064
  action: "deployApp",
17283
18065
  serviceName,
@@ -17321,11 +18103,17 @@ function registerAppTools(server) {
17321
18103
  // 上传代码到 COS(仅本地模式需要,cloud mode 用 cosTimestamp 跳过)
17322
18104
  let cosTs = cosTimestamp;
17323
18105
  if (filePath && !cosTs) {
18106
+ // 默认排除大目录(2026-08-14 实证:ato 项目 target/ 54GB 被整个打进 zip)
18107
+ // 用户显式传 ignore 时合并,避免覆盖默认值
18108
+ const mergedIgnore = Array.from(new Set([
18109
+ ...defaultPackIgnore,
18110
+ ...(ignore ?? []),
18111
+ ]));
17324
18112
  const uploadResult = await appService.uploadCode({
17325
18113
  deployType: "static-hosting",
17326
18114
  serviceName,
17327
18115
  localPath: filePath,
17328
- ignore,
18116
+ ignore: mergedIgnore,
17329
18117
  });
17330
18118
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, uploadResult);
17331
18119
  cosTs = uploadResult.cosTimestamp;
@@ -18064,16 +18852,19 @@ function isDirectoryUploadTarget(localPath, cloudPath) {
18064
18852
  return true;
18065
18853
  return path_1.default.posix.extname(normalizedCloudPath) === '';
18066
18854
  }
18067
- function buildHostingAccessUrl(staticDomain, cloudPath, localPath) {
18068
- if (!staticDomain)
18069
- return '';
18855
+ function hostingAccessPathname(cloudPath, localPath) {
18070
18856
  const normalizedCloudPath = (cloudPath ?? '').trim().replace(/^\/+|\/+$/g, '');
18071
18857
  const isDirectory = isDirectoryUploadTarget(localPath, cloudPath);
18072
18858
  if (!normalizedCloudPath) {
18073
- return `https://${staticDomain}/`;
18859
+ return '/';
18074
18860
  }
18075
18861
  const pathname = isDirectory ? `${normalizedCloudPath}/` : normalizedCloudPath;
18076
- return `https://${staticDomain}/${pathname}`;
18862
+ return `/${pathname}`;
18863
+ }
18864
+ function buildHostingAccessUrl(staticDomain, cloudPath, localPath) {
18865
+ if (!staticDomain)
18866
+ return '';
18867
+ return `https://${staticDomain}${hostingAccessPathname(cloudPath, localPath)}`;
18077
18868
  }
18078
18869
  function buildUploadErrorMessage(error, localPath) {
18079
18870
  const baseMessage = error instanceof Error ? error.message : String(error);
@@ -18254,7 +19045,7 @@ function buildDomainMutationResult(params) {
18254
19045
  message: `静态托管域名${actionLabel}请求已提交。域名配置、证书校验和边缘侧传播通常需要 30 秒到 10 分钟,请继续调用 queryHosting(action="domainStatus") 确认最终结果。`,
18255
19046
  };
18256
19047
  }
18257
- async function getHostingWebsiteConfig(cloudbase, logger) {
19048
+ async function getHostingWebsiteConfig(cloudbase, logger, cloudBaseOptions) {
18258
19049
  const websiteConfig = await cloudbase.hosting.getWebsiteConfig();
18259
19050
  (0, cloudbase_manager_js_1.logCloudBaseResult)(logger, websiteConfig);
18260
19051
  const hostingResult = {
@@ -18271,6 +19062,27 @@ async function getHostingWebsiteConfig(cloudbase, logger) {
18271
19062
  catch {
18272
19063
  // Ignore enrichment failures and return the website config as-is.
18273
19064
  }
19065
+ try {
19066
+ const cdnDomain = typeof hostingResult.CdnDomain === 'string' ? hostingResult.CdnDomain : '';
19067
+ if (cdnDomain && typeof cloudbase.env?.describeHttpServiceRoute === 'function') {
19068
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
19069
+ const routeResult = await cloudbase.env.describeHttpServiceRoute({
19070
+ EnvId: envId,
19071
+ Limit: 1000,
19072
+ });
19073
+ const routes = (0, gateway_access_urls_js_1.flattenHttpServiceRoutes)(routeResult);
19074
+ const reachable = (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(routes, cdnDomain, '/');
19075
+ hostingResult.staticDomainRouteEnabled = reachable;
19076
+ hostingResult.accessUrlReachable = reachable !== false;
19077
+ if (reachable === false) {
19078
+ hostingResult.routeDisabled = true;
19079
+ hostingResult.disabledAccessUrls = [`https://${cdnDomain}/`];
19080
+ }
19081
+ }
19082
+ }
19083
+ catch {
19084
+ // Gateway route lookup is best-effort enrichment only.
19085
+ }
18274
19086
  return hostingResult;
18275
19087
  }
18276
19088
  async function resolveHostingStaticDomain(cloudbase, logger) {
@@ -18354,7 +19166,7 @@ function registerHostingTools(server) {
18354
19166
  const cloudbase = await getManager();
18355
19167
  switch (input.action) {
18356
19168
  case 'websiteConfig': {
18357
- const websiteConfig = await getHostingWebsiteConfig(cloudbase, server.logger);
19169
+ const websiteConfig = await getHostingWebsiteConfig(cloudbase, server.logger, cloudBaseOptions);
18358
19170
  return (0, tool_result_js_1.buildJsonToolResult)({
18359
19171
  success: true,
18360
19172
  data: {
@@ -18480,7 +19292,7 @@ function registerHostingTools(server) {
18480
19292
  });
18481
19293
  server.registerTool('manageHosting', {
18482
19294
  title: '管理 CloudBase 静态托管',
18483
- description: '管理 CloudBase 静态托管的变更操作。action=upload 上传本地构建产物到共享域名(域名格式:<envId>-<appId>.tcloudbaseapp.com/<cloudPath>);action=delete 删除托管文件或目录(必须 confirm=true);action=setWebsiteDocument 设置首页/错误页/路由规则;action=enableService 开通静态托管;action=bindDomain / unbindDomain / updateDomain 管理自定义域名;action=downloadFile / downloadDirectory 下载托管内容到本地。⚠️ 新项目部署优先使用 manageApps(部署到独立子域名),本工具适合已有老项目继续使用或作为 manageApps 的 fallback。manageApps 与 manageHosting 域名不同,切换会导致老链接失效。若任务只是查看配置、文件或域名状态,请改用 queryHosting。',
19295
+ 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。',
18484
19296
  inputSchema: manageHostingInputSchema,
18485
19297
  annotations: {
18486
19298
  readOnlyHint: false,
@@ -18520,7 +19332,24 @@ function registerHostingTools(server) {
18520
19332
  const cdnFromStore = (store.CdnDomain ?? store.StaticDomain);
18521
19333
  const staticDomain = cdnFromStore || await resolveHostingStaticDomain(cloudbase, server.logger);
18522
19334
  const fallbackAccessUrl = buildHostingAccessUrl(staticDomain, input.cloudPath, input.localPath);
19335
+ const accessPathname = hostingAccessPathname(input.cloudPath, input.localPath);
18523
19336
  const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
19337
+ const getGatewayManager = async () => {
19338
+ const manager = await getManager();
19339
+ if (!manager) {
19340
+ throw new Error("cloudbase manager unavailable");
19341
+ }
19342
+ return manager;
19343
+ };
19344
+ const allGatewayRoutes = await (0, gateway_access_urls_js_1.resolveAllGatewayRoutes)({
19345
+ envId,
19346
+ getManager: getGatewayManager,
19347
+ });
19348
+ const staticDomainRouteEnabled = staticDomain
19349
+ ? (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(allGatewayRoutes, staticDomain, accessPathname)
19350
+ : null;
19351
+ // null = no matching gateway route info → keep legacy fallback.
19352
+ const fallbackReachable = staticDomainRouteEnabled !== false;
18524
19353
  const gatewayCandidates = Array.from(new Set([
18525
19354
  store.StaticStoreName,
18526
19355
  store.Name,
@@ -18530,34 +19359,38 @@ function registerHostingTools(server) {
18530
19359
  ]
18531
19360
  .map((item) => (typeof item === "string" ? item.trim() : ""))
18532
19361
  .filter(Boolean)));
18533
- let accessUrl = fallbackAccessUrl;
19362
+ let accessUrl = fallbackReachable ? fallbackAccessUrl : "";
18534
19363
  let accessUrls = accessUrl ? [accessUrl] : [];
18535
19364
  let accessUrlSource = accessUrl
18536
19365
  ? "hosting.staticDomain"
18537
19366
  : undefined;
19367
+ let accessUrlReachable = Boolean(accessUrl);
19368
+ let disabledAccessUrls = [];
19369
+ if (fallbackAccessUrl && !fallbackReachable) {
19370
+ disabledAccessUrls.push(fallbackAccessUrl);
19371
+ }
18538
19372
  for (const upstreamName of gatewayCandidates) {
18539
19373
  const gateway = await (0, gateway_access_urls_js_1.resolveGatewayAccessUrls)({
18540
19374
  envId,
18541
19375
  upstreamResourceName: upstreamName,
18542
19376
  upstreamResourceTypes: ["STATIC_STORE"],
18543
- getManager: async () => {
18544
- const manager = await getManager();
18545
- if (!manager) {
18546
- throw new Error("cloudbase manager unavailable");
18547
- }
18548
- return manager;
18549
- },
19377
+ getManager: getGatewayManager,
18550
19378
  });
18551
19379
  const preferred = (0, gateway_access_urls_js_1.preferGatewayOrFallback)({
18552
19380
  gateway,
18553
19381
  fallbackUrl: fallbackAccessUrl || undefined,
18554
19382
  fallbackSource: "hosting.staticDomain",
19383
+ fallbackReachable,
18555
19384
  });
18556
- if (preferred.accessUrl) {
18557
- accessUrl = preferred.accessUrl;
19385
+ if (preferred.accessUrl || preferred.disabledAccessUrls.length > 0) {
19386
+ accessUrl = preferred.accessUrl ?? "";
18558
19387
  accessUrls = preferred.accessUrls;
18559
19388
  accessUrlSource = preferred.accessUrlSource;
18560
- break;
19389
+ accessUrlReachable = preferred.accessUrlReachable;
19390
+ disabledAccessUrls = preferred.disabledAccessUrls;
19391
+ if (preferred.accessUrl) {
19392
+ break;
19393
+ }
18561
19394
  }
18562
19395
  }
18563
19396
  try {
@@ -18573,18 +19406,31 @@ function registerHostingTools(server) {
18573
19406
  projectName = path_1.default.basename(input.localPath);
18574
19407
  }
18575
19408
  }
18576
- await (0, notification_js_1.sendDeployNotification)(server, {
18577
- deployType: 'hosting',
18578
- url: accessUrl,
18579
- projectId: envId,
18580
- projectName,
18581
- consoleUrl: `https://tcb.cloud.tencent.com/dev?envId=${envId}#/static-hosting`,
18582
- });
19409
+ // sendDeployNotification requires a concrete url; skip when no reachable accessUrl
19410
+ if (accessUrl) {
19411
+ await (0, notification_js_1.sendDeployNotification)(server, {
19412
+ deployType: 'hosting',
19413
+ url: accessUrl,
19414
+ projectId: envId,
19415
+ projectName,
19416
+ consoleUrl: `https://tcb.cloud.tencent.com/dev?envId=${envId}#/static-hosting`,
19417
+ });
19418
+ }
18583
19419
  }
18584
19420
  catch {
18585
19421
  // Notification failure should not block uploads.
18586
19422
  }
18587
19423
  const uploadPrefix = input.cloudPath ?? input.files?.[0]?.cloudPath ?? '';
19424
+ const routeDisabled = staticDomainRouteEnabled === false;
19425
+ let message = '静态托管文件上传成功。若需要校验上传结果,请继续调用 queryHosting(action="findFiles") 或 queryHosting(action="listFiles")。';
19426
+ if (routeDisabled && accessUrlReachable) {
19427
+ message =
19428
+ '静态托管文件上传成功。默认静态托管域名的网关路由已禁用(访问会返回 GATEWAY_ROUTE_DISABLED),已改用其他可达域名作为 accessUrl。可用 manageGateway(action="updateRoute", route.enable=true) 重新启用默认域路由。';
19429
+ }
19430
+ else if (routeDisabled && !accessUrlReachable) {
19431
+ message =
19432
+ '静态托管文件上传成功,但默认静态托管域名的网关路由已禁用(GATEWAY_ROUTE_DISABLED),当前没有可达的 accessUrl。请用 manageGateway(action="updateRoute", domain="<静态域名>", path="/", upstreamResourceType="STATIC_STORE", targetName="staticstore", route.enable=true) 启用路由,或改用已启用的自定义域名 / CloudBase Sites 域名访问。';
19433
+ }
18588
19434
  return (0, tool_result_js_1.buildJsonToolResult)({
18589
19435
  success: true,
18590
19436
  data: {
@@ -18594,13 +19440,19 @@ function registerHostingTools(server) {
18594
19440
  files: input.files ?? [],
18595
19441
  ignore: input.ignore,
18596
19442
  staticDomain,
18597
- accessUrl,
19443
+ staticDomainRouteEnabled,
19444
+ accessUrl: accessUrl || undefined,
18598
19445
  accessUrls,
18599
19446
  accessUrlSource,
19447
+ accessUrlReachable,
19448
+ ...(disabledAccessUrls.length > 0
19449
+ ? { disabledAccessUrls }
19450
+ : {}),
19451
+ ...(routeDisabled ? { routeDisabled: true } : {}),
18600
19452
  result,
18601
19453
  nextActions: uploadPrefix ? [buildFindFilesNextStep(uploadPrefix.replace(/^\/+/, ''))] : undefined,
18602
19454
  },
18603
- message: '静态托管文件上传成功。若需要校验上传结果,请继续调用 queryHosting(action="findFiles") 或 queryHosting(action="listFiles")。',
19455
+ message,
18604
19456
  });
18605
19457
  }
18606
19458
  case 'delete': {
@@ -18803,6 +19655,7 @@ function registerHostingTools(server) {
18803
19655
  Object.defineProperty(exports, "__esModule", ({ value: true }));
18804
19656
  exports.buildVerificationUriComplete = buildVerificationUriComplete;
18805
19657
  exports.buildDeviceAuthChallengePayload = buildDeviceAuthChallengePayload;
19658
+ exports.getCloudBaseApiKeyFromEnv = getCloudBaseApiKeyFromEnv;
18806
19659
  exports.getAuthProgressStateSync = getAuthProgressStateSync;
18807
19660
  exports.getAuthProgressState = getAuthProgressState;
18808
19661
  exports.resolveAuthOptions = resolveAuthOptions;
@@ -18818,7 +19671,7 @@ exports.getLoginState = getLoginState;
18818
19671
  exports.logout = logout;
18819
19672
  const toolbox_1 = __webpack_require__(2090);
18820
19673
  const logger_js_1 = __webpack_require__(3039);
18821
- const tencent_cloud_js_1 = __webpack_require__(1454);
19674
+ const site_map_js_1 = __webpack_require__(8107);
18822
19675
  const auth = toolbox_1.AuthSupervisor.getInstance({});
18823
19676
  const authProgressState = {
18824
19677
  status: "IDLE",
@@ -18904,16 +19757,24 @@ function updateAuthProgressState(partial) {
18904
19757
  });
18905
19758
  return getAuthProgressStateSync();
18906
19759
  }
19760
+ /**
19761
+ * Resolve CloudBase API Key from env.
19762
+ * Prefer CLOUDBASE_API_KEY; fall back to CLOUDBASE_APIKEY (js-sdk / node-sdk convention).
19763
+ */
19764
+ function getCloudBaseApiKeyFromEnv() {
19765
+ return process.env.CLOUDBASE_API_KEY || process.env.CLOUDBASE_APIKEY || undefined;
19766
+ }
18907
19767
  function normalizeLoginStateFromEnvVars(options) {
18908
19768
  if (options?.ignoreEnvVars) {
18909
19769
  return null;
18910
19770
  }
18911
- // 优先检查 CloudBase API Key(优先级高于 TENCENTCLOUD_*)
18912
- const { CLOUDBASE_API_KEY, CLOUDBASE_ENV_ID: CLOUDBASE_ENV, } = process.env;
18913
- if (CLOUDBASE_API_KEY && CLOUDBASE_ENV) {
19771
+ // Prefer CloudBase API Key over TENCENTCLOUD_*
19772
+ const apiKey = getCloudBaseApiKeyFromEnv();
19773
+ const CLOUDBASE_ENV = process.env.CLOUDBASE_ENV_ID;
19774
+ if (apiKey && CLOUDBASE_ENV) {
18914
19775
  return {
18915
19776
  _type: 'api_key',
18916
- apiKey: CLOUDBASE_API_KEY,
19777
+ apiKey,
18917
19778
  envId: CLOUDBASE_ENV,
18918
19779
  };
18919
19780
  }
@@ -19059,6 +19920,98 @@ function resetAuthProgressState() {
19059
19920
  lastError: undefined,
19060
19921
  });
19061
19922
  }
19923
+ const LEGACY_SITE = "domestic";
19924
+ function isSlottedCredential(value) {
19925
+ if (!value || typeof value !== "object") {
19926
+ return false;
19927
+ }
19928
+ return "domestic" in value || "intl" in value;
19929
+ }
19930
+ async function readStoredCredentialRaw() {
19931
+ return toolbox_1.authStore.get("credential");
19932
+ }
19933
+ /**
19934
+ * 读取指定 site 槽位的原始凭证;旧格式(单槽 flat)视为 domestic 槽位。
19935
+ */
19936
+ async function readSiteCredentialRaw(site) {
19937
+ const raw = await readStoredCredentialRaw();
19938
+ if (!raw) {
19939
+ return undefined;
19940
+ }
19941
+ if (isSlottedCredential(raw)) {
19942
+ return raw[site];
19943
+ }
19944
+ return site === LEGACY_SITE ? raw : undefined;
19945
+ }
19946
+ /**
19947
+ * 将凭证写入指定 site 槽位,保留其他槽位数据。
19948
+ * base 缺省时读取当前存储;旧单槽数据会被迁移为 domestic 槽位(写回时升级分槽)。
19949
+ */
19950
+ async function writeSiteCredential(site, credential, base) {
19951
+ const current = base ?? (await readStoredCredentialRaw());
19952
+ const slotted = current && isSlottedCredential(current)
19953
+ ? { ...current }
19954
+ : { domestic: current ?? {} };
19955
+ slotted[site] = credential ?? {};
19956
+ await toolbox_1.authStore.set("credential", slotted);
19957
+ }
19958
+ /**
19959
+ * 确保存储已为分槽格式并返回槽位快照。
19960
+ * 用于登录前迁移旧单槽数据,避免 @cloudbase/toolbox 内部误命中旧凭证而跳过新站点登录。
19961
+ */
19962
+ async function ensureSlottedCredential() {
19963
+ const raw = await readStoredCredentialRaw();
19964
+ if (raw && isSlottedCredential(raw)) {
19965
+ return { ...raw };
19966
+ }
19967
+ const slotted = { domestic: raw ?? {} };
19968
+ await toolbox_1.authStore.set("credential", slotted);
19969
+ return slotted;
19970
+ }
19971
+ function isSlotTokenExpired(credential, gap = 120) {
19972
+ return (!!credential.accessTokenExpired &&
19973
+ Number(credential.accessTokenExpired) < Date.now() + gap * 1000);
19974
+ }
19975
+ /**
19976
+ * 读取并解析指定 site 的登录态,必要时走 refreshToken 续期(镜像 @cloudbase/toolbox 行为)。
19977
+ */
19978
+ async function resolveSiteLoginState(site) {
19979
+ const raw = await readSiteCredentialRaw(site);
19980
+ if (!raw) {
19981
+ return null;
19982
+ }
19983
+ const credential = (0, toolbox_1.resolveCredential)(raw);
19984
+ if (!credential?.secretId || !credential?.secretKey) {
19985
+ return null;
19986
+ }
19987
+ if (credential.refreshToken) {
19988
+ if (!isSlotTokenExpired(credential)) {
19989
+ return credential;
19990
+ }
19991
+ if (Date.now() < Number(credential.expired)) {
19992
+ try {
19993
+ const refreshed = await (0, toolbox_1.refreshTmpToken)(credential);
19994
+ await writeSiteCredential(site, refreshed ?? {});
19995
+ const resolved = (0, toolbox_1.resolveCredential)(refreshed ?? {});
19996
+ return resolved?.secretId ? resolved : null;
19997
+ }
19998
+ catch (e) {
19999
+ const code = e?.code;
20000
+ if (code === "AUTH_FAIL" || code === "InternalError.GetRoleError") {
20001
+ return null;
20002
+ }
20003
+ throw e;
20004
+ }
20005
+ }
20006
+ return null;
20007
+ }
20008
+ return credential;
20009
+ }
20010
+ function isUsableCredential(value) {
20011
+ return (!!value &&
20012
+ typeof value === "object" &&
20013
+ Object.keys(value).length > 0);
20014
+ }
19062
20015
  async function peekLoginState(options) {
19063
20016
  const envVarLoginState = normalizeLoginStateFromEnvVars(options);
19064
20017
  if (envVarLoginState) {
@@ -19080,14 +20033,25 @@ async function peekLoginState(options) {
19080
20033
  (0, logger_js_1.debug)("loginByApiSecret");
19081
20034
  return envVarLoginState;
19082
20035
  }
19083
- // 尝试从 toolbox 本地存储读取(包括项目级 API Key 凭证)
19084
- return auth.getLoginState();
20036
+ // 按 site 读取分槽凭证(缺省按 TCB_SITE/region 解析站点,默认 domestic)
20037
+ const site = (0, site_map_js_1.normalizeSite)(options?.site) ?? (0, site_map_js_1.resolveSite)(options?.region, process.env.TCB_SITE);
20038
+ const slotLoginState = await resolveSiteLoginState(site);
20039
+ if (slotLoginState) {
20040
+ return slotLoginState;
20041
+ }
20042
+ // 兼容旧单槽数据(视为 domestic),走 toolbox 读取/续期
20043
+ if (site === LEGACY_SITE) {
20044
+ return auth.getLoginState();
20045
+ }
20046
+ return null;
19085
20047
  }
19086
20048
  async function ensureLogin(options) {
19087
20049
  (0, logger_js_1.debug)("TENCENTCLOUD_SECRETID", { hasSecretId: !!process.env.TENCENTCLOUD_SECRETID });
19088
- (0, logger_js_1.debug)("CLOUDBASE_API_KEY", { hasApiKey: !!process.env.CLOUDBASE_API_KEY });
20050
+ (0, logger_js_1.debug)("CLOUDBASE_API_KEY", { hasApiKey: !!getCloudBaseApiKeyFromEnv() });
19089
20051
  const loginState = await peekLoginState({
19090
20052
  ignoreEnvVars: options?.ignoreEnvVars,
20053
+ site: options?.site,
20054
+ region: options?.region,
19091
20055
  });
19092
20056
  if (!loginState) {
19093
20057
  const resolvedAuthOptions = resolveAuthOptions({
@@ -19103,21 +20067,31 @@ async function ensureLogin(options) {
19103
20067
  throw new Error(validationError);
19104
20068
  }
19105
20069
  const mode = resolvedAuthOptions.authMode;
20070
+ // 按显式 site > TCB_SITE > region 映射表解析站点(ap-singapore 歧义默认 intl,兼容既有国际站行为)
20071
+ const resolvedSite = (0, site_map_js_1.resolveSite)(options?.region, options?.site ?? process.env.TCB_SITE);
19106
20072
  const loginOptions = { flow: mode };
19107
20073
  if (mode === "web") {
19108
20074
  loginOptions.getAuthUrl =
19109
- options?.fromCloudBaseLoginPage && !(0, tencent_cloud_js_1.isInternationalRegion)(options?.region)
20075
+ options?.fromCloudBaseLoginPage && resolvedSite === "domestic"
19110
20076
  ? (url) => {
19111
20077
  const separator = url.includes("?") ? "&" : "?";
19112
20078
  const urlWithParam = `${url}${separator}allowNoEnv=true`;
19113
- return `https://tcb.cloud.tencent.com/login?_redirect_uri=${encodeURIComponent(urlWithParam)}`;
20079
+ return `https://${site_map_js_1.SITE_REGION_MAP.domestic.authHost}/login?_redirect_uri=${encodeURIComponent(urlWithParam)}`;
19114
20080
  }
19115
20081
  : (url) => {
19116
- if ((0, tencent_cloud_js_1.isInternationalRegion)(options?.region)) {
19117
- url = url.replace("cloud.tencent.com", "tencentcloud.com");
20082
+ let finalUrl = url;
20083
+ if (resolvedSite === "intl") {
20084
+ try {
20085
+ const parsed = new URL(url);
20086
+ parsed.host = site_map_js_1.SITE_REGION_MAP.intl.authHost;
20087
+ finalUrl = parsed.toString();
20088
+ }
20089
+ catch {
20090
+ finalUrl = url.replace("cloud.tencent.com", "tencentcloud.com");
20091
+ }
19118
20092
  }
19119
- const separator = url.includes("?") ? "&" : "?";
19120
- return `${url}${separator}allowNoEnv=true`;
20093
+ const separator = finalUrl.includes("?") ? "&" : "?";
20094
+ return `${finalUrl}${separator}allowNoEnv=true`;
19121
20095
  };
19122
20096
  }
19123
20097
  else {
@@ -19138,8 +20112,23 @@ async function ensureLogin(options) {
19138
20112
  }
19139
20113
  }
19140
20114
  (0, logger_js_1.debug)("beforeloginByWebAuth", { loginOptions });
20115
+ // 登录前迁移旧单槽数据为分槽格式,避免 toolbox 内部命中旧凭证而跳过新站点登录
20116
+ const slotsBefore = await ensureSlottedCredential();
19141
20117
  try {
19142
- await auth.loginByWebAuth(loginOptions);
20118
+ const loginResult = await auth.loginByWebAuth(loginOptions);
20119
+ // toolbox 将新凭证写入 flat 'credential',将其并入对应 site 槽位,保留其他槽位
20120
+ const newRaw = await readStoredCredentialRaw();
20121
+ let credentialToSlot = loginResult;
20122
+ if (isSlottedCredential(newRaw)) {
20123
+ credentialToSlot = newRaw[resolvedSite] ?? newRaw.domestic;
20124
+ }
20125
+ else if (newRaw) {
20126
+ credentialToSlot = newRaw;
20127
+ }
20128
+ if (isUsableCredential(credentialToSlot)) {
20129
+ const merged = { ...slotsBefore, [resolvedSite]: credentialToSlot };
20130
+ await toolbox_1.authStore.set("credential", merged);
20131
+ }
19143
20132
  resolveAuthProgressState();
19144
20133
  }
19145
20134
  catch (error) {
@@ -19148,6 +20137,8 @@ async function ensureLogin(options) {
19148
20137
  }
19149
20138
  const loginState = await peekLoginState({
19150
20139
  ignoreEnvVars: options?.ignoreEnvVars,
20140
+ site: options?.site,
20141
+ region: options?.region,
19151
20142
  });
19152
20143
  (0, logger_js_1.debug)("loginByWebAuth", { mode, hasLoginState: !!loginState });
19153
20144
  return loginState;
@@ -19160,11 +20151,19 @@ async function ensureLogin(options) {
19160
20151
  async function getLoginState(options) {
19161
20152
  return ensureLogin(options);
19162
20153
  }
19163
- async function logout() {
19164
- const cwd = process.env.WORKSPACE_FOLDER_PATHS || process.cwd();
19165
- const result = await auth.logout({ cwd });
20154
+ async function logout(options) {
20155
+ const site = (0, site_map_js_1.normalizeSite)(options?.site) ?? "domestic";
20156
+ const raw = await readStoredCredentialRaw();
20157
+ if (raw && isSlottedCredential(raw)) {
20158
+ const slotted = { ...raw };
20159
+ delete slotted[site];
20160
+ await toolbox_1.authStore.set("credential", slotted);
20161
+ }
20162
+ else if (site === LEGACY_SITE) {
20163
+ const cwd = process.env.WORKSPACE_FOLDER_PATHS || process.cwd();
20164
+ await auth.logout({ cwd });
20165
+ }
19166
20166
  resetAuthProgressState();
19167
- return result;
19168
20167
  }
19169
20168
 
19170
20169
 
@@ -19189,6 +20188,126 @@ module.exports = __WEBPACK_EXTERNAL_MODULE_crypto__;
19189
20188
 
19190
20189
  module.exports = __WEBPACK_EXTERNAL_MODULE_fs_promises_f8dae9d1__;
19191
20190
 
20191
+ /***/ }),
20192
+
20193
+ /***/ 8107:
20194
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
20195
+
20196
+
20197
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
20198
+ exports.SITE_IDS = exports.SITE_REGION_MAP = void 0;
20199
+ exports.isSiteId = isSiteId;
20200
+ exports.normalizeSite = normalizeSite;
20201
+ exports.getSite = getSite;
20202
+ exports.resolveSite = resolveSite;
20203
+ exports.resolveSiteAndRegion = resolveSiteAndRegion;
20204
+ const project_config_js_1 = __webpack_require__(8445);
20205
+ exports.SITE_REGION_MAP = {
20206
+ domestic: {
20207
+ id: "domestic",
20208
+ label: "国内站",
20209
+ authHost: "tcb.cloud.tencent.com",
20210
+ consoleHost: "tcb.cloud.tencent.com",
20211
+ defaultRegion: "ap-shanghai",
20212
+ regions: ["ap-shanghai", "ap-guangzhou", "ap-singapore"],
20213
+ capabilities: { noSql: true },
20214
+ },
20215
+ intl: {
20216
+ id: "intl",
20217
+ label: "国际站",
20218
+ authHost: "tcb.tencentcloud.com",
20219
+ consoleHost: "tcb.tencentcloud.com",
20220
+ defaultRegion: "ap-singapore",
20221
+ regions: ["ap-singapore"],
20222
+ capabilities: { noSql: false },
20223
+ },
20224
+ };
20225
+ exports.SITE_IDS = Object.keys(exports.SITE_REGION_MAP);
20226
+ function isSiteId(value) {
20227
+ return value === "domestic" || value === "intl";
20228
+ }
20229
+ function normalizeSite(value) {
20230
+ if (typeof value !== "string") {
20231
+ return undefined;
20232
+ }
20233
+ const normalized = value.trim().toLowerCase();
20234
+ if (normalized === "intl" || normalized === "international") {
20235
+ return "intl";
20236
+ }
20237
+ if (normalized === "domestic" || normalized === "cn") {
20238
+ return "domestic";
20239
+ }
20240
+ return undefined;
20241
+ }
20242
+ /**
20243
+ * 根据 region 与显式 site 解析站点。
20244
+ *
20245
+ * - 显式 site 优先(`domestic`/`intl`,兼容 `cn`/`international` 别名)
20246
+ * - 否则查 SITE_REGION_MAP:region 只属于一个站点时返回该站点
20247
+ * - 当 region 同时存在于多个站点(如 ap-singapore)时返回 `ambiguous`,由上层决定
20248
+ * - 未知 region 或未提供 region 时回退 domestic(全局默认)
20249
+ */
20250
+ function getSite(region, explicitSite) {
20251
+ const explicit = normalizeSite(explicitSite);
20252
+ if (explicit) {
20253
+ return explicit;
20254
+ }
20255
+ if (!region) {
20256
+ return "domestic";
20257
+ }
20258
+ const matches = exports.SITE_IDS.filter((site) => exports.SITE_REGION_MAP[site].regions.includes(region));
20259
+ if (matches.length === 1) {
20260
+ return matches[0];
20261
+ }
20262
+ if (matches.length > 1) {
20263
+ return "ambiguous";
20264
+ }
20265
+ return "domestic";
20266
+ }
20267
+ /**
20268
+ * 认证/路由场景下解析站点:歧义(如 ap-singapore 未配 site)时默认 intl,保持既有国际站行为。
20269
+ */
20270
+ function resolveSite(region, explicitSite) {
20271
+ const site = getSite(region, explicitSite);
20272
+ return site === "ambiguous" ? "intl" : site;
20273
+ }
20274
+ /**
20275
+ * 统一 site/region 解析入口(收敛各调用点分散的 region fallback)。
20276
+ *
20277
+ * 优先级:显式参数 > 环境变量(TCB_SITE/TCB_REGION)> 项目配置(.cloudbase/project.json)> 全局默认
20278
+ * (site=domestic, region=ap-shanghai)。
20279
+ *
20280
+ * 当仅指定 region 且该 region 在多个 site 的地域列表中都存在(如 ap-singapore)时,
20281
+ * 返回 site=intl 并标记 ambiguous=true(兼容既有国际站行为),调用方可用 ambiguous 提示用户显式指定 site。
20282
+ */
20283
+ function resolveSiteAndRegion(opts = {}) {
20284
+ const projectConfig = (0, project_config_js_1.readProjectConfig)();
20285
+ const explicitSite = normalizeSite(opts.site) ??
20286
+ normalizeSite(process.env.TCB_SITE) ??
20287
+ normalizeSite(projectConfig?.site);
20288
+ const explicitRegion = opts.region ?? process.env.TCB_REGION ?? projectConfig?.region;
20289
+ let site = explicitSite;
20290
+ let ambiguous = false;
20291
+ if (!site && explicitRegion) {
20292
+ const inferred = getSite(explicitRegion);
20293
+ if (inferred === "ambiguous") {
20294
+ site = "intl";
20295
+ ambiguous = true;
20296
+ }
20297
+ else if (inferred !== "domestic") {
20298
+ site = inferred;
20299
+ }
20300
+ }
20301
+ const resolvedSite = site ?? "domestic";
20302
+ const region = explicitRegion ?? exports.SITE_REGION_MAP[resolvedSite].defaultRegion;
20303
+ return {
20304
+ site: resolvedSite,
20305
+ region,
20306
+ ...(ambiguous ? { ambiguous: true } : {}),
20307
+ };
20308
+ }
20309
+
20310
+
19192
20311
  /***/ }),
19193
20312
 
19194
20313
  /***/ 8116:
@@ -19411,418 +20530,38 @@ function listLikelyRedeployFields(config) {
19411
20530
 
19412
20531
  /***/ }),
19413
20532
 
19414
- /***/ 9097:
19415
- /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
20533
+ /***/ 8445:
20534
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
19416
20535
 
19417
20536
 
19418
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19419
- if (k2 === undefined) k2 = k;
19420
- var desc = Object.getOwnPropertyDescriptor(m, k);
19421
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19422
- desc = { enumerable: true, get: function() { return m[k]; } };
19423
- }
19424
- Object.defineProperty(o, k2, desc);
19425
- }) : (function(o, m, k, k2) {
19426
- if (k2 === undefined) k2 = k;
19427
- o[k2] = m[k];
19428
- }));
19429
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19430
- Object.defineProperty(o, "default", { enumerable: true, value: v });
19431
- }) : function(o, v) {
19432
- o["default"] = v;
19433
- });
19434
- var __importStar = (this && this.__importStar) || (function () {
19435
- var ownKeys = function(o) {
19436
- ownKeys = Object.getOwnPropertyNames || function (o) {
19437
- var ar = [];
19438
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
19439
- return ar;
19440
- };
19441
- return ownKeys(o);
19442
- };
19443
- return function (mod) {
19444
- if (mod && mod.__esModule) return mod;
19445
- var result = {};
19446
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
19447
- __setModuleDefault(result, mod);
19448
- return result;
19449
- };
19450
- })();
19451
20537
  Object.defineProperty(exports, "__esModule", ({ value: true }));
19452
- exports.registerDownloadTools = registerDownloadTools;
19453
- const crypto = __importStar(__webpack_require__(7823));
19454
- const fs = __importStar(__webpack_require__(4421));
19455
- const fsPromises = __importStar(__webpack_require__(7932));
19456
- const http = __importStar(__webpack_require__(3782));
19457
- const https = __importStar(__webpack_require__(3871));
19458
- const os = __importStar(__webpack_require__(8116));
19459
- const path = __importStar(__webpack_require__(2521));
19460
- const url_1 = __webpack_require__(6887);
19461
- const zod_1 = __webpack_require__(2971);
19462
- const remote_url_safety_js_1 = __webpack_require__(9192);
19463
- // 常量定义
19464
- const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
19465
- const MAX_REDIRECT_HOPS = 5;
19466
- const ALLOWED_CONTENT_TYPES = [
19467
- "text/",
19468
- "image/",
19469
- "application/json",
19470
- "application/xml",
19471
- "application/pdf",
19472
- "application/zip",
19473
- "application/x-zip-compressed"
19474
- ];
19475
- // 获取项目根目录
19476
- function getProjectRoot() {
19477
- // 优先级:环境变量 > 当前工作目录
19478
- return process.env.WORKSPACE_FOLDER_PATHS ||
19479
- process.env.PROJECT_ROOT ||
19480
- process.env.GITHUB_WORKSPACE ||
19481
- process.env.CI_PROJECT_DIR ||
19482
- process.env.BUILD_SOURCESDIRECTORY ||
19483
- process.cwd();
19484
- }
19485
- // 验证相对路径是否安全(不允许路径遍历)
19486
- function isPathSafe(relativePath) {
19487
- // 检查是否包含路径遍历操作
19488
- if (relativePath.includes('..') ||
19489
- relativePath.includes('~') ||
19490
- path.isAbsolute(relativePath)) {
19491
- return false;
19492
- }
19493
- // 检查路径是否规范化后仍然安全
19494
- const normalizedPath = path.normalize(relativePath);
19495
- if (normalizedPath.startsWith('..') ||
19496
- normalizedPath.startsWith('/') ||
19497
- normalizedPath.startsWith('\\')) {
19498
- return false;
19499
- }
19500
- return true;
19501
- }
19502
- // 计算最终下载路径
19503
- function calculateDownloadPath(relativePath) {
19504
- const projectRoot = getProjectRoot();
19505
- const finalPath = path.join(projectRoot, relativePath);
19506
- // 确保最终路径在项目根目录内
19507
- const normalizedProjectRoot = path.resolve(projectRoot);
19508
- const normalizedFinalPath = path.resolve(finalPath);
19509
- if (!normalizedFinalPath.startsWith(normalizedProjectRoot)) {
19510
- throw new Error('相对路径超出项目根目录范围');
19511
- }
19512
- return finalPath;
19513
- }
19514
- // 生成随机文件名
19515
- function generateRandomFileName(extension = '') {
19516
- const randomBytes = crypto.randomBytes(16);
19517
- const fileName = randomBytes.toString('hex');
19518
- return `${fileName}${extension}`;
19519
- }
19520
- // 获取安全的临时文件路径
19521
- function getSafeTempFilePath(fileName) {
19522
- return path.join(os.tmpdir(), fileName);
19523
- }
19524
- // 从 URL 或 Content-Disposition 获取文件扩展名
19525
- function getFileExtension(url, contentType, contentDisposition) {
19526
- let extension = "";
19527
- // 从 URL 获取扩展名
19528
- const urlPath = new url_1.URL(url).pathname;
19529
- const urlExt = path.extname(urlPath);
19530
- if (urlExt) {
19531
- extension = urlExt;
19532
- }
19533
- // 从 Content-Disposition 获取扩展名
19534
- if (contentDisposition) {
19535
- const filenameMatch = contentDisposition.match(/filename=["']?([^"']+)["']?/);
19536
- if (filenameMatch) {
19537
- const dispositionExt = path.extname(filenameMatch[1]);
19538
- if (dispositionExt) {
19539
- extension = dispositionExt;
19540
- }
20538
+ exports.readProjectConfig = readProjectConfig;
20539
+ const node_fs_1 = __webpack_require__(9627);
20540
+ const node_path_1 = __webpack_require__(5663);
20541
+ /**
20542
+ * 读取项目级配置 `.cloudbase/project.json`。
20543
+ *
20544
+ * 该文件是 site/region/envId 的可选持久化来源,让"打开项目即连对服务"
20545
+ * (对齐 Vercel `.vercel/project.json` 的三级回退)。读取失败/文件不存在
20546
+ * 返回 undefined,不阻塞调用方。
20547
+ */
20548
+ function readProjectConfig(cwd) {
20549
+ try {
20550
+ const projectRoot = cwd ?? process.env.WORKSPACE_FOLDER_PATHS ?? process.cwd();
20551
+ const configPath = (0, node_path_1.join)(projectRoot, ".cloudbase", "project.json");
20552
+ if (!(0, node_fs_1.existsSync)(configPath)) {
20553
+ return undefined;
19541
20554
  }
20555
+ const raw = (0, node_fs_1.readFileSync)(configPath, "utf-8");
20556
+ const parsed = JSON.parse(raw);
20557
+ if (!parsed || typeof parsed !== "object") {
20558
+ return undefined;
20559
+ }
20560
+ return parsed;
19542
20561
  }
19543
- // 从 Content-Type 获取扩展名
19544
- if (!extension && contentType) {
19545
- const mimeToExt = {
19546
- "text/plain": ".txt",
19547
- "text/html": ".html",
19548
- "text/css": ".css",
19549
- "text/javascript": ".js",
19550
- "image/jpeg": ".jpg",
19551
- "image/png": ".png",
19552
- "image/gif": ".gif",
19553
- "image/webp": ".webp",
19554
- "application/json": ".json",
19555
- "application/xml": ".xml",
19556
- "application/pdf": ".pdf",
19557
- "application/zip": ".zip",
19558
- "application/x-zip-compressed": ".zip"
19559
- };
19560
- extension = mimeToExt[contentType] || "";
19561
- }
19562
- return extension;
19563
- }
19564
- function isAllowedContentType(contentType) {
19565
- return ALLOWED_CONTENT_TYPES.some((allowedType) => contentType.startsWith(allowedType));
19566
- }
19567
- // 检查是否为可重试的网络错误
19568
- function isRetryableError(error) {
19569
- const retryableCodes = [
19570
- 'ECONNRESET',
19571
- 'ECONNREFUSED',
19572
- 'ETIMEDOUT',
19573
- 'ESOCKETTIMEDOUT',
19574
- 'ENOTFOUND',
19575
- 'EHOSTUNREACH',
19576
- 'ENETUNREACH',
19577
- 'ECONNABORTED'
19578
- ];
19579
- return retryableCodes.includes(error.code || '');
19580
- }
19581
- // 下载文件到指定路径(带重试逻辑)
19582
- async function downloadFileToPath(url, targetPath, retryCount = 0, redirectCount = 0) {
19583
- const MAX_RETRIES = 3;
19584
- const BASE_DELAY_MS = 1000; // 1秒基础延迟
19585
- if (redirectCount > MAX_REDIRECT_HOPS) {
19586
- throw new Error(`重定向次数超过 ${MAX_REDIRECT_HOPS} 次限制`);
19587
- }
19588
- const { requestUrl, requestOptions } = await (0, remote_url_safety_js_1.prepareSafeRemoteRequest)(url);
19589
- const client = requestUrl.protocol === "https:" ? https : http;
19590
- return new Promise((resolve, reject) => {
19591
- const request = client.get(requestUrl, requestOptions ?? {}, async (res) => {
19592
- // 处理重定向
19593
- if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
19594
- try {
19595
- const redirectUrl = new url_1.URL(res.headers.location, requestUrl).toString();
19596
- return resolve(await downloadFileToPath(redirectUrl, targetPath, retryCount, redirectCount + 1));
19597
- }
19598
- catch (error) {
19599
- return reject(error);
19600
- }
19601
- }
19602
- if (res.statusCode !== 200) {
19603
- reject(new Error(`HTTP Error: ${res.statusCode}`));
19604
- return;
19605
- }
19606
- const contentType = res.headers["content-type"] || "";
19607
- const contentLength = parseInt(res.headers["content-length"] || "0", 10);
19608
- if (!isAllowedContentType(contentType)) {
19609
- reject(new Error(`不允许的内容类型: ${contentType || "unknown"}`));
19610
- return;
19611
- }
19612
- // 文件大小检查
19613
- if (contentLength > MAX_FILE_SIZE) {
19614
- reject(new Error(`文件大小 ${contentLength} 字节超过 ${MAX_FILE_SIZE} 字节限制`));
19615
- return;
19616
- }
19617
- // 确保目标目录存在
19618
- const targetDir = path.dirname(targetPath);
19619
- try {
19620
- await fsPromises.mkdir(targetDir, { recursive: true });
19621
- }
19622
- catch (error) {
19623
- reject(new Error(`无法创建目标目录: ${error instanceof Error ? error.message : "未知错误"}`));
19624
- return;
19625
- }
19626
- // 创建写入流
19627
- const fileStream = fs.createWriteStream(targetPath);
19628
- let downloadedSize = 0;
19629
- res.on("data", (chunk) => {
19630
- downloadedSize += chunk.length;
19631
- if (downloadedSize > MAX_FILE_SIZE) {
19632
- fileStream.destroy();
19633
- fsPromises.unlink(targetPath).catch(() => { });
19634
- reject(new Error(`文件大小超过 ${MAX_FILE_SIZE} 字节限制`));
19635
- }
19636
- });
19637
- res.pipe(fileStream);
19638
- fileStream.on("finish", () => {
19639
- resolve({
19640
- filePath: targetPath,
19641
- contentType,
19642
- fileSize: downloadedSize,
19643
- });
19644
- });
19645
- fileStream.on("error", (error) => {
19646
- fsPromises.unlink(targetPath).catch(() => { });
19647
- reject(error);
19648
- });
19649
- });
19650
- request.on("error", async (error) => {
19651
- // 检查是否可重试
19652
- if (isRetryableError(error) && retryCount < MAX_RETRIES) {
19653
- const delay = BASE_DELAY_MS * Math.pow(2, retryCount); // 指数退避
19654
- console.warn(`下载失败 (${error.code}),${delay}ms 后重试 (${retryCount + 1}/${MAX_RETRIES})...`);
19655
- await new Promise((resolveDelay) => setTimeout(resolveDelay, delay));
19656
- try {
19657
- return resolve(await downloadFileToPath(url, targetPath, retryCount + 1, redirectCount));
19658
- }
19659
- catch (retryError) {
19660
- return reject(retryError);
19661
- }
19662
- }
19663
- reject(error);
19664
- });
19665
- });
19666
- }
19667
- // 下载文件到临时目录(保持向后兼容,带重试逻辑)
19668
- async function downloadFile(url, retryCount = 0, redirectCount = 0) {
19669
- const MAX_RETRIES = 3;
19670
- const BASE_DELAY_MS = 1000; // 1秒基础延迟
19671
- if (redirectCount > MAX_REDIRECT_HOPS) {
19672
- throw new Error(`重定向次数超过 ${MAX_REDIRECT_HOPS} 次限制`);
19673
- }
19674
- const { requestUrl, requestOptions } = await (0, remote_url_safety_js_1.prepareSafeRemoteRequest)(url);
19675
- const client = requestUrl.protocol === "https:" ? https : http;
19676
- return new Promise((resolve, reject) => {
19677
- const request = client.get(requestUrl, requestOptions ?? {}, async (res) => {
19678
- // 处理重定向
19679
- if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
19680
- try {
19681
- const redirectUrl = new url_1.URL(res.headers.location, requestUrl).toString();
19682
- return resolve(await downloadFile(redirectUrl, retryCount, redirectCount + 1));
19683
- }
19684
- catch (error) {
19685
- return reject(error);
19686
- }
19687
- }
19688
- if (res.statusCode !== 200) {
19689
- reject(new Error(`HTTP Error: ${res.statusCode}`));
19690
- return;
19691
- }
19692
- const contentType = res.headers["content-type"] || "";
19693
- const contentLength = parseInt(res.headers["content-length"] || "0", 10);
19694
- const contentDisposition = res.headers["content-disposition"];
19695
- if (!isAllowedContentType(contentType)) {
19696
- reject(new Error(`不允许的内容类型: ${contentType || "unknown"}`));
19697
- return;
19698
- }
19699
- // 文件大小检查
19700
- if (contentLength > MAX_FILE_SIZE) {
19701
- reject(new Error(`文件大小 ${contentLength} 字节超过 ${MAX_FILE_SIZE} 字节限制`));
19702
- return;
19703
- }
19704
- // 生成临时文件路径
19705
- const extension = getFileExtension(requestUrl.toString(), contentType, contentDisposition);
19706
- const fileName = generateRandomFileName(extension);
19707
- const filePath = getSafeTempFilePath(fileName);
19708
- // 创建写入流
19709
- const fileStream = fs.createWriteStream(filePath);
19710
- let downloadedSize = 0;
19711
- res.on("data", (chunk) => {
19712
- downloadedSize += chunk.length;
19713
- if (downloadedSize > MAX_FILE_SIZE) {
19714
- fileStream.destroy();
19715
- fsPromises.unlink(filePath).catch(() => { });
19716
- reject(new Error(`文件大小超过 ${MAX_FILE_SIZE} 字节限制`));
19717
- }
19718
- });
19719
- res.pipe(fileStream);
19720
- fileStream.on("finish", () => {
19721
- resolve({
19722
- filePath,
19723
- contentType,
19724
- fileSize: downloadedSize,
19725
- });
19726
- });
19727
- fileStream.on("error", (error) => {
19728
- fsPromises.unlink(filePath).catch(() => { });
19729
- reject(error);
19730
- });
19731
- });
19732
- request.on("error", async (error) => {
19733
- // 检查是否可重试
19734
- if (isRetryableError(error) && retryCount < MAX_RETRIES) {
19735
- const delay = BASE_DELAY_MS * Math.pow(2, retryCount); // 指数退避
19736
- console.warn(`下载失败 (${error.code}),${delay}ms 后重试 (${retryCount + 1}/${MAX_RETRIES})...`);
19737
- await new Promise((resolveDelay) => setTimeout(resolveDelay, delay));
19738
- try {
19739
- return resolve(await downloadFile(url, retryCount + 1, redirectCount));
19740
- }
19741
- catch (retryError) {
19742
- return reject(retryError);
19743
- }
19744
- }
19745
- reject(error);
19746
- });
19747
- });
19748
- }
19749
- function buildDownloadRemoteFileErrorMessage(url, error) {
19750
- const baseMessage = error instanceof Error ? error.message : String(error);
19751
- const suggestions = [];
19752
- if (/HTTP Error:\s*404/i.test(baseMessage)) {
19753
- suggestions.push("目标 URL 当前返回 404,说明资源链接已失效或路径不正确。");
19754
- suggestions.push("请先在浏览器中确认该链接可访问,再重新执行下载。");
19755
- }
19756
- if (/ENOTFOUND|ECONNRESET|ETIMEDOUT|socket hang up/i.test(baseMessage)) {
19757
- suggestions.push("下载过程中出现网络异常,系统已自动重试 3 次但均失败。");
19758
- suggestions.push("建议:1) 检查本地网络或代理设置;2) 该网站可能限频,请尝试更换其他图片源(如 Unsplash、Pexels);3) 稍后重试。");
19759
- }
19760
- if (suggestions.length === 0) {
19761
- suggestions.push("请检查 URL 是否可访问,以及目标资源是否允许直接下载。");
20562
+ catch {
20563
+ return undefined;
19762
20564
  }
19763
- return `[downloadRemoteFile] ${baseMessage}\nURL: ${url}\n建议:${suggestions.join(" ")}`;
19764
- }
19765
- function registerDownloadTools(server) {
19766
- server.registerTool("downloadRemoteFile", {
19767
- title: "下载远程文件到指定路径",
19768
- description: "下载远程文件到项目根目录下的指定相对路径。例如:小程序的 Tabbar 等素材图片,必须使用 **png** 格式,可以从 Unsplash、wikimedia【一般选用 500 大小即可、Pexels、Apple 官方 UI 等资源中选择来下载。",
19769
- inputSchema: {
19770
- url: zod_1.z.string().describe("远程文件的 URL 地址"),
19771
- relativePath: zod_1.z.string().describe("相对于项目根目录的路径,例如:'assets/images/logo.png' 或 'docs/api.md'。不允许使用 ../ 等路径遍历操作。")
19772
- },
19773
- annotations: {
19774
- readOnlyHint: false,
19775
- destructiveHint: false,
19776
- idempotentHint: false,
19777
- openWorldHint: true,
19778
- category: "download"
19779
- }
19780
- }, async ({ url, relativePath }) => {
19781
- if (!isPathSafe(relativePath)) {
19782
- return {
19783
- content: [
19784
- {
19785
- type: "text",
19786
- text: JSON.stringify({
19787
- success: false,
19788
- error: "不安全的相对路径",
19789
- message: "相对路径包含路径遍历操作(../)或绝对路径,出于安全考虑已拒绝",
19790
- suggestion: "请使用项目根目录下的相对路径,例如:'assets/images/logo.png'"
19791
- }, null, 2)
19792
- }
19793
- ]
19794
- };
19795
- }
19796
- const targetPath = calculateDownloadPath(relativePath);
19797
- const projectRoot = getProjectRoot();
19798
- console.log(`📁 项目根目录: ${projectRoot}`);
19799
- console.log(`📁 相对路径: ${relativePath}`);
19800
- console.log(`📁 最终路径: ${targetPath}`);
19801
- let result;
19802
- try {
19803
- result = await downloadFileToPath(url, targetPath);
19804
- }
19805
- catch (error) {
19806
- throw new Error(buildDownloadRemoteFileErrorMessage(url, error));
19807
- }
19808
- return {
19809
- content: [
19810
- {
19811
- type: "text",
19812
- text: JSON.stringify({
19813
- success: true,
19814
- filePath: result.filePath,
19815
- relativePath: relativePath,
19816
- contentType: result.contentType,
19817
- fileSize: result.fileSize,
19818
- projectRoot: projectRoot,
19819
- message: "文件下载成功到指定路径",
19820
- note: `文件已保存到项目目录: ${relativePath}`
19821
- }, null, 2)
19822
- }
19823
- ]
19824
- };
19825
- });
19826
20565
  }
19827
20566
 
19828
20567
 
@@ -20517,6 +21256,13 @@ function registerAgentTools(server) {
20517
21256
  }
20518
21257
 
20519
21258
 
21259
+ /***/ }),
21260
+
21261
+ /***/ 9627:
21262
+ /***/ ((module) => {
21263
+
21264
+ module.exports = __WEBPACK_EXTERNAL_MODULE_node_fs_75ed2103__;
21265
+
20520
21266
  /***/ }),
20521
21267
 
20522
21268
  /***/ 9684:
@@ -20591,7 +21337,6 @@ function shouldRegisterTool(toolName) {
20591
21337
  'createFunction', // also involves local files
20592
21338
  // Download tools - local file downloads
20593
21339
  'downloadTemplate',
20594
- 'downloadRemoteFile',
20595
21340
  // Setup tools - local config file operations
20596
21341
  'setupEnvironmentId',
20597
21342
  // CloudRun tools - local file operations