@cloudbase/cloudbase-mcp 2.25.10 → 2.26.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.cjs CHANGED
@@ -3321,6 +3321,7 @@ const auth_js_1 = __webpack_require__(77291);
3321
3321
  const cloudbase_manager_js_1 = __webpack_require__(3431);
3322
3322
  const logger_js_1 = __webpack_require__(13039);
3323
3323
  const tool_result_js_1 = __webpack_require__(9835);
3324
+ const gateway_access_urls_js_1 = __webpack_require__(22435);
3324
3325
  const env_setup_js_1 = __webpack_require__(6978);
3325
3326
  /**
3326
3327
  * Resources accepted by CreateEnv. Matches Cloud API / Manager SDK contract.
@@ -4073,6 +4074,89 @@ async function enrichEnvInfoWithMissingFields(manager, result, envId) {
4073
4074
  return result;
4074
4075
  }
4075
4076
  }
4077
+ /**
4078
+ * Project gateway Route.Enable onto envQuery(info) without mutating StaticDomain.
4079
+ *
4080
+ * StaticStorages[].StaticDomain stays the cloud-API nominal hostname.
4081
+ * Sibling field staticDomainRouteEnabled (and EnvInfo-level mirrors for the
4082
+ * primary store) tell callers whether the default hosting domain route is
4083
+ * actually reachable (Enable !== false). Matches queryHosting websiteConfig.
4084
+ */
4085
+ async function enrichEnvInfoWithStaticDomainRouteEnabled(manager, result, envId) {
4086
+ const envInfo = result?.EnvInfo;
4087
+ if (!envInfo || typeof envInfo !== "object") {
4088
+ return result;
4089
+ }
4090
+ const staticStorages = Array.isArray(envInfo.StaticStorages)
4091
+ ? envInfo.StaticStorages
4092
+ : null;
4093
+ if (!staticStorages || staticStorages.length === 0) {
4094
+ return result;
4095
+ }
4096
+ if (typeof manager?.env?.describeHttpServiceRoute !== "function") {
4097
+ return result;
4098
+ }
4099
+ const resolvedEnvId = (typeof envId === "string" && envId.trim()) ||
4100
+ (typeof envInfo.EnvId === "string" && envInfo.EnvId.trim()) ||
4101
+ undefined;
4102
+ if (!resolvedEnvId) {
4103
+ return result;
4104
+ }
4105
+ try {
4106
+ const routeResult = await manager.env.describeHttpServiceRoute({
4107
+ EnvId: resolvedEnvId,
4108
+ Limit: 1000,
4109
+ });
4110
+ const routes = (0, gateway_access_urls_js_1.flattenHttpServiceRoutes)(routeResult);
4111
+ let primaryRouteEnabled = null;
4112
+ const enrichedStorages = staticStorages.map((store, index) => {
4113
+ if (!store || typeof store !== "object" || Array.isArray(store)) {
4114
+ return store;
4115
+ }
4116
+ const record = store;
4117
+ const domain = typeof record.StaticDomain === "string" ? record.StaticDomain : "";
4118
+ if (!domain) {
4119
+ return store;
4120
+ }
4121
+ const reachable = (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(routes, domain, "/");
4122
+ if (index === 0) {
4123
+ primaryRouteEnabled = reachable;
4124
+ }
4125
+ return {
4126
+ ...record,
4127
+ staticDomainRouteEnabled: reachable,
4128
+ };
4129
+ });
4130
+ const enrichment = {
4131
+ StaticStorages: enrichedStorages,
4132
+ };
4133
+ if (primaryRouteEnabled !== null) {
4134
+ enrichment.staticDomainRouteEnabled = primaryRouteEnabled;
4135
+ enrichment.accessUrlReachable = primaryRouteEnabled !== false;
4136
+ if (primaryRouteEnabled === false) {
4137
+ const primaryDomain = typeof enrichedStorages[0]
4138
+ ?.StaticDomain === "string"
4139
+ ? String(enrichedStorages[0].StaticDomain)
4140
+ : "";
4141
+ enrichment.routeDisabled = true;
4142
+ if (primaryDomain) {
4143
+ enrichment.disabledAccessUrls = [`https://${primaryDomain}/`];
4144
+ }
4145
+ }
4146
+ }
4147
+ return {
4148
+ ...result,
4149
+ EnvInfo: {
4150
+ ...envInfo,
4151
+ ...enrichment,
4152
+ },
4153
+ };
4154
+ }
4155
+ catch {
4156
+ // Gateway route lookup is best-effort enrichment only.
4157
+ return result;
4158
+ }
4159
+ }
4076
4160
  function normalizeOptionalToolString(value) {
4077
4161
  return typeof value === "string" && value.trim().length > 0
4078
4162
  ? value.trim()
@@ -4554,8 +4638,9 @@ function registerEnvTools(server) {
4554
4638
  if (action === "status") {
4555
4639
  const loginState = await (0, auth_js_1.peekLoginState)();
4556
4640
  const authFlowState = await (0, auth_js_1.getAuthProgressState)();
4557
- // 判断是否为 API Key 模式
4558
- const isApiKeyMode = !!(process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID);
4641
+ // Detect API Key mode (CLOUDBASE_API_KEY preferred, CLOUDBASE_APIKEY fallback)
4642
+ const apiKeyFromEnv = (0, auth_js_1.getCloudBaseApiKeyFromEnv)();
4643
+ const isApiKeyMode = !!(apiKeyFromEnv && process.env.CLOUDBASE_ENV_ID);
4559
4644
  const authStatus = loginState
4560
4645
  ? "READY"
4561
4646
  : authFlowState.status === "PENDING"
@@ -4603,8 +4688,9 @@ function registerEnvTools(server) {
4603
4688
  });
4604
4689
  }
4605
4690
  if (action === "start_auth") {
4606
- // API Key 模式:尝试换取临时密钥
4607
- if (process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID) {
4691
+ // API Key mode: exchange for temporary credentials
4692
+ const apiKeyFromEnv = (0, auth_js_1.getCloudBaseApiKeyFromEnv)();
4693
+ if (apiKeyFromEnv && process.env.CLOUDBASE_ENV_ID) {
4608
4694
  try {
4609
4695
  const existingLoginState = await (0, auth_js_1.peekLoginState)();
4610
4696
  if (existingLoginState) {
@@ -4618,22 +4704,21 @@ function registerEnvTools(server) {
4618
4704
  }
4619
4705
  }
4620
4706
  catch (e) {
4621
- // peekLoginState 内部异常,记录后 fall through
4707
+ // peekLoginState threw; log and fall through to diagnostic
4622
4708
  (0, logger_js_1.debug)("start_auth: peekLoginState threw", { error: e instanceof Error ? e.message : String(e) });
4623
4709
  }
4624
- // API Key 换取失败:返回详细诊断信息
4625
- // 尝试获取更详细的错误信息
4710
+ // API Key exchange failed: return diagnostic details
4626
4711
  let diagMessage = "当前配置了 API Key 认证模式,但换取临时密钥失败。";
4627
4712
  const endpoint = process.env.CLOUDBASE_API_ENDPOINT || `https://${process.env.CLOUDBASE_ENV_ID}.ap-shanghai.tcb-api.tencentcloudapi.com`;
4628
4713
  diagMessage += `\n\n诊断信息:`;
4629
4714
  diagMessage += `\n- CLOUDBASE_ENV_ID: ${process.env.CLOUDBASE_ENV_ID}`;
4630
- diagMessage += `\n- CLOUDBASE_API_KEY: ${process.env.CLOUDBASE_API_KEY.slice(0, 20)}...(已截断)`;
4715
+ diagMessage += `\n- CLOUDBASE_API_KEY: ${apiKeyFromEnv.slice(0, 20)}...(已截断)`;
4631
4716
  diagMessage += `\n- Endpoint: ${endpoint}`;
4632
4717
  diagMessage += `\n\n可能原因:`;
4633
4718
  diagMessage += `\n1. API Key 已过期或被删除`;
4634
4719
  diagMessage += `\n2. Endpoint 不可达(网络/DNS 问题)`;
4635
4720
  diagMessage += `\n3. CLOUDBASE_ENV_ID 与 API Key 所属环境不匹配`;
4636
- diagMessage += `\n\n建议:检查 MCP 配置中的 CLOUDBASE_API_KEY 和 CLOUDBASE_ENV_ID 环境变量是否正确。`;
4721
+ diagMessage += `\n\n建议:检查 MCP 配置中的 CLOUDBASE_API_KEY(或兼容的 CLOUDBASE_APIKEY)和 CLOUDBASE_ENV_ID 环境变量是否正确。`;
4637
4722
  return (0, tool_result_js_1.buildJsonToolResult)({
4638
4723
  ok: false,
4639
4724
  code: "API_KEY_AUTH_FAILED",
@@ -4919,12 +5004,12 @@ function registerEnvTools(server) {
4919
5004
  });
4920
5005
  }
4921
5006
  if (action === "logout") {
4922
- // API Key 模式下不允许 logout
4923
- if (process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID) {
5007
+ // Disallow logout in API Key mode
5008
+ if ((0, auth_js_1.getCloudBaseApiKeyFromEnv)() && process.env.CLOUDBASE_ENV_ID) {
4924
5009
  return (0, tool_result_js_1.buildJsonToolResult)({
4925
5010
  ok: false,
4926
5011
  code: "LOGOUT_NOT_ALLOWED",
4927
- message: "当前使用 API Key 认证模式,不支持退出登录。如需切换认证方式,请移除 CLOUDBASE_API_KEY 环境变量后重启。",
5012
+ message: "当前使用 API Key 认证模式,不支持退出登录。如需切换认证方式,请移除 CLOUDBASE_API_KEY(或兼容的 CLOUDBASE_APIKEY)环境变量后重启。",
4928
5013
  auth_mode: "api_key",
4929
5014
  });
4930
5015
  }
@@ -5146,6 +5231,8 @@ function registerEnvTools(server) {
5146
5231
  if (envId) {
5147
5232
  result = await enrichEnvInfoWithMissingFields(cloudbaseInfo, result, envId);
5148
5233
  }
5234
+ // Project gateway Route.Enable next to StaticDomain (do not rewrite StaticDomain).
5235
+ result = await enrichEnvInfoWithStaticDomainRouteEnabled(cloudbaseInfo, result, envId);
5149
5236
  result = await enrichEnvInfoWithRuntimeMode(result, cloudbaseInfo);
5150
5237
  break;
5151
5238
  case "domains":
@@ -5214,7 +5301,7 @@ function registerEnvTools(server) {
5214
5301
  // Register primary tool name (queryEnv)
5215
5302
  const queryEnvToolSchema = {
5216
5303
  title: "CloudBase 环境查询",
5217
- 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 都不该使用。",
5304
+ 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 都不该使用。",
5218
5305
  inputSchema: {
5219
5306
  action: zod_1.z
5220
5307
  .enum(["list", "info", "domains"])
@@ -84798,12 +84885,19 @@ exports.colors = {
84798
84885
  "use strict";
84799
84886
 
84800
84887
  Object.defineProperty(exports, "__esModule", ({ value: true }));
84888
+ exports.isGatewayRouteEnabled = isGatewayRouteEnabled;
84889
+ exports.isDomainPathReachableViaGateway = isDomainPathReachableViaGateway;
84801
84890
  exports.rankGatewayAccessUrls = rankGatewayAccessUrls;
84802
84891
  exports.flattenHttpServiceRoutes = flattenHttpServiceRoutes;
84803
84892
  exports.filterRoutesByUpstream = filterRoutesByUpstream;
84804
84893
  exports.toAccessUrlEnvelope = toAccessUrlEnvelope;
84805
84894
  exports.resolveGatewayAccessUrls = resolveGatewayAccessUrls;
84895
+ exports.resolveAllGatewayRoutes = resolveAllGatewayRoutes;
84806
84896
  exports.preferGatewayOrFallback = preferGatewayOrFallback;
84897
+ /** Route is reachable unless Enable is explicitly false. */
84898
+ function isGatewayRouteEnabled(route) {
84899
+ return route.Enable !== false;
84900
+ }
84807
84901
  function normalizeAccessPath(path) {
84808
84902
  if (!path) {
84809
84903
  return "/";
@@ -84813,13 +84907,63 @@ function normalizeAccessPath(path) {
84813
84907
  function buildHttpsUrl(domain, path) {
84814
84908
  return `https://${domain}${normalizeAccessPath(path)}`;
84815
84909
  }
84910
+ function extractHostname(urlOrHost) {
84911
+ const trimmed = urlOrHost.trim();
84912
+ if (!trimmed) {
84913
+ return "";
84914
+ }
84915
+ try {
84916
+ if (trimmed.includes("://")) {
84917
+ return new URL(trimmed).hostname.toLowerCase();
84918
+ }
84919
+ }
84920
+ catch {
84921
+ // Fall through to treat as bare hostname.
84922
+ }
84923
+ return trimmed.replace(/\/.*$/, "").toLowerCase();
84924
+ }
84925
+ /**
84926
+ * Whether a hostname has an enabled covering gateway route for the given path.
84927
+ * Longest matching Path prefix wins. Missing route info → unknown (null).
84928
+ */
84929
+ function isDomainPathReachableViaGateway(routes, domainOrUrl, requestPath) {
84930
+ const hostname = extractHostname(domainOrUrl);
84931
+ if (!hostname) {
84932
+ return null;
84933
+ }
84934
+ const normalizedRequest = normalizeAccessPath(requestPath);
84935
+ const domainRoutes = routes.filter((route) => Boolean(route.Domain) &&
84936
+ route.Domain.toLowerCase() === hostname);
84937
+ if (domainRoutes.length === 0) {
84938
+ return null;
84939
+ }
84940
+ const covering = domainRoutes
84941
+ .map((route) => ({
84942
+ route,
84943
+ path: normalizeAccessPath(route.Path),
84944
+ }))
84945
+ .filter(({ path }) => {
84946
+ if (path === "/") {
84947
+ return true;
84948
+ }
84949
+ return (normalizedRequest === path ||
84950
+ normalizedRequest.startsWith(path.endsWith("/") ? path : `${path}/`));
84951
+ })
84952
+ .sort((a, b) => b.path.length - a.path.length);
84953
+ if (covering.length === 0) {
84954
+ return null;
84955
+ }
84956
+ return isGatewayRouteEnabled(covering[0].route);
84957
+ }
84816
84958
  /**
84817
84959
  * Rank gateway route URLs: custom domains (IsDefault !== true) before default domain.
84818
- * Stable within each group; dedupe by full URL.
84960
+ * By default skips Enable === false routes. Stable within each group; dedupe by full URL.
84819
84961
  */
84820
- function rankGatewayAccessUrls(routes) {
84962
+ function rankGatewayAccessUrls(routes, options) {
84963
+ const includeDisabled = options?.includeDisabled === true;
84821
84964
  const mapped = routes
84822
84965
  .filter((route) => Boolean(route.Domain))
84966
+ .filter((route) => includeDisabled || isGatewayRouteEnabled(route))
84823
84967
  .map((route) => {
84824
84968
  const path = normalizeAccessPath(route.Path);
84825
84969
  const isDefault = route.IsDefault === true;
@@ -84828,6 +84972,7 @@ function rankGatewayAccessUrls(routes) {
84828
84972
  domain: route.Domain,
84829
84973
  path,
84830
84974
  isDefault,
84975
+ enabled: isGatewayRouteEnabled(route),
84831
84976
  source: (isDefault
84832
84977
  ? "gateway.default"
84833
84978
  : "gateway.custom"),
@@ -84855,6 +85000,7 @@ function flattenHttpServiceRoutes(result) {
84855
85000
  Domain: domainItem.Domain ?? "",
84856
85001
  IsDefault: domainItem.IsDefault,
84857
85002
  Path: typeof route.Path === "string" ? route.Path : undefined,
85003
+ Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
84858
85004
  UpstreamResourceType: typeof route.UpstreamResourceType === "string"
84859
85005
  ? route.UpstreamResourceType
84860
85006
  : undefined,
@@ -84877,64 +85023,116 @@ function filterRoutesByUpstream(routes, input) {
84877
85023
  types.includes(String(route.UpstreamResourceType)));
84878
85024
  });
84879
85025
  }
84880
- function toAccessUrlEnvelope(ranked) {
84881
- if (ranked.length === 0) {
84882
- return { accessUrls: [], routes: [] };
85026
+ function toAccessUrlEnvelope(ranked, disabledAccessUrls = []) {
85027
+ const enabled = ranked.filter((item) => item.enabled);
85028
+ if (enabled.length === 0) {
85029
+ return {
85030
+ accessUrls: [],
85031
+ routes: [],
85032
+ disabledAccessUrls,
85033
+ };
84883
85034
  }
84884
85035
  return {
84885
- accessUrl: ranked[0].url,
84886
- accessUrls: ranked.map((item) => item.url),
84887
- accessUrlSource: ranked[0].source,
84888
- routes: ranked,
85036
+ accessUrl: enabled[0].url,
85037
+ accessUrls: enabled.map((item) => item.url),
85038
+ accessUrlSource: enabled[0].source,
85039
+ routes: enabled,
85040
+ disabledAccessUrls,
84889
85041
  };
84890
85042
  }
85043
+ async function loadHttpServiceRoutes(getManager, envId) {
85044
+ const manager = await getManager();
85045
+ const result = await manager.env.describeHttpServiceRoute({
85046
+ EnvId: envId,
85047
+ Limit: 1000,
85048
+ });
85049
+ return flattenHttpServiceRoutes(result);
85050
+ }
84891
85051
  /**
84892
85052
  * Best-effort lookup of gateway access URLs for an upstream resource.
84893
85053
  * Never throws — returns empty lists on failure.
85054
+ * Disabled routes (Enable === false) are excluded from accessUrl(s).
84894
85055
  */
84895
85056
  async function resolveGatewayAccessUrls(input) {
84896
85057
  try {
84897
- const manager = await input.getManager();
84898
- const result = await manager.env.describeHttpServiceRoute({
84899
- EnvId: input.envId,
84900
- Limit: 1000,
84901
- });
84902
- const flattened = flattenHttpServiceRoutes(result);
85058
+ const flattened = await loadHttpServiceRoutes(input.getManager, input.envId);
84903
85059
  const matched = filterRoutesByUpstream(flattened, {
84904
85060
  upstreamResourceName: input.upstreamResourceName,
84905
85061
  upstreamResourceTypes: input.upstreamResourceTypes,
84906
85062
  });
84907
- return toAccessUrlEnvelope(rankGatewayAccessUrls(matched));
85063
+ const disabledAccessUrls = rankGatewayAccessUrls(matched, {
85064
+ includeDisabled: true,
85065
+ })
85066
+ .filter((item) => !item.enabled)
85067
+ .map((item) => item.url);
85068
+ return toAccessUrlEnvelope(rankGatewayAccessUrls(matched), disabledAccessUrls);
85069
+ }
85070
+ catch {
85071
+ return { accessUrls: [], routes: [], disabledAccessUrls: [] };
85072
+ }
85073
+ }
85074
+ /**
85075
+ * Load all flattened HTTP service routes (best-effort). Used by hosting to
85076
+ * judge whether the static-domain fallback itself is gateway-disabled.
85077
+ */
85078
+ async function resolveAllGatewayRoutes(input) {
85079
+ try {
85080
+ return await loadHttpServiceRoutes(input.getManager, input.envId);
84908
85081
  }
84909
85082
  catch {
84910
- return { accessUrls: [], routes: [] };
85083
+ return [];
84911
85084
  }
84912
85085
  }
84913
85086
  /**
84914
85087
  * Merge ranked gateway URLs with a resource-native fallback URL.
84915
85088
  * Gateway custom/default always win over the fallback when present.
85089
+ * Unreachable fallbacks are omitted from accessUrl(s) and listed under
85090
+ * disabledAccessUrls instead.
84916
85091
  */
84917
85092
  function preferGatewayOrFallback(input) {
85093
+ const disabledAccessUrls = [
85094
+ ...(input.gateway.disabledAccessUrls ?? []),
85095
+ ];
85096
+ const fallbackReachable = input.fallbackReachable !== false;
84918
85097
  if (input.gateway.accessUrl) {
84919
85098
  const urls = [...input.gateway.accessUrls];
84920
85099
  if (input.fallbackUrl &&
85100
+ fallbackReachable &&
84921
85101
  !urls.includes(input.fallbackUrl)) {
84922
85102
  urls.push(input.fallbackUrl);
84923
85103
  }
85104
+ else if (input.fallbackUrl &&
85105
+ !fallbackReachable &&
85106
+ !disabledAccessUrls.includes(input.fallbackUrl)) {
85107
+ disabledAccessUrls.push(input.fallbackUrl);
85108
+ }
84924
85109
  return {
84925
85110
  accessUrl: input.gateway.accessUrl,
84926
85111
  accessUrls: urls,
84927
85112
  accessUrlSource: input.gateway.accessUrlSource,
85113
+ accessUrlReachable: true,
85114
+ disabledAccessUrls,
84928
85115
  };
84929
85116
  }
84930
- if (input.fallbackUrl) {
85117
+ if (input.fallbackUrl && fallbackReachable) {
84931
85118
  return {
84932
85119
  accessUrl: input.fallbackUrl,
84933
85120
  accessUrls: [input.fallbackUrl],
84934
85121
  accessUrlSource: input.fallbackSource,
85122
+ accessUrlReachable: true,
85123
+ disabledAccessUrls,
84935
85124
  };
84936
85125
  }
84937
- return { accessUrls: [] };
85126
+ if (input.fallbackUrl && !fallbackReachable) {
85127
+ if (!disabledAccessUrls.includes(input.fallbackUrl)) {
85128
+ disabledAccessUrls.push(input.fallbackUrl);
85129
+ }
85130
+ }
85131
+ return {
85132
+ accessUrls: [],
85133
+ accessUrlReachable: false,
85134
+ disabledAccessUrls,
85135
+ };
84938
85136
  }
84939
85137
 
84940
85138
 
@@ -123609,16 +123807,19 @@ function isDirectoryUploadTarget(localPath, cloudPath) {
123609
123807
  return true;
123610
123808
  return path_1.default.posix.extname(normalizedCloudPath) === '';
123611
123809
  }
123612
- function buildHostingAccessUrl(staticDomain, cloudPath, localPath) {
123613
- if (!staticDomain)
123614
- return '';
123810
+ function hostingAccessPathname(cloudPath, localPath) {
123615
123811
  const normalizedCloudPath = (cloudPath ?? '').trim().replace(/^\/+|\/+$/g, '');
123616
123812
  const isDirectory = isDirectoryUploadTarget(localPath, cloudPath);
123617
123813
  if (!normalizedCloudPath) {
123618
- return `https://${staticDomain}/`;
123814
+ return '/';
123619
123815
  }
123620
123816
  const pathname = isDirectory ? `${normalizedCloudPath}/` : normalizedCloudPath;
123621
- return `https://${staticDomain}/${pathname}`;
123817
+ return `/${pathname}`;
123818
+ }
123819
+ function buildHostingAccessUrl(staticDomain, cloudPath, localPath) {
123820
+ if (!staticDomain)
123821
+ return '';
123822
+ return `https://${staticDomain}${hostingAccessPathname(cloudPath, localPath)}`;
123622
123823
  }
123623
123824
  function buildUploadErrorMessage(error, localPath) {
123624
123825
  const baseMessage = error instanceof Error ? error.message : String(error);
@@ -123799,7 +124000,7 @@ function buildDomainMutationResult(params) {
123799
124000
  message: `静态托管域名${actionLabel}请求已提交。域名配置、证书校验和边缘侧传播通常需要 30 秒到 10 分钟,请继续调用 queryHosting(action="domainStatus") 确认最终结果。`,
123800
124001
  };
123801
124002
  }
123802
- async function getHostingWebsiteConfig(cloudbase, logger) {
124003
+ async function getHostingWebsiteConfig(cloudbase, logger, cloudBaseOptions) {
123803
124004
  const websiteConfig = await cloudbase.hosting.getWebsiteConfig();
123804
124005
  (0, cloudbase_manager_js_1.logCloudBaseResult)(logger, websiteConfig);
123805
124006
  const hostingResult = {
@@ -123816,6 +124017,27 @@ async function getHostingWebsiteConfig(cloudbase, logger) {
123816
124017
  catch {
123817
124018
  // Ignore enrichment failures and return the website config as-is.
123818
124019
  }
124020
+ try {
124021
+ const cdnDomain = typeof hostingResult.CdnDomain === 'string' ? hostingResult.CdnDomain : '';
124022
+ if (cdnDomain && typeof cloudbase.env?.describeHttpServiceRoute === 'function') {
124023
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
124024
+ const routeResult = await cloudbase.env.describeHttpServiceRoute({
124025
+ EnvId: envId,
124026
+ Limit: 1000,
124027
+ });
124028
+ const routes = (0, gateway_access_urls_js_1.flattenHttpServiceRoutes)(routeResult);
124029
+ const reachable = (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(routes, cdnDomain, '/');
124030
+ hostingResult.staticDomainRouteEnabled = reachable;
124031
+ hostingResult.accessUrlReachable = reachable !== false;
124032
+ if (reachable === false) {
124033
+ hostingResult.routeDisabled = true;
124034
+ hostingResult.disabledAccessUrls = [`https://${cdnDomain}/`];
124035
+ }
124036
+ }
124037
+ }
124038
+ catch {
124039
+ // Gateway route lookup is best-effort enrichment only.
124040
+ }
123819
124041
  return hostingResult;
123820
124042
  }
123821
124043
  async function resolveHostingStaticDomain(cloudbase, logger) {
@@ -123899,7 +124121,7 @@ function registerHostingTools(server) {
123899
124121
  const cloudbase = await getManager();
123900
124122
  switch (input.action) {
123901
124123
  case 'websiteConfig': {
123902
- const websiteConfig = await getHostingWebsiteConfig(cloudbase, server.logger);
124124
+ const websiteConfig = await getHostingWebsiteConfig(cloudbase, server.logger, cloudBaseOptions);
123903
124125
  return (0, tool_result_js_1.buildJsonToolResult)({
123904
124126
  success: true,
123905
124127
  data: {
@@ -124025,7 +124247,7 @@ function registerHostingTools(server) {
124025
124247
  });
124026
124248
  server.registerTool('manageHosting', {
124027
124249
  title: '管理 CloudBase 静态托管',
124028
- 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。',
124250
+ 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。',
124029
124251
  inputSchema: manageHostingInputSchema,
124030
124252
  annotations: {
124031
124253
  readOnlyHint: false,
@@ -124065,7 +124287,24 @@ function registerHostingTools(server) {
124065
124287
  const cdnFromStore = (store.CdnDomain ?? store.StaticDomain);
124066
124288
  const staticDomain = cdnFromStore || await resolveHostingStaticDomain(cloudbase, server.logger);
124067
124289
  const fallbackAccessUrl = buildHostingAccessUrl(staticDomain, input.cloudPath, input.localPath);
124290
+ const accessPathname = hostingAccessPathname(input.cloudPath, input.localPath);
124068
124291
  const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
124292
+ const getGatewayManager = async () => {
124293
+ const manager = await getManager();
124294
+ if (!manager) {
124295
+ throw new Error("cloudbase manager unavailable");
124296
+ }
124297
+ return manager;
124298
+ };
124299
+ const allGatewayRoutes = await (0, gateway_access_urls_js_1.resolveAllGatewayRoutes)({
124300
+ envId,
124301
+ getManager: getGatewayManager,
124302
+ });
124303
+ const staticDomainRouteEnabled = staticDomain
124304
+ ? (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(allGatewayRoutes, staticDomain, accessPathname)
124305
+ : null;
124306
+ // null = no matching gateway route info → keep legacy fallback.
124307
+ const fallbackReachable = staticDomainRouteEnabled !== false;
124069
124308
  const gatewayCandidates = Array.from(new Set([
124070
124309
  store.StaticStoreName,
124071
124310
  store.Name,
@@ -124075,34 +124314,38 @@ function registerHostingTools(server) {
124075
124314
  ]
124076
124315
  .map((item) => (typeof item === "string" ? item.trim() : ""))
124077
124316
  .filter(Boolean)));
124078
- let accessUrl = fallbackAccessUrl;
124317
+ let accessUrl = fallbackReachable ? fallbackAccessUrl : "";
124079
124318
  let accessUrls = accessUrl ? [accessUrl] : [];
124080
124319
  let accessUrlSource = accessUrl
124081
124320
  ? "hosting.staticDomain"
124082
124321
  : undefined;
124322
+ let accessUrlReachable = Boolean(accessUrl);
124323
+ let disabledAccessUrls = [];
124324
+ if (fallbackAccessUrl && !fallbackReachable) {
124325
+ disabledAccessUrls.push(fallbackAccessUrl);
124326
+ }
124083
124327
  for (const upstreamName of gatewayCandidates) {
124084
124328
  const gateway = await (0, gateway_access_urls_js_1.resolveGatewayAccessUrls)({
124085
124329
  envId,
124086
124330
  upstreamResourceName: upstreamName,
124087
124331
  upstreamResourceTypes: ["STATIC_STORE"],
124088
- getManager: async () => {
124089
- const manager = await getManager();
124090
- if (!manager) {
124091
- throw new Error("cloudbase manager unavailable");
124092
- }
124093
- return manager;
124094
- },
124332
+ getManager: getGatewayManager,
124095
124333
  });
124096
124334
  const preferred = (0, gateway_access_urls_js_1.preferGatewayOrFallback)({
124097
124335
  gateway,
124098
124336
  fallbackUrl: fallbackAccessUrl || undefined,
124099
124337
  fallbackSource: "hosting.staticDomain",
124338
+ fallbackReachable,
124100
124339
  });
124101
- if (preferred.accessUrl) {
124102
- accessUrl = preferred.accessUrl;
124340
+ if (preferred.accessUrl || preferred.disabledAccessUrls.length > 0) {
124341
+ accessUrl = preferred.accessUrl ?? "";
124103
124342
  accessUrls = preferred.accessUrls;
124104
124343
  accessUrlSource = preferred.accessUrlSource;
124105
- break;
124344
+ accessUrlReachable = preferred.accessUrlReachable;
124345
+ disabledAccessUrls = preferred.disabledAccessUrls;
124346
+ if (preferred.accessUrl) {
124347
+ break;
124348
+ }
124106
124349
  }
124107
124350
  }
124108
124351
  try {
@@ -124118,18 +124361,31 @@ function registerHostingTools(server) {
124118
124361
  projectName = path_1.default.basename(input.localPath);
124119
124362
  }
124120
124363
  }
124121
- await (0, notification_js_1.sendDeployNotification)(server, {
124122
- deployType: 'hosting',
124123
- url: accessUrl,
124124
- projectId: envId,
124125
- projectName,
124126
- consoleUrl: `https://tcb.cloud.tencent.com/dev?envId=${envId}#/static-hosting`,
124127
- });
124364
+ // sendDeployNotification requires a concrete url; skip when no reachable accessUrl
124365
+ if (accessUrl) {
124366
+ await (0, notification_js_1.sendDeployNotification)(server, {
124367
+ deployType: 'hosting',
124368
+ url: accessUrl,
124369
+ projectId: envId,
124370
+ projectName,
124371
+ consoleUrl: `https://tcb.cloud.tencent.com/dev?envId=${envId}#/static-hosting`,
124372
+ });
124373
+ }
124128
124374
  }
124129
124375
  catch {
124130
124376
  // Notification failure should not block uploads.
124131
124377
  }
124132
124378
  const uploadPrefix = input.cloudPath ?? input.files?.[0]?.cloudPath ?? '';
124379
+ const routeDisabled = staticDomainRouteEnabled === false;
124380
+ let message = '静态托管文件上传成功。若需要校验上传结果,请继续调用 queryHosting(action="findFiles") 或 queryHosting(action="listFiles")。';
124381
+ if (routeDisabled && accessUrlReachable) {
124382
+ message =
124383
+ '静态托管文件上传成功。默认静态托管域名的网关路由已禁用(访问会返回 GATEWAY_ROUTE_DISABLED),已改用其他可达域名作为 accessUrl。可用 manageGateway(action="updateRoute", route.enable=true) 重新启用默认域路由。';
124384
+ }
124385
+ else if (routeDisabled && !accessUrlReachable) {
124386
+ message =
124387
+ '静态托管文件上传成功,但默认静态托管域名的网关路由已禁用(GATEWAY_ROUTE_DISABLED),当前没有可达的 accessUrl。请用 manageGateway(action="updateRoute", domain="<静态域名>", path="/", upstreamResourceType="STATIC_STORE", targetName="staticstore", route.enable=true) 启用路由,或改用已启用的自定义域名 / CloudBase Sites 域名访问。';
124388
+ }
124133
124389
  return (0, tool_result_js_1.buildJsonToolResult)({
124134
124390
  success: true,
124135
124391
  data: {
@@ -124139,13 +124395,19 @@ function registerHostingTools(server) {
124139
124395
  files: input.files ?? [],
124140
124396
  ignore: input.ignore,
124141
124397
  staticDomain,
124142
- accessUrl,
124398
+ staticDomainRouteEnabled,
124399
+ accessUrl: accessUrl || undefined,
124143
124400
  accessUrls,
124144
124401
  accessUrlSource,
124402
+ accessUrlReachable,
124403
+ ...(disabledAccessUrls.length > 0
124404
+ ? { disabledAccessUrls }
124405
+ : {}),
124406
+ ...(routeDisabled ? { routeDisabled: true } : {}),
124145
124407
  result,
124146
124408
  nextActions: uploadPrefix ? [buildFindFilesNextStep(uploadPrefix.replace(/^\/+/, ''))] : undefined,
124147
124409
  },
124148
- message: '静态托管文件上传成功。若需要校验上传结果,请继续调用 queryHosting(action="findFiles") 或 queryHosting(action="listFiles")。',
124410
+ message,
124149
124411
  });
124150
124412
  }
124151
124413
  case 'delete': {
@@ -142128,7 +142390,7 @@ class TelemetryReporter {
142128
142390
  const arch = os_1.default.arch(); // 系统架构
142129
142391
  // Build-time injected version; guard for vitest / non-webpack runtimes.
142130
142392
  const mcpVersion = process.env.npm_package_version ||
142131
- ( true ? "2.25.10" : 0);
142393
+ ( true ? "2.26.0" : 0);
142132
142394
  return {
142133
142395
  userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
142134
142396
  deviceId: this.deviceId,
@@ -223544,7 +223806,7 @@ ${envIdSection}
223544
223806
  ## 环境信息
223545
223807
  - 操作系统: ${os_1.default.type()} ${os_1.default.release()}
223546
223808
  - Node.js版本: ${process.version}
223547
- - MCP 版本:${process.env.npm_package_version || ( true ? "2.25.10" : 0)}
223809
+ - MCP 版本:${process.env.npm_package_version || ( true ? "2.26.0" : 0)}
223548
223810
  - 系统架构: ${os_1.default.arch()}
223549
223811
  - 时间: ${new Date().toISOString()}
223550
223812
  - 请求ID: ${requestId}
@@ -223625,7 +223887,7 @@ function createWrappedHandler(name, handler, server) {
223625
223887
  requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
223626
223888
  ide: server.ide || process.env.INTEGRATION_IDE || ''
223627
223889
  });
223628
- const mcpVersion = true ? "2.25.10" : 0;
223890
+ const mcpVersion = true ? "2.26.0" : 0;
223629
223891
  const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
223630
223892
  const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
223631
223893
  // 创建新的错误对象,保持原有的错误类型但更新消息
@@ -240845,7 +241107,7 @@ function registerSetupTools(server) {
240845
241107
  server.registerTool("downloadTemplate", {
240846
241108
  title: "下载项目模板",
240847
241109
  description: `自动下载并部署CloudBase项目模板。
240848
- **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)`,
241110
+ **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.26.0" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
240849
241111
  inputSchema: {
240850
241112
  template: zod_1.z
240851
241113
  .enum(["react", "vue", "miniprogram", "uniapp", "rules"])
@@ -242259,6 +242521,7 @@ module.exports.sync = path => {
242259
242521
  Object.defineProperty(exports, "__esModule", ({ value: true }));
242260
242522
  exports.buildVerificationUriComplete = buildVerificationUriComplete;
242261
242523
  exports.buildDeviceAuthChallengePayload = buildDeviceAuthChallengePayload;
242524
+ exports.getCloudBaseApiKeyFromEnv = getCloudBaseApiKeyFromEnv;
242262
242525
  exports.getAuthProgressStateSync = getAuthProgressStateSync;
242263
242526
  exports.getAuthProgressState = getAuthProgressState;
242264
242527
  exports.resolveAuthOptions = resolveAuthOptions;
@@ -242360,16 +242623,24 @@ function updateAuthProgressState(partial) {
242360
242623
  });
242361
242624
  return getAuthProgressStateSync();
242362
242625
  }
242626
+ /**
242627
+ * Resolve CloudBase API Key from env.
242628
+ * Prefer CLOUDBASE_API_KEY; fall back to CLOUDBASE_APIKEY (js-sdk / node-sdk convention).
242629
+ */
242630
+ function getCloudBaseApiKeyFromEnv() {
242631
+ return process.env.CLOUDBASE_API_KEY || process.env.CLOUDBASE_APIKEY || undefined;
242632
+ }
242363
242633
  function normalizeLoginStateFromEnvVars(options) {
242364
242634
  if (options?.ignoreEnvVars) {
242365
242635
  return null;
242366
242636
  }
242367
- // 优先检查 CloudBase API Key(优先级高于 TENCENTCLOUD_*)
242368
- const { CLOUDBASE_API_KEY, CLOUDBASE_ENV_ID: CLOUDBASE_ENV, } = process.env;
242369
- if (CLOUDBASE_API_KEY && CLOUDBASE_ENV) {
242637
+ // Prefer CloudBase API Key over TENCENTCLOUD_*
242638
+ const apiKey = getCloudBaseApiKeyFromEnv();
242639
+ const CLOUDBASE_ENV = process.env.CLOUDBASE_ENV_ID;
242640
+ if (apiKey && CLOUDBASE_ENV) {
242370
242641
  return {
242371
242642
  _type: 'api_key',
242372
- apiKey: CLOUDBASE_API_KEY,
242643
+ apiKey,
242373
242644
  envId: CLOUDBASE_ENV,
242374
242645
  };
242375
242646
  }
@@ -242541,7 +242812,7 @@ async function peekLoginState(options) {
242541
242812
  }
242542
242813
  async function ensureLogin(options) {
242543
242814
  (0, logger_js_1.debug)("TENCENTCLOUD_SECRETID", { hasSecretId: !!process.env.TENCENTCLOUD_SECRETID });
242544
- (0, logger_js_1.debug)("CLOUDBASE_API_KEY", { hasApiKey: !!process.env.CLOUDBASE_API_KEY });
242815
+ (0, logger_js_1.debug)("CLOUDBASE_API_KEY", { hasApiKey: !!getCloudBaseApiKeyFromEnv() });
242545
242816
  const loginState = await peekLoginState({
242546
242817
  ignoreEnvVars: options?.ignoreEnvVars,
242547
242818
  });
@@ -263813,6 +264084,8 @@ const MANAGE_GATEWAY_ACTIONS = [
263813
264084
  "createRoute",
263814
264085
  "updateRoute",
263815
264086
  "deleteRoute",
264087
+ "enableRoute",
264088
+ "disableRoute",
263816
264089
  "bindCustomDomain",
263817
264090
  "deleteCustomDomain",
263818
264091
  "enableService",
@@ -263885,6 +264158,7 @@ function registerGatewayTools(server) {
263885
264158
  Domain: route.Domain,
263886
264159
  Path: route.Path,
263887
264160
  IsDefault: route.IsDefault,
264161
+ Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
263888
264162
  UpstreamResourceType: route.UpstreamResourceType,
263889
264163
  UpstreamResourceName: route.UpstreamResourceName,
263890
264164
  }));
@@ -263895,16 +264169,26 @@ function registerGatewayTools(server) {
263895
264169
  Domain: route.Domain,
263896
264170
  Path: route.Path,
263897
264171
  IsDefault: route.IsDefault,
264172
+ Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
263898
264173
  UpstreamResourceType: route.UpstreamResourceType,
263899
264174
  UpstreamResourceName: route.UpstreamResourceName,
263900
264175
  }));
263901
- const envelope = (0, gateway_access_urls_js_1.toAccessUrlEnvelope)((0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates));
264176
+ const disabledAccessUrls = (0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates, {
264177
+ includeDisabled: true,
264178
+ })
264179
+ .filter((item) => !item.enabled)
264180
+ .map((item) => item.url);
264181
+ const envelope = (0, gateway_access_urls_js_1.toAccessUrlEnvelope)((0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates), disabledAccessUrls);
263902
264182
  return {
263903
264183
  ...(envelope.accessUrl ? { accessUrl: envelope.accessUrl } : {}),
263904
264184
  accessUrls: envelope.accessUrls,
263905
264185
  ...(envelope.accessUrlSource
263906
264186
  ? { accessUrlSource: envelope.accessUrlSource }
263907
264187
  : {}),
264188
+ accessUrlReachable: Boolean(envelope.accessUrl),
264189
+ ...(disabledAccessUrls.length > 0
264190
+ ? { disabledAccessUrls, routeDisabled: true }
264191
+ : {}),
263908
264192
  };
263909
264193
  };
263910
264194
  const pickUsableDefaultDomain = (domains) => {
@@ -263994,6 +264278,15 @@ function registerGatewayTools(server) {
263994
264278
  : input.enablePathTransmission !== undefined
263995
264279
  ? input.enablePathTransmission
263996
264280
  : undefined;
264281
+ // Route enable: prefer route.enable. For updateRoute only, top-level
264282
+ // enable also maps to Route.Enable (enableService/authSwitch use the
264283
+ // same field name but different actions).
264284
+ const routeEnable = input.route?.enable !== undefined
264285
+ ? input.route.enable
264286
+ : input.action === "updateRoute" &&
264287
+ typeof input.enable === "boolean"
264288
+ ? input.enable
264289
+ : undefined;
263997
264290
  const route = {
263998
264291
  Path: path,
263999
264292
  UpstreamResourceType: upstreamResourceType,
@@ -264003,6 +264296,9 @@ function registerGatewayTools(server) {
264003
264296
  if (enablePathTransmission !== undefined) {
264004
264297
  route.EnablePathTransmission = enablePathTransmission;
264005
264298
  }
264299
+ if (routeEnable !== undefined) {
264300
+ route.Enable = routeEnable;
264301
+ }
264006
264302
  return {
264007
264303
  EnvId: await resolveEnvId(),
264008
264304
  Domain: {
@@ -264016,9 +264312,46 @@ function registerGatewayTools(server) {
264016
264312
  upstreamResourceName,
264017
264313
  enableAuth,
264018
264314
  enablePathTransmission,
264315
+ enable: routeEnable,
264019
264316
  },
264020
264317
  };
264021
264318
  };
264319
+ const resolveExistingRouteForToggle = async (input) => {
264320
+ const routePath = input.routePath ?? input.path;
264321
+ if (!routePath) {
264322
+ throw new Error("action=enableRoute/disableRoute 时必须提供 path 或 route.path(例如 \"/\" 或 \"/api\")");
264323
+ }
264324
+ const normalizedPath = normalizeAccessPath(routePath);
264325
+ const preferredDomain = input.domain;
264326
+ const result = await listHttpServiceRoutes(preferredDomain ? { domain: preferredDomain } : undefined);
264327
+ const upstreamName = input.routeServiceName ?? input.targetName;
264328
+ const matches = flattenRoutes(result).filter((item) => {
264329
+ if (preferredDomain &&
264330
+ item.Domain !== preferredDomain) {
264331
+ return false;
264332
+ }
264333
+ if (normalizeAccessPath(String(item.Path ?? "")) !== normalizedPath) {
264334
+ return false;
264335
+ }
264336
+ if (upstreamName && item.UpstreamResourceName !== upstreamName) {
264337
+ return false;
264338
+ }
264339
+ return true;
264340
+ });
264341
+ if (matches.length === 0) {
264342
+ throw new Error(`未找到路径 ${normalizedPath} 的路由` +
264343
+ (preferredDomain ? `(域名 ${preferredDomain})` : "") +
264344
+ (upstreamName ? `(上游 ${upstreamName})` : "") +
264345
+ "。请先用 queryGateway(action=\"listRoutes\") 确认 Domain / Path,再调用 enableRoute/disableRoute。" +
264346
+ "关闭静态托管默认域名(*.tcloudbaseapp.com)时,请显式传 domain=该 STATIC_STORE IsDefault 域名,并通常 path=\"/\"。");
264347
+ }
264348
+ if (matches.length > 1) {
264349
+ const domains = [...new Set(matches.map((item) => item.Domain))];
264350
+ throw new Error(`路径 ${normalizedPath} 匹配到 ${matches.length} 条路由(域名:${domains.join(", ")})。` +
264351
+ "请补充 domain(必要时再加 targetName/route.serviceName)精确定位后再 enableRoute/disableRoute。");
264352
+ }
264353
+ return matches[0];
264354
+ };
264022
264355
  const routeMutationNextActions = (targetName) => [
264023
264356
  {
264024
264357
  tool: "queryGateway",
@@ -264222,6 +264555,10 @@ function registerGatewayTools(server) {
264222
264555
  Domain: payload.Domain,
264223
264556
  });
264224
264557
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
264558
+ const routeEnabled = payload.resolved.enable !== false;
264559
+ const accessUrl = routeEnabled
264560
+ ? `https://${payload.resolved.domain}${payload.resolved.path}`
264561
+ : undefined;
264225
264562
  return buildEnvelope({
264226
264563
  action: input.action,
264227
264564
  model: "httpServiceRoute",
@@ -264231,13 +264568,31 @@ function registerGatewayTools(server) {
264231
264568
  upstreamResourceName: payload.resolved.upstreamResourceName,
264232
264569
  auth: payload.resolved.enableAuth ?? null,
264233
264570
  enablePathTransmission: payload.resolved.enablePathTransmission ?? null,
264234
- accessUrl: `https://${payload.resolved.domain}${payload.resolved.path}`,
264235
- accessUrls: [`https://${payload.resolved.domain}${payload.resolved.path}`],
264571
+ enable: payload.resolved.enable ?? null,
264572
+ ...(accessUrl
264573
+ ? {
264574
+ accessUrl,
264575
+ accessUrls: [accessUrl],
264576
+ accessUrlReachable: true,
264577
+ }
264578
+ : {
264579
+ accessUrls: [],
264580
+ accessUrlReachable: false,
264581
+ routeDisabled: true,
264582
+ disabledAccessUrls: [
264583
+ `https://${payload.resolved.domain}${payload.resolved.path}`,
264584
+ ],
264585
+ }),
264236
264586
  accessUrlSource: input.domain && input.domain === payload.resolved.domain
264237
264587
  ? "gateway.custom"
264238
264588
  : "gateway.default",
264239
264589
  raw: result,
264240
264590
  }, `HTTP 路由更新成功(${payload.resolved.domain}${payload.resolved.path}` +
264591
+ (payload.resolved.enable === false
264592
+ ? ",路由已禁用 Enable=false"
264593
+ : payload.resolved.enable === true
264594
+ ? ",路由已启用 Enable=true"
264595
+ : "") +
264241
264596
  (payload.resolved.enablePathTransmission === true
264242
264597
  ? ",路径透传=开启"
264243
264598
  : payload.resolved.enablePathTransmission === false
@@ -264245,6 +264600,102 @@ function registerGatewayTools(server) {
264245
264600
  : "") +
264246
264601
  `)`, routeMutationNextActions(payload.resolved.upstreamResourceName));
264247
264602
  }
264603
+ case "enableRoute":
264604
+ case "disableRoute": {
264605
+ const enable = input.action === "enableRoute";
264606
+ const existing = await resolveExistingRouteForToggle({
264607
+ path: input.path,
264608
+ routePath: input.route?.path,
264609
+ domain: input.domain,
264610
+ targetName: input.targetName,
264611
+ routeServiceName: input.route?.serviceName,
264612
+ });
264613
+ const upstreamResourceType = resolveUpstreamResourceType({
264614
+ upstreamResourceType: input.upstreamResourceType ??
264615
+ existing.UpstreamResourceType,
264616
+ routeUpstreamResourceType: input.route?.upstreamResourceType,
264617
+ });
264618
+ const upstreamResourceName = input.route?.serviceName ??
264619
+ input.targetName ??
264620
+ String(existing.UpstreamResourceName ?? "");
264621
+ if (!upstreamResourceName) {
264622
+ throw new Error(`action=${input.action} 无法解析上游资源名;请用 queryGateway(listRoutes) 确认后传入 targetName 或 route.serviceName。`);
264623
+ }
264624
+ const normalizedPath = normalizeAccessPath(String(existing.Path ?? input.route?.path ?? input.path ?? "/"));
264625
+ const domain = existing.Domain;
264626
+ const enableAuth = input.route?.auth !== undefined
264627
+ ? input.route.auth
264628
+ : input.auth !== undefined
264629
+ ? input.auth
264630
+ : existing.EnableAuth;
264631
+ const enablePathTransmission = input.route?.enablePathTransmission !== undefined
264632
+ ? input.route.enablePathTransmission
264633
+ : input.enablePathTransmission !== undefined
264634
+ ? input.enablePathTransmission
264635
+ : existing.EnablePathTransmission;
264636
+ const route = {
264637
+ Path: normalizedPath,
264638
+ UpstreamResourceType: upstreamResourceType,
264639
+ UpstreamResourceName: upstreamResourceName,
264640
+ Enable: enable,
264641
+ };
264642
+ if (enableAuth !== undefined) {
264643
+ route.EnableAuth = enableAuth;
264644
+ }
264645
+ if (enablePathTransmission !== undefined) {
264646
+ route.EnablePathTransmission = enablePathTransmission;
264647
+ }
264648
+ const cloudbase = await getManager();
264649
+ const result = await cloudbase.env.modifyHttpServiceRoute({
264650
+ EnvId: await resolveEnvId(),
264651
+ Domain: {
264652
+ Domain: domain,
264653
+ Routes: [route],
264654
+ },
264655
+ });
264656
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
264657
+ const verb = enable ? "启用" : "禁用";
264658
+ const accessUrl = enable
264659
+ ? `https://${domain}${normalizedPath}`
264660
+ : undefined;
264661
+ return buildEnvelope({
264662
+ action: input.action,
264663
+ model: "httpServiceRoute",
264664
+ domain,
264665
+ path: normalizedPath,
264666
+ upstreamResourceType,
264667
+ upstreamResourceName,
264668
+ enable,
264669
+ auth: enableAuth ?? null,
264670
+ enablePathTransmission: enablePathTransmission ?? null,
264671
+ ...(accessUrl
264672
+ ? {
264673
+ accessUrl,
264674
+ accessUrls: [accessUrl],
264675
+ accessUrlReachable: true,
264676
+ }
264677
+ : {
264678
+ accessUrls: [],
264679
+ accessUrlReachable: false,
264680
+ routeDisabled: true,
264681
+ disabledAccessUrls: [`https://${domain}${normalizedPath}`],
264682
+ }),
264683
+ accessUrlSource: existing.IsDefault
264684
+ ? "gateway.default"
264685
+ : "gateway.custom",
264686
+ raw: result,
264687
+ }, `HTTP 路由已${verb}(${domain}${normalizedPath},Enable=${enable})。` +
264688
+ (enable
264689
+ ? "启用后通常数秒到约 30 秒内可访问;请用 queryGateway(getRoute) 或探测 accessUrl 确认。"
264690
+ : "禁用后该路径将不可公网访问(GATEWAY_ROUTE_DISABLED);关闭静态托管默认域名(*.tcloudbaseapp.com)时,请确认 DomainType=STATIC_STORE 且 IsDefault=true。") +
264691
+ " 底层对应 tcb ModifyHTTPServiceRoute(不是 ModifyGatewayRoute)。", [
264692
+ {
264693
+ tool: "queryGateway",
264694
+ action: "getRoute",
264695
+ reason: `复核路由 Enable=${enable} 是否已生效`,
264696
+ },
264697
+ ]);
264698
+ }
264248
264699
  case "deleteRoute": {
264249
264700
  const routePath = input.route?.path ?? input.path;
264250
264701
  if (!routePath) {
@@ -264452,8 +264903,11 @@ function registerGatewayTools(server) {
264452
264903
  }, async (input) => withEnvelope(() => handleQueryGateway(input)));
264453
264904
  server.registerTool?.("manageGateway", {
264454
264905
  title: "管理 CloudBase 网关",
264455
- description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;未传 domain 时用 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(形如 *.{region}.app.tcloudbase.com),不会使用静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE)。" +
264906
+ description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;" +
264907
+ "enableRoute/disableRoute 启用或禁用已有路由(底层 ModifyHTTPServiceRoute 的 Routes[].Enable,不是 ModifyGatewayRoute)。" +
264908
+ "未传 domain 时用 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(形如 *.{region}.app.tcloudbase.com),不会使用静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE)。" +
264456
264909
  "这是网关默认域上的路径路由,不是 STATIC_STORE 上游绑定;STATIC_STORE 上游必须显式传 upstreamResourceType=STATIC_STORE。" +
264910
+ "关闭静态托管默认域名(*.tcloudbaseapp.com):先 queryGateway(listRoutes) 找到 DomainType=STATIC_STORE 且 IsDefault=true 的 domain,再 manageGateway(action=\"disableRoute\", domain=该域名, path=\"/\");勿用 manageHosting。" +
264457
264911
  "创建后可用 queryGateway(action=\"listRoutes\") 核对 Domain / DomainType / Path / UpstreamResourceType。" +
264458
264912
  "上游类型只用一个参数 upstreamResourceType(也可写在 route.upstreamResourceType,route 优先):" +
264459
264913
  "WEB_SCF=HTTP云函数,SCF=Event云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器;" +
@@ -264466,9 +264920,12 @@ function registerGatewayTools(server) {
264466
264920
  inputSchema: {
264467
264921
  action: zod_1.z
264468
264922
  .enum(MANAGE_GATEWAY_ACTIONS)
264469
- .describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;bindCustomDomain/deleteCustomDomain 管理自定义域名;" +
264923
+ .describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;enableRoute/disableRoute 启用或禁用已有路由(需 path,建议显式传 domain);" +
264924
+ "bindCustomDomain/deleteCustomDomain 管理自定义域名;" +
264470
264925
  "enableService/authSwitch 开关 HTTP 网关总开关与访问鉴权(需配合 enable 参数)。" +
264471
- "createRoute/updateRoute 必须提供 upstreamResourceType。" +
264926
+ "createRoute/updateRoute 必须提供 upstreamResourceType;enableRoute/disableRoute 会先 listRoutes 定位已有路由,通常不必重填上游。" +
264927
+ "updateRoute 也可传 enable/route.enable 直接改 Routes[].Enable。" +
264928
+ "关闭 *.tcloudbaseapp.com 默认静态托管域:disableRoute + domain=该 STATIC_STORE IsDefault 域名 + path=\"/\"。" +
264472
264929
  "已有自定义域名时优先 createRoute(domain=已有域名) 实现访问,不必再次 bindCustomDomain / 传入 certificateId;" +
264473
264930
  "bindCustomDomain 仅用于首次绑定新域名(需 certificateId;可选 accessType=DIRECT|CDN|CUSTOM,CUSTOM 需 customCname;普通场景用默认 DIRECT)。" +
264474
264931
  "接入说明:https://docs.cloudbase.net/service/custom-domain"),
@@ -264517,16 +264974,25 @@ function registerGatewayTools(server) {
264517
264974
  .boolean()
264518
264975
  .optional()
264519
264976
  .describe("同顶层 enablePathTransmission。route 内设置时优先于顶层。"),
264977
+ enable: zod_1.z
264978
+ .boolean()
264979
+ .optional()
264980
+ .describe("路由级开关(Routes[].Enable / Route.Enable)。createRoute/updateRoute 可用:" +
264981
+ "enable=false 禁用该 Domain+Path(访问返回 GATEWAY_ROUTE_DISABLED);" +
264982
+ "enable=true 重新启用。updateRoute 也可用顶层 enable 表达同一语义;" +
264983
+ "也可用专用 action enableRoute/disableRoute。route.enable 优先于顶层 enable。"),
264520
264984
  })
264521
264985
  .optional()
264522
264986
  .describe("路由对象(可选写法)。例:云函数 {upstreamResourceType:\"WEB_SCF\",serviceName:\"fn\",path:\"/api\"};" +
264523
264987
  "云托管 {upstreamResourceType:\"CBR\",serviceName:\"svc\",path:\"/api\"};" +
264524
- "静态托管 {upstreamResourceType:\"STATIC_STORE\",serviceName:\"staticstore\",path:\"/\"}。"),
264988
+ "静态托管 {upstreamResourceType:\"STATIC_STORE\",serviceName:\"staticstore\",path:\"/\"};" +
264989
+ "禁用路由 {path:\"/\",enable:false}(配合 updateRoute,或直接用 disableRoute)。"),
264525
264990
  domain: zod_1.z
264526
264991
  .string()
264527
264992
  .optional()
264528
264993
  .describe("域名。省略时自动使用环境 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(*.{region}.app.tcloudbase.com),不会回退到静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE);也不是 STATIC_STORE 上游绑定。" +
264529
264994
  "可用 queryGateway(action=\"listRoutes\") 核对实际 Domain / DomainType。" +
264995
+ "enableRoute/disableRoute 操作 *.tcloudbaseapp.com 时必须显式传入该域名。" +
264530
264996
  "已有自定义域名时请显式传入该域名并 createRoute/updateRoute/deleteRoute,即可实现自定义域名访问且无需证书 ID;" +
264531
264997
  "仅 bindCustomDomain 时表示要新绑定的域名。"),
264532
264998
  certificateId: zod_1.z
@@ -264549,9 +265015,11 @@ function registerGatewayTools(server) {
264549
265015
  enable: zod_1.z
264550
265016
  .boolean()
264551
265017
  .optional()
264552
- .describe("开关目标状态(enableService / authSwitch 必填):enable=true 开启,enable=false 关闭。" +
264553
- "bindCustomDomain 可选:enable=false 表示绑定后禁用域名(默认启用)。" +
264554
- "省略或非布尔值会返回参数错误。"),
265018
+ .describe("开关目标状态:enableService / authSwitch 必填(true 开启 / false 关闭);" +
265019
+ "bindCustomDomain 可选:enable=false 表示绑定后禁用域名(默认启用);" +
265020
+ "updateRoute 可选:映射到 Routes[].Enable(也可用 route.enable,route 优先;" +
265021
+ "也可用专用 action enableRoute/disableRoute)。" +
265022
+ "省略或非布尔值在 enableService/authSwitch 会返回参数错误。"),
264555
265023
  },
264556
265024
  annotations: {
264557
265025
  readOnlyHint: false,