@cloudbase/cloudbase-mcp 2.18.0 → 2.19.5

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
@@ -161,12 +161,71 @@ function simplifyEnvDomains(domains) {
161
161
  }
162
162
  const source = domain;
163
163
  return {
164
+ ...(source.Id !== undefined ? { Id: source.Id } : {}),
164
165
  ...(source.Domain !== undefined ? { Domain: source.Domain } : {}),
166
+ ...(source.CreateTime !== undefined ? { CreateTime: source.CreateTime } : {}),
167
+ ...(source.UpdateTime !== undefined ? { UpdateTime: source.UpdateTime } : {}),
165
168
  ...(source.Status !== undefined ? { Status: source.Status } : {}),
166
169
  ...(source.Type !== undefined ? { Type: source.Type } : {}),
167
170
  };
168
171
  });
169
172
  }
173
+ function buildEnvDomainManagementResult(params) {
174
+ const { action, domains, result } = params;
175
+ const rawResult = result && typeof result === "object" && !Array.isArray(result)
176
+ ? result
177
+ : { result };
178
+ if (action === "create") {
179
+ return {
180
+ ...rawResult,
181
+ ok: true,
182
+ code: "DOMAIN_UPDATE_PENDING",
183
+ operation: action,
184
+ targetDomains: domains,
185
+ asyncState: "PENDING",
186
+ message: '安全域名已提交添加请求。该变更通常需要约 10 分钟传播,请继续轮询 envQuery(action="domains"),直到目标域名状态为 ENABLE。',
187
+ propagation: {
188
+ requiresPolling: true,
189
+ pollTool: "envQuery",
190
+ pollAction: "domains",
191
+ pollIntervalSuggestionSeconds: 10,
192
+ timeoutSuggestionSeconds: 600,
193
+ successCondition: '目标域名出现在 envQuery(action="domains") 返回中,且 Status 为 ENABLE。',
194
+ },
195
+ next_step: {
196
+ tool: "envQuery",
197
+ action: "domains",
198
+ suggested_args: {
199
+ action: "domains",
200
+ },
201
+ },
202
+ };
203
+ }
204
+ return {
205
+ ...rawResult,
206
+ ok: true,
207
+ code: "DOMAIN_DELETE_PENDING",
208
+ operation: action,
209
+ targetDomains: domains,
210
+ asyncState: "PENDING",
211
+ message: '安全域名已提交删除请求。该变更可能需要数分钟传播,请继续轮询 envQuery(action="domains"),直到目标域名不再出现。',
212
+ propagation: {
213
+ requiresPolling: true,
214
+ pollTool: "envQuery",
215
+ pollAction: "domains",
216
+ pollIntervalSuggestionSeconds: 10,
217
+ timeoutSuggestionSeconds: 600,
218
+ successCondition: '目标域名不再出现在 envQuery(action="domains") 返回中。',
219
+ },
220
+ next_step: {
221
+ tool: "envQuery",
222
+ action: "domains",
223
+ suggested_args: {
224
+ action: "domains",
225
+ },
226
+ },
227
+ };
228
+ }
170
229
  function formatDeviceAuthHint(deviceAuthInfo) {
171
230
  if (!deviceAuthInfo) {
172
231
  return "";
@@ -503,6 +562,49 @@ function normalizeOptionalToolString(value) {
503
562
  ? value.trim()
504
563
  : undefined;
505
564
  }
565
+ /**
566
+ * Build enhanced error message for envQuery tool errors
567
+ * Provides actionable guidance based on error patterns
568
+ */
569
+ function buildEnvQueryErrorMessage(error, action) {
570
+ const baseMessage = error instanceof Error ? error.message : String(error);
571
+ // Check for common error patterns and provide specific guidance
572
+ const hasInvalidParameterError = /400|invalid parameter|invalid argument|parameter value/i.test(baseMessage);
573
+ const hasAuthError = /未登录|auth required|unauthorized|authentication|credential|token|secret/i.test(baseMessage);
574
+ const hasNetworkError = /ECONNRESET|socket hang up|ETIMEDOUT|ENOTFOUND|timeout/i.test(baseMessage);
575
+ const hasPermissionError = /permission|denied|forbidden|无权|拒绝/i.test(baseMessage);
576
+ const hasEnvNotFoundError = /env|environment|环境.*不存在|not found/i.test(baseMessage);
577
+ const suggestions = [];
578
+ if (hasInvalidParameterError) {
579
+ suggestions.push("参数错误:可能是认证信息无效或已过期,请尝试以下步骤:");
580
+ suggestions.push("1. 先调用 auth(action=\"status\") 检查当前登录状态");
581
+ suggestions.push("2. 如果未登录,调用 auth(action=\"start_auth\", authMode=\"device\") 完成登录");
582
+ suggestions.push("3. 登录完成后再次调用 envQuery(action=\"list\")");
583
+ }
584
+ if (hasAuthError) {
585
+ suggestions.push("认证错误:当前未登录或认证已过期。");
586
+ suggestions.push("建议先执行 auth(action=\"status\") 查看状态,然后按提示完成登录。");
587
+ }
588
+ if (hasPermissionError) {
589
+ suggestions.push("权限错误:当前账号可能没有访问该资源的权限。");
590
+ suggestions.push("请确认:1) 已选择正确的环境 2) 账号有对应权限");
591
+ }
592
+ if (hasEnvNotFoundError) {
593
+ suggestions.push("环境错误:指定的环境不存在或无法访问。");
594
+ suggestions.push("请使用 envQuery(action=\"list\") 查看可用的环境列表。");
595
+ }
596
+ if (hasNetworkError) {
597
+ suggestions.push("网络错误:请检查网络连接,稍后重试。");
598
+ }
599
+ // If no specific pattern matched, provide general guidance
600
+ if (suggestions.length === 0) {
601
+ suggestions.push("查询环境信息时出错,建议:");
602
+ suggestions.push("1. 先调用 auth(action=\"status\") 确认登录状态");
603
+ suggestions.push("2. 如未登录,执行 auth(action=\"start_auth\") 完成认证");
604
+ suggestions.push("3. 确认环境 ID 正确且可访问");
605
+ }
606
+ return `[envQuery/${action}] 调用失败: ${baseMessage}\n\n解决建议:\n${suggestions.join("\n")}`;
607
+ }
506
608
  function normalizeOptionalToolBoolean(value) {
507
609
  return typeof value === "boolean" ? value : undefined;
508
610
  }
@@ -967,17 +1069,17 @@ function registerEnvTools(server) {
967
1069
  });
968
1070
  }
969
1071
  });
970
- // envQuery - 环境查询(合并 listEnvs + getEnvInfo + getEnvAuthDomains + getWebsiteConfig)
1072
+ // envQuery - 环境查询(合并 listEnvs + getEnvInfo + getEnvAuthDomains)
971
1073
  server.registerTool?.("envQuery", {
972
1074
  title: "环境查询",
973
- description: "查询云开发环境相关信息,支持查询环境列表、当前环境信息、安全域名和静态网站托管配置。(原工具名:listEnvs/getEnvInfo/getEnvAuthDomains/getWebsiteConfig,为兼容旧AI规则可继续使用这些名称)当 action=list 时,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知环境的详细信息,请使用 action=info。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。",
1075
+ description: "查询云开发环境相关信息,支持查询环境列表、指定环境详情和安全域名。(原工具名:listEnvs/getEnvInfo/getEnvAuthDomains,为兼容旧AI规则可继续使用这些名称)当 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 等计费字段)。",
974
1076
  inputSchema: {
975
1077
  action: zod_1.z
976
- .enum(["list", "info", "domains", "hosting"])
977
- .describe("查询类型:list=环境列表/摘要筛选(即使传 envId 也只返回 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,不支持 expiry),info=当前环境详细信息(详情中可查看更完整资源字段),domains=安全域名列表,hosting=静态网站托管配置"),
1078
+ .enum(["list", "info", "domains"])
1079
+ .describe("查询类型:list=环境列表/摘要筛选(按 DescribeEnvs 语义筛选,支持通过 envId 筛选,返回 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,不支持 expiry),info=指定环境的详细信息(必须传入 envId,返回资源字段和计费信息),domains=安全域名列表"),
978
1080
  alias: zod_1.z.string().optional().describe("按环境别名筛选。action=list 时可选"),
979
1081
  aliasExact: zod_1.z.boolean().optional().describe("按环境别名精确筛选。action=list 时可选;与 alias 配合使用"),
980
- envId: zod_1.z.string().optional().describe("按环境 ID 精确筛选。action=list 时可选;注意 list + envId 仍只返回摘要,如需该环境详情请改用 action=info"),
1082
+ envId: zod_1.z.string().optional().describe("按环境 ID 筛选。action=list 时可选(仅按 DescribeEnvs 语义做筛选,仍返回摘要);action=info 时必填(返回该环境的详细信息,包含资源字段和计费信息)。如果任务已经给出了明确的 EnvId 并要求查询详情,请直接使用 action=info + envId,而不是 action=list"),
981
1083
  limit: zod_1.z.number().int().positive().optional().describe("返回数量上限。action=list 时可选"),
982
1084
  offset: zod_1.z.number().int().min(0).optional().describe("分页偏移。action=list 时可选"),
983
1085
  fields: zod_1.z
@@ -1044,14 +1146,12 @@ function registerEnvTools(server) {
1044
1146
  return toolPayloadResult;
1045
1147
  }
1046
1148
  (0, logger_js_1.debug)("降级到 listEnvs() 也失败:", fallbackError instanceof Error ? fallbackError : new Error(String(fallbackError)));
1149
+ const enhancedMessage = buildEnvQueryErrorMessage(fallbackError, "list");
1047
1150
  return {
1048
1151
  content: [
1049
1152
  {
1050
1153
  type: "text",
1051
- text: "获取环境列表时出错: " +
1052
- (fallbackError instanceof Error
1053
- ? fallbackError.message
1054
- : String(fallbackError)),
1154
+ text: enhancedMessage,
1055
1155
  },
1056
1156
  ],
1057
1157
  };
@@ -1116,29 +1216,6 @@ function registerEnvTools(server) {
1116
1216
  };
1117
1217
  }
1118
1218
  break;
1119
- case "hosting": {
1120
- const cloudbaseHosting = await getManager();
1121
- const websiteConfig = await cloudbaseHosting.hosting.getWebsiteConfig();
1122
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, websiteConfig);
1123
- const hostingResult = {
1124
- ...websiteConfig,
1125
- CdnDomain: websiteConfig.CdnDomain ?? null,
1126
- Bucket: websiteConfig.Bucket ?? null,
1127
- };
1128
- try {
1129
- const envInfo = await cloudbaseHosting.env.getEnvInfo();
1130
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, envInfo);
1131
- hostingResult.CdnDomain = envInfo.EnvInfo?.StaticStorages?.[0]?.StaticDomain ?? hostingResult.CdnDomain;
1132
- hostingResult.Bucket = envInfo.EnvInfo?.StaticStorages?.[0]?.Bucket ?? hostingResult.Bucket;
1133
- }
1134
- catch (hostingInfoError) {
1135
- (0, logger_js_1.debug)("Failed to enrich hosting envQuery result with env info", {
1136
- error: hostingInfoError,
1137
- });
1138
- }
1139
- result = hostingResult;
1140
- break;
1141
- }
1142
1219
  default:
1143
1220
  throw new Error(`不支持的查询类型: ${action}`);
1144
1221
  }
@@ -1157,11 +1234,12 @@ function registerEnvTools(server) {
1157
1234
  if (toolPayloadResult) {
1158
1235
  return toolPayloadResult;
1159
1236
  }
1237
+ const enhancedMessage = buildEnvQueryErrorMessage(error, action);
1160
1238
  return {
1161
1239
  content: [
1162
1240
  {
1163
1241
  type: "text",
1164
- text: `环境查询失败: ${error instanceof Error ? error.message : String(error)}`,
1242
+ text: enhancedMessage,
1165
1243
  },
1166
1244
  ],
1167
1245
  };
@@ -1169,13 +1247,13 @@ function registerEnvTools(server) {
1169
1247
  });
1170
1248
  // envDomainManagement - 环境域名管理(合并 createEnvDomain + deleteEnvDomain)
1171
1249
  server.registerTool?.("envDomainManagement", {
1172
- title: "环境域名管理",
1173
- description: "管理云开发环境的安全域名,支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 或自定义域名直接访问 CloudBase 资源时,应先用 envQuery(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。",
1250
+ title: "环境域名管理(安全域名 / CORS 白名单)",
1251
+ description: "管理云开发环境的安全域名(安全域名 / CORS 白名单),支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 直接访问 CloudBase 资源时,应先用 envQuery(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。新增或删除后通常需要继续轮询 envQuery(action=domains) 确认状态收敛;安全域名一般约 10 分钟生效。⚠️ 重要:此工具仅用于 CORS/请求来源验证,不涉及 SSL 证书。如需绑定自定义域名供公网 HTTPS 访问,请使用 manageGateway(action=\"bindCustomDomain\")。",
1174
1252
  inputSchema: {
1175
1253
  action: zod_1.z
1176
1254
  .enum(["create", "delete"])
1177
- .describe("操作类型:create=添加域名,delete=删除域名"),
1178
- domains: zod_1.z.array(zod_1.z.string()).describe("安全域名数组"),
1255
+ .describe("操作类型:create=添加安全域名,delete=删除安全域名"),
1256
+ domains: zod_1.z.array(zod_1.z.string()).describe("安全域名数组(格式:host:port,例如 localhost:5173 或 127.0.0.1:4173)。注意:不是自定义域名,不需要证书。"),
1179
1257
  },
1180
1258
  annotations: {
1181
1259
  readOnlyHint: false,
@@ -1200,14 +1278,11 @@ function registerEnvTools(server) {
1200
1278
  default:
1201
1279
  throw new Error(`不支持的操作类型: ${action}`);
1202
1280
  }
1203
- return {
1204
- content: [
1205
- {
1206
- type: "text",
1207
- text: `${JSON.stringify(result, null, 2)}\n\n请注意安全域名需要10分钟才能生效,用户也应该了解这一点。`,
1208
- },
1209
- ],
1210
- };
1281
+ return (0, tool_result_js_1.buildJsonToolResult)(buildEnvDomainManagementResult({
1282
+ action,
1283
+ domains,
1284
+ result,
1285
+ }));
1211
1286
  }
1212
1287
  catch (error) {
1213
1288
  const toolPayloadResult = (0, tool_result_js_1.toolPayloadErrorToResult)(error);
@@ -1324,6 +1399,7 @@ function buildCapiErrorMessage(service, action, error) {
1324
1399
  const tcbEntry = service === "tcb" ? findTcbActionEntry(action) : undefined;
1325
1400
  const hasInvalidActionError = /invalid or not found|does not exist|not recognized/i.test(baseMessage);
1326
1401
  const hasParameterError = /parameter\s+`?.+?`?\s+is not recognized|MissingParameter|missing parameter|missing required/i.test(baseMessage);
1402
+ const hasInvalidParameterValueError = /invalid parameter value/i.test(baseMessage);
1327
1403
  if (hasInvalidActionError) {
1328
1404
  suggestions.push(`Action \`${action}\` 可能不存在或不对外开放。请不要继续猜测 Action 名称,先确认 service=\`${service}\` 下该 Action 在当前 API 版本是否真实存在。`);
1329
1405
  if (service === "tcb") {
@@ -1354,6 +1430,20 @@ function buildCapiErrorMessage(service, action, error) {
1354
1430
  }
1355
1431
  }
1356
1432
  }
1433
+ if (hasInvalidParameterValueError) {
1434
+ suggestions.push("请求参数值格式不正确或超出有效范围。请检查:");
1435
+ suggestions.push("1. 字符串参数是否为空或包含非法字符");
1436
+ suggestions.push("2. 数值参数是否在允许范围内");
1437
+ suggestions.push("3. 枚举值是否使用了正确的取值(区分大小写)");
1438
+ suggestions.push("4. 必填参数是否有值");
1439
+ if (service === "tcb" && tcbEntry) {
1440
+ const paramsTypeHint = formatTcbParamsTypeHint(tcbEntry.action);
1441
+ if (paramsTypeHint) {
1442
+ suggestions.push("参数类型参考:");
1443
+ suggestions.push(paramsTypeHint);
1444
+ }
1445
+ }
1446
+ }
1357
1447
  if (/ECONNRESET|socket hang up|ETIMEDOUT|ENOTFOUND/i.test(baseMessage)) {
1358
1448
  suggestions.push("网络请求异常,建议稍后重试,并检查本地网络/代理设置。");
1359
1449
  }
@@ -1532,7 +1622,7 @@ ${envIdSection}
1532
1622
  ## 环境信息
1533
1623
  - 操作系统: ${os_1.default.type()} ${os_1.default.release()}
1534
1624
  - Node.js版本: ${process.version}
1535
- - MCP 版本:${process.env.npm_package_version || "2.18.0" || 0}
1625
+ - MCP 版本:${process.env.npm_package_version || "2.19.5" || 0}
1536
1626
  - 系统架构: ${os_1.default.arch()}
1537
1627
  - 时间: ${new Date().toISOString()}
1538
1628
  - 请求ID: ${requestId}
@@ -1612,7 +1702,8 @@ function createWrappedHandler(name, handler, server) {
1612
1702
  requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
1613
1703
  ide: server.ide || process.env.INTEGRATION_IDE || ''
1614
1704
  });
1615
- const enhancedErrorMessage = `${errorMessage}\n\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
1705
+ const mcpVersion = true ? "2.19.5" : 0;
1706
+ const enhancedErrorMessage = `${errorMessage}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
1616
1707
  // 创建新的错误对象,保持原有的错误类型但更新消息
1617
1708
  const enhancedError = error instanceof Error
1618
1709
  ? new Error(enhancedErrorMessage)
@@ -1799,6 +1890,7 @@ const DEFAULT_PLUGINS = [
1799
1890
  "cloudrun",
1800
1891
  "gateway",
1801
1892
  "app-auth",
1893
+ "apps",
1802
1894
  "permissions",
1803
1895
  "logs",
1804
1896
  "agents",
@@ -1928,7 +2020,9 @@ async function createCloudBaseMcpServer(options) {
1928
2020
  }
1929
2021
  catch (error) {
1930
2022
  if ((0, tool_result_js_1.isToolPayloadError)(error)) {
1931
- return (0, tool_result_js_1.buildJsonToolResult)(error.payload);
2023
+ // 在结构化错误返回中注入 MCP 版本号
2024
+ const payload = { ...error.payload, mcpVersion: version };
2025
+ return (0, tool_result_js_1.buildJsonToolResult)(payload);
1932
2026
  }
1933
2027
  throw error;
1934
2028
  }
@@ -2055,16 +2149,59 @@ function registerLogTools(server) {
2055
2149
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
2056
2150
  server.registerTool?.("queryLogs", {
2057
2151
  title: "查询日志服务",
2058
- description: "日志域统一只读入口。支持检查日志服务状态并搜索 CLS 日志。",
2152
+ description: "日志域统一只读入口。支持检查日志服务状态并搜索 CLS 日志。" +
2153
+ "\n\n**重要区分**:" +
2154
+ "\n- 查询云函数日志:使用 `queryFunctions(action=\"listFunctionLogs\", functionName=\"xxx\")`" +
2155
+ "\n- 查询 CLS 日志(跨服务日志聚合):使用本工具 `queryLogs(action=\"searchLogs\")`" +
2156
+ "\n\n**适用场景**:" +
2157
+ "\n- 检查 CLS 日志服务是否开通:`action=\"checkLogService\"`" +
2158
+ "\n- 跨服务日志搜索(如搜索所有 ERROR 日志):`action=\"searchLogs\"`" +
2159
+ "\n- 按 CLS 语法检索特定服务的日志:`action=\"searchLogs\", service=\"tcb|tcbr\"`",
2059
2160
  inputSchema: {
2060
- action: zod_1.z.enum(QUERY_LOG_ACTIONS),
2061
- queryString: zod_1.z.string().optional(),
2062
- service: zod_1.z.enum(["tcb", "tcbr"]).optional(),
2063
- startTime: zod_1.z.string().optional(),
2064
- endTime: zod_1.z.string().optional(),
2065
- limit: zod_1.z.number().optional(),
2066
- context: zod_1.z.string().optional(),
2067
- sort: zod_1.z.enum(["asc", "desc"]).optional(),
2161
+ action: zod_1.z
2162
+ .enum(QUERY_LOG_ACTIONS)
2163
+ .describe("操作类型:" +
2164
+ "\n- `checkLogService`: 检查 CLS 日志服务是否开通" +
2165
+ "\n- `searchLogs`: 搜索 CLS 日志(需要提供 queryString)"),
2166
+ queryString: zod_1.z
2167
+ .string()
2168
+ .optional()
2169
+ .describe("CLS 查询语句,**action=\"searchLogs\" 时必填**,需严格遵循 CLS(Cloud Log Service)语法规范,详见 https://cloud.tencent.com/document/api/876/128127" +
2170
+ "\n\n**云函数相关查询**:" +
2171
+ "\n- 云函数日志:`(src:app OR src:system) AND log:\"START RequestId\"`" +
2172
+ "\n- 聚合云函数请求状态:`| select request_id, max(status_code) as status where ((request_id='44738f94-16dd-11f1-****' AND retry_num=0) AND retry_num=0) AND status_code!=202 group by request_id, retry_num`" +
2173
+ "\n\n**云数据库 / 文档型**:" +
2174
+ "\n- 云数据库(文档型):`module:database`" +
2175
+ "\n- 云数据库(文档型)事件:`module:database AND eventType:(MongoSlowQuery)`(MongoSlowQuery 为文档型数据库慢查询事件)" +
2176
+ "\n\n**云数据库 / SQL 型**:" +
2177
+ "\n- 云数据库(SQL 型):`module:rdb`" +
2178
+ "\n- 云数据库(SQL 型)事件:`module:rdb AND eventType:(MysqlFreeze OR MysqlRecover OR MysqlSlowQuery)`(MysqlFreeze 冻结、MysqlRecover 恢复、MysqlSlowQuery 慢查询)" +
2179
+ "\n\n**其它服务**:" +
2180
+ "\n- 审批流:`module:workflow`" +
2181
+ "\n- 模型:`module:model`" +
2182
+ "\n- 用户权限:`module:auth`" +
2183
+ "\n- 大模型:`module:llm AND logType:llm-tracelog`" +
2184
+ "\n- 网关服务调用:`logType:accesslog`" +
2185
+ "\n- 应用发布/删除事件:`module:app AND eventType:(AppProdPub OR AppProdDel)`(AppProdPub 发布事件、AppProdDel 删除事件)" +
2186
+ "\n\n以上仅为示例,实际使用时请根据具体日志内容调整。" +
2187
+ "\n\n**注意**:查询特定云函数的执行日志时,优先使用 `queryFunctions(action=\"listFunctionLogs\", functionName=\"xxx\")`。"),
2188
+ service: zod_1.z
2189
+ .enum(["tcb", "tcbr"])
2190
+ .optional()
2191
+ .describe("日志来源服务:" +
2192
+ "\n- `tcb`: 云函数、数据库、存储等基础服务日志" +
2193
+ "\n- `tcbr`: CloudRun 容器服务日志"),
2194
+ startTime: zod_1.z
2195
+ .string()
2196
+ .optional()
2197
+ .describe("查询开始时间,格式:`YYYY-MM-DD HH:mm:ss`,如 `2024-01-01 00:00:00`"),
2198
+ endTime: zod_1.z
2199
+ .string()
2200
+ .optional()
2201
+ .describe("查询结束时间,格式:`YYYY-MM-DD HH:mm:ss`,如 `2024-01-01 23:59:59`"),
2202
+ limit: zod_1.z.number().optional().describe("返回日志条数限制,默认 20"),
2203
+ context: zod_1.z.string().optional().describe("翻页上下文,用于继续上一次查询"),
2204
+ sort: zod_1.z.enum(["asc", "desc"]).optional().describe("按时间排序:`asc` 升序,`desc` 降序"),
2068
2205
  },
2069
2206
  annotations: {
2070
2207
  readOnlyHint: true,
@@ -2082,7 +2219,14 @@ function registerLogTools(server) {
2082
2219
  }, enabled ? "日志服务已开通" : "日志服务未开通或仍在初始化中"));
2083
2220
  }
2084
2221
  if (!queryString) {
2085
- throw new Error("action=searchLogs 时必须提供 queryString");
2222
+ throw new Error("action=\"searchLogs\" 时必须提供 queryString 参数(CLS 查询语句,需遵循 CLS 语法规范,参考 https://cloud.tencent.com/document/api/876/128127)。" +
2223
+ "\n\n常用查询示例:" +
2224
+ "\n- 云函数日志:`(src:app OR src:system) AND log:\"START RequestId\"`" +
2225
+ "\n- 文档型数据库:`module:database`" +
2226
+ "\n- SQL 型数据库:`module:rdb`" +
2227
+ "\n- 网关访问日志:`logType:accesslog`" +
2228
+ "\n- 大模型 trace 日志:`module:llm AND logType:llm-tracelog`" +
2229
+ "\n\n如果需要查询特定云函数的执行日志,建议使用 `queryFunctions(action=\"listFunctionLogs\", functionName=\"xxx\")`。");
2086
2230
  }
2087
2231
  const result = await cloudbase.log.searchClsLog({
2088
2232
  queryString,
@@ -3552,20 +3696,9 @@ async function getCloudBaseManager(options = {}) {
3552
3696
  }
3553
3697
  // envId priority: explicit option > envManager cache > loginState.envId
3554
3698
  const resolvedEnvId = finalEnvId || loginEnvId;
3555
- let region = fallbackRegion;
3556
- if (resolvedEnvId && !cloudBaseOptions?.region) {
3557
- try {
3558
- const envCandidates = await listAvailableEnvCandidates({ loginState });
3559
- const matchedEnv = envCandidates.find((candidate) => candidate.envId === resolvedEnvId);
3560
- if (matchedEnv?.region) {
3561
- region = matchedEnv.region;
3562
- (0, logger_js_1.debug)('使用环境实际 region:', { envId: resolvedEnvId, region });
3563
- }
3564
- }
3565
- catch {
3566
- (0, logger_js_1.debug)('无法获取环境列表,使用 fallback region');
3567
- }
3568
- }
3699
+ // region 直接使用 fallbackRegion(来自 cloudBaseOptions?.region ?? TCB_REGION ?? 'ap-shanghai')
3700
+ // 不再通过 DescribeEnvs 查询 region,以兼容 STS 临时密钥场景
3701
+ const region = fallbackRegion;
3569
3702
  const manager = new manager_node_1.default({
3570
3703
  secretId,
3571
3704
  secretKey,
@@ -3771,13 +3904,8 @@ async function resolveNoSqlInstanceId(options) {
3771
3904
  return resolved;
3772
3905
  }
3773
3906
  async function callNoSqlContentApi(options) {
3774
- const resolvedInstance = await resolveNoSqlInstanceId({
3775
- toolName: options.toolName,
3776
- cloudbase: options.cloudbase,
3777
- cloudBaseOptions: options.cloudBaseOptions,
3778
- instanceIdOverride: options.instanceIdOverride,
3779
- collectionName: options.collectionName,
3780
- });
3907
+ // 直接使用 EnvId 替代 Tag,无需通过 DescribeEnvs 获取 instanceId
3908
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(options.cloudBaseOptions);
3781
3909
  const startedAt = Date.now();
3782
3910
  try {
3783
3911
  const result = await options.cloudbase
@@ -3787,14 +3915,14 @@ async function callNoSqlContentApi(options) {
3787
3915
  Param: {
3788
3916
  ...options.param,
3789
3917
  TableName: options.collectionName,
3790
- Tag: resolvedInstance.instanceId,
3918
+ EnvId: envId,
3791
3919
  },
3792
3920
  });
3793
3921
  logNoSqlLatency(options.toolName, "cloudApiCall", {
3794
3922
  collectionName: options.collectionName,
3795
3923
  action: options.action,
3796
3924
  durationMs: Date.now() - startedAt,
3797
- instanceIdSource: resolvedInstance.source,
3925
+ instanceIdSource: "envId",
3798
3926
  requestId: result?.RequestId,
3799
3927
  });
3800
3928
  return result;
@@ -3804,20 +3932,9 @@ async function callNoSqlContentApi(options) {
3804
3932
  collectionName: options.collectionName,
3805
3933
  action: options.action,
3806
3934
  durationMs: Date.now() - startedAt,
3807
- instanceIdSource: resolvedInstance.source,
3935
+ instanceIdSource: "envId",
3808
3936
  message: error instanceof Error ? error.message : String(error),
3809
3937
  });
3810
- if (!options.instanceIdOverride &&
3811
- resolvedInstance.cacheKey &&
3812
- isInvalidInstanceIdError(error)) {
3813
- (0, cloudbase_manager_js_1.invalidateDatabaseInstanceIdCache)({
3814
- cacheKey: resolvedInstance.cacheKey,
3815
- });
3816
- logNoSqlLatency(options.toolName, "invalidateInstanceIdCache", {
3817
- collectionName: options.collectionName,
3818
- cacheKey: resolvedInstance.cacheKey,
3819
- });
3820
- }
3821
3938
  throw error;
3822
3939
  }
3823
3940
  }
@@ -3835,18 +3952,25 @@ async function waitForCollectionReady({ cloudbase, collectionName, logger, timeo
3835
3952
  });
3836
3953
  while (Date.now() <= deadline) {
3837
3954
  try {
3838
- const result = await cloudbase.database.checkCollectionExists(collectionName);
3955
+ // 直接用 commonService 调用 DescribeTable 检查集合是否存在,避免 SDK lazyInit → DescribeEnvs
3956
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)();
3957
+ const result = await cloudbase.commonService("tcb", "2018-06-08").call({
3958
+ Action: "DescribeTable",
3959
+ Param: {
3960
+ EnvId: envId,
3961
+ TableName: collectionName,
3962
+ },
3963
+ });
3839
3964
  (0, cloudbase_manager_js_1.logCloudBaseResult)(logger, result);
3840
- if (result.Exists) {
3841
- logger?.({
3842
- type: "toolInfo",
3843
- toolName: "writeNoSqlDatabaseStructure",
3844
- message: "NoSQL collection is ready for subsequent operations",
3845
- collectionName,
3846
- waitedMs: Date.now() - startedAt,
3847
- });
3848
- return;
3849
- }
3965
+ // DescribeTable 成功返回即表示集合存在
3966
+ logger?.({
3967
+ type: "toolInfo",
3968
+ toolName: "writeNoSqlDatabaseStructure",
3969
+ message: "NoSQL collection is ready for subsequent operations",
3970
+ collectionName,
3971
+ waitedMs: Date.now() - startedAt,
3972
+ });
3973
+ return;
3850
3974
  }
3851
3975
  catch (error) {
3852
3976
  lastError = error;
@@ -3878,7 +4002,7 @@ function registerDatabaseTools(server) {
3878
4002
  // readNoSqlDatabaseStructure
3879
4003
  server.registerTool?.("readNoSqlDatabaseStructure", {
3880
4004
  title: "读取 NoSQL 数据库结构",
3881
- description: "读取 NoSQL 数据库集合与索引结构,支持列出集合、查看集合详情、列出索引以及检查索引是否存在。",
4005
+ description: "读取 NoSQL 数据库集合与索引结构,支持列出集合、查看集合详情、列出索引以及检查索引是否存在。本工具为服务端管理工具,用于管理端查询数据库结构,不用于编写客户端代码。",
3882
4006
  inputSchema: {
3883
4007
  action: zod_1.z.enum([
3884
4008
  "listCollections",
@@ -3916,9 +4040,14 @@ checkIndex: 检查指定索引是否存在`),
3916
4040
  }, async ({ action, limit, offset, collectionName, indexName }) => {
3917
4041
  const cloudbase = await getManager();
3918
4042
  if (action === "listCollections") {
3919
- const result = await cloudbase.database.listCollections({
3920
- MgoOffset: offset,
3921
- MgoLimit: limit,
4043
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(server.cloudBaseOptions);
4044
+ const result = await cloudbase.commonService("tcb", "2018-06-08").call({
4045
+ Action: "ListTables",
4046
+ Param: {
4047
+ EnvId: envId,
4048
+ MgoOffset: offset ?? 0,
4049
+ MgoLimit: limit ?? 100,
4050
+ },
3922
4051
  });
3923
4052
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
3924
4053
  return {
@@ -3928,7 +4057,7 @@ checkIndex: 检查指定索引是否存在`),
3928
4057
  text: JSON.stringify({
3929
4058
  success: true,
3930
4059
  requestId: result.RequestId,
3931
- collections: result.Collections,
4060
+ collections: result.Tables,
3932
4061
  pager: result.Pager,
3933
4062
  message: "获取 NoSQL 数据库集合列表成功",
3934
4063
  }, null, 2),
@@ -3940,17 +4069,30 @@ checkIndex: 检查指定索引是否存在`),
3940
4069
  if (!collectionName) {
3941
4070
  throw new Error("检查集合时必须提供 collectionName");
3942
4071
  }
3943
- const result = await cloudbase.database.checkCollectionExists(collectionName);
3944
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
4072
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(server.cloudBaseOptions);
4073
+ let exists = false;
4074
+ let requestId = "";
4075
+ try {
4076
+ const result = await cloudbase.commonService("tcb", "2018-06-08").call({
4077
+ Action: "DescribeTable",
4078
+ Param: { EnvId: envId, TableName: collectionName },
4079
+ });
4080
+ exists = true;
4081
+ requestId = result?.RequestId ?? "";
4082
+ }
4083
+ catch (e) {
4084
+ // DescribeTable 失败即集合不存在
4085
+ requestId = e?.requestId ?? "";
4086
+ }
3945
4087
  return {
3946
4088
  content: [
3947
4089
  {
3948
4090
  type: "text",
3949
4091
  text: JSON.stringify(withCollectionName(collectionName, {
3950
4092
  success: true,
3951
- exists: result.Exists,
3952
- requestId: result.RequestId,
3953
- message: result.Exists
4093
+ exists,
4094
+ requestId,
4095
+ message: exists
3954
4096
  ? "云开发数据库集合已存在"
3955
4097
  : "云开发数据库集合不存在",
3956
4098
  }), null, 2),
@@ -3962,7 +4104,11 @@ checkIndex: 检查指定索引是否存在`),
3962
4104
  if (!collectionName) {
3963
4105
  throw new Error("查看集合详情时必须提供 collectionName");
3964
4106
  }
3965
- const result = await cloudbase.database.describeCollection(collectionName);
4107
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(server.cloudBaseOptions);
4108
+ const result = await cloudbase.commonService("tcb", "2018-06-08").call({
4109
+ Action: "DescribeTable",
4110
+ Param: { EnvId: envId, TableName: collectionName },
4111
+ });
3966
4112
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
3967
4113
  return {
3968
4114
  content: [
@@ -3983,7 +4129,11 @@ checkIndex: 检查指定索引是否存在`),
3983
4129
  if (!collectionName) {
3984
4130
  throw new Error("获取索引列表时必须提供 collectionName");
3985
4131
  }
3986
- const result = await cloudbase.database.describeCollection(collectionName);
4132
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(server.cloudBaseOptions);
4133
+ const result = await cloudbase.commonService("tcb", "2018-06-08").call({
4134
+ Action: "DescribeTable",
4135
+ Param: { EnvId: envId, TableName: collectionName },
4136
+ });
3987
4137
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
3988
4138
  return {
3989
4139
  content: [
@@ -4004,8 +4154,22 @@ checkIndex: 检查指定索引是否存在`),
4004
4154
  if (!collectionName || !indexName) {
4005
4155
  throw new Error("检查索引时必须提供 collectionName 和 indexName");
4006
4156
  }
4007
- const result = await cloudbase.database.checkIndexExists(collectionName, indexName);
4008
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
4157
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(server.cloudBaseOptions);
4158
+ let exists = false;
4159
+ let requestId = "";
4160
+ try {
4161
+ const result = await cloudbase.commonService("tcb", "2018-06-08").call({
4162
+ Action: "DescribeTable",
4163
+ Param: { EnvId: envId, TableName: collectionName },
4164
+ });
4165
+ requestId = result?.RequestId ?? "";
4166
+ const indexes = result?.Indexes ?? result?.IndexNum ? result?.Indexes : [];
4167
+ exists = Array.isArray(indexes) && indexes.some((idx) => idx.IndexName === indexName);
4168
+ }
4169
+ catch (e) {
4170
+ requestId = e?.requestId ?? "";
4171
+ }
4172
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, { RequestId: requestId });
4009
4173
  return {
4010
4174
  content: [
4011
4175
  {
@@ -4013,9 +4177,9 @@ checkIndex: 检查指定索引是否存在`),
4013
4177
  text: JSON.stringify(withCollectionName(collectionName, {
4014
4178
  success: true,
4015
4179
  indexName,
4016
- exists: result.Exists,
4017
- requestId: result.RequestId,
4018
- message: result.Exists ? "索引已存在" : "索引不存在",
4180
+ exists,
4181
+ requestId,
4182
+ message: exists ? "索引已存在" : "索引不存在",
4019
4183
  }), null, 2),
4020
4184
  },
4021
4185
  ],
@@ -4025,8 +4189,8 @@ checkIndex: 检查指定索引是否存在`),
4025
4189
  });
4026
4190
  // writeNoSqlDatabaseStructure
4027
4191
  server.registerTool?.("writeNoSqlDatabaseStructure", {
4028
- title: "修改 NoSQL 数据库结构",
4029
- description: "修改 NoSQL 数据库结构,支持创建/删除集合,以及通过 updateCollection 的 updateOptions.CreateIndexes / updateOptions.DropIndexes 添加索引和删除索引。",
4192
+ title: "创建并管理 NoSQL 数据库集合",
4193
+ description: "创建、删除和管理 NoSQL 数据库集合(collection)。支持创建新集合、删除现有集合,以及通过 updateCollection 的 updateOptions.CreateIndexes / updateOptions.DropIndexes 添加索引和删除索引。当需要新建集合时,使用 action=createCollection。本工具为服务端管理工具,用于管理端操作集合和索引结构,不用于编写客户端代码。",
4030
4194
  inputSchema: {
4031
4195
  action: zod_1.z.enum([
4032
4196
  "createCollection",
@@ -4073,9 +4237,22 @@ deleteCollection: 删除集合`),
4073
4237
  }, async ({ action, collectionName, updateOptions }) => {
4074
4238
  const cloudbase = await getManager();
4075
4239
  if (action === "createCollection") {
4076
- const existsResult = await cloudbase.database.checkCollectionExists(collectionName);
4077
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, existsResult);
4078
- if (existsResult.Exists) {
4240
+ // 检查集合是否存在 - 使用 commonService 避免 SDK lazyInit → DescribeEnvs
4241
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(server.cloudBaseOptions);
4242
+ let collectionExists = false;
4243
+ let existsRequestId = "";
4244
+ try {
4245
+ const descResult = await cloudbase.commonService("tcb", "2018-06-08").call({
4246
+ Action: "DescribeTable",
4247
+ Param: { EnvId: envId, TableName: collectionName },
4248
+ });
4249
+ collectionExists = true;
4250
+ existsRequestId = descResult?.RequestId ?? "";
4251
+ }
4252
+ catch (e) {
4253
+ existsRequestId = e?.requestId ?? "";
4254
+ }
4255
+ if (collectionExists) {
4079
4256
  return {
4080
4257
  content: [
4081
4258
  {
@@ -4083,7 +4260,7 @@ deleteCollection: 删除集合`),
4083
4260
  text: JSON.stringify(withCollectionName(collectionName, {
4084
4261
  success: true,
4085
4262
  action,
4086
- requestId: existsResult.RequestId,
4263
+ requestId: existsRequestId,
4087
4264
  message: "集合已存在,无需重复创建",
4088
4265
  exists: true,
4089
4266
  }), null, 2),
@@ -4091,7 +4268,13 @@ deleteCollection: 删除集合`),
4091
4268
  ],
4092
4269
  };
4093
4270
  }
4094
- const result = await cloudbase.database.createCollection(collectionName);
4271
+ const result = await cloudbase.commonService("tcb", "2018-06-08").call({
4272
+ Action: "CreateTable",
4273
+ Param: {
4274
+ EnvId: await (0, cloudbase_manager_js_1.getEnvId)(server.cloudBaseOptions),
4275
+ TableName: collectionName,
4276
+ },
4277
+ });
4095
4278
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
4096
4279
  await waitForCollectionReady({
4097
4280
  cloudbase,
@@ -4116,7 +4299,14 @@ deleteCollection: 删除集合`),
4116
4299
  if (!updateOptions) {
4117
4300
  throw new Error("更新集合时必须提供 options");
4118
4301
  }
4119
- const result = await cloudbase.database.updateCollection(collectionName, updateOptions);
4302
+ const result = await cloudbase.commonService("tcb", "2018-06-08").call({
4303
+ Action: "UpdateTable",
4304
+ Param: {
4305
+ EnvId: await (0, cloudbase_manager_js_1.getEnvId)(server.cloudBaseOptions),
4306
+ TableName: collectionName,
4307
+ ...updateOptions,
4308
+ },
4309
+ });
4120
4310
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
4121
4311
  return {
4122
4312
  content: [
@@ -4133,7 +4323,13 @@ deleteCollection: 删除集合`),
4133
4323
  };
4134
4324
  }
4135
4325
  if (action === "deleteCollection") {
4136
- const result = await cloudbase.database.deleteCollection(collectionName);
4326
+ const result = await cloudbase.commonService("tcb", "2018-06-08").call({
4327
+ Action: "DeleteTable",
4328
+ Param: {
4329
+ EnvId: await (0, cloudbase_manager_js_1.getEnvId)(server.cloudBaseOptions),
4330
+ TableName: collectionName,
4331
+ },
4332
+ });
4137
4333
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
4138
4334
  const body = withCollectionName(collectionName, {
4139
4335
  success: true,
@@ -4158,7 +4354,7 @@ deleteCollection: 删除集合`),
4158
4354
  // readNoSqlDatabaseContent
4159
4355
  server.registerTool?.("readNoSqlDatabaseContent", {
4160
4356
  title: "查询并获取 NoSQL 数据库数据记录",
4161
- description: "查询并获取 NoSQL 数据库数据记录",
4357
+ description: "查询并获取 NoSQL 数据库数据记录。⚠️ 本工具为服务端管理工具,用于管理端/运维端查询。当任务要求编写客户端应用代码时(例如「用 JS SDK 读取数据」),不应使用本工具,而应在项目代码中编写 @cloudbase/js-sdk 客户端代码(如 db.collection().where().get())。",
4162
4358
  inputSchema: {
4163
4359
  collectionName: zod_1.z.string().describe("集合名称"),
4164
4360
  instanceId: zod_1.z
@@ -4245,7 +4441,7 @@ deleteCollection: 删除集合`),
4245
4441
  // writeNoSqlDatabaseContent
4246
4442
  server.registerTool?.("writeNoSqlDatabaseContent", {
4247
4443
  title: "修改 NoSQL 数据库数据记录",
4248
- description: "修改 NoSQL 数据库数据记录。可按 MongoDB updateOne/updateMany 的心智模型理解:部分更新必须使用 `$set`、`$inc`、`$push` 等更新操作符;如果直接传“字段到值的普通对象”这类内容,底层会把它当作替换内容,存在覆盖整条文档的风险。更新嵌套对象中的某个字段时必须使用点号路径,例如把 `address.city` 设为 `shenzhen`;如果把整个 `address` 对象作为 `$set` 的值传入,则整个 `address` 对象会被替换,同级其他字段将丢失。若集合中的角色/档案文档会在前端通过 `db.collection(...).doc(uid)` 读取,请确保文档 `_id` 就是该 `uid`;不要用按 `uid` 条件查询再配合 `upsert=true` 的方式去更新 `users` / `profiles`,否则经常会生成一个不同的 `_id`,导致后续 `doc(uid)` 读取命中不到。",
4444
+ description: "修改 NoSQL 数据库数据记录。⚠️ 本工具为服务端管理工具,用于管理端/运维端操作(如后台脚本、数据迁移、批量修改)。当任务要求编写客户端应用代码时(例如「用 JS SDK 登录并插入数据」、「在前端读写数据库」),不应使用本工具,而应在项目代码中编写 @cloudbase/js-sdk 客户端代码(如 app.database().collection().add()、db.collection().where().get() 等)。可按 MongoDB updateOne/updateMany 的心智模型理解:部分更新必须使用 `$set`、`$inc`、`$push` 等更新操作符;如果直接传「字段到值的普通对象」这类内容,底层会把它当作替换内容,存在覆盖整条文档的风险。⚠️ 嵌套对象部分更新务必使用点号路径:要更新 `shipping.city`,应传 `$set: {\"shipping.city\": \"guangzhou\"}`,绝不能传 `$set: {\"shipping\": {\"city\": \"guangzhou\"}}`(后者会丢失 shipping 下的其他字段)。若集合中的角色/档案文档会在前端通过 `db.collection(...).doc(uid)` 读取,请确保文档 `_id` 就是该 `uid`;不要用按 `uid` 条件查询再配合 `upsert=true` 的方式去更新 `users` / `profiles`,否则经常会生成一个不同的 `_id`,导致后续 `doc(uid)` 读取命中不到。",
4249
4445
  inputSchema: {
4250
4446
  action: zod_1.z
4251
4447
  .enum(["insert", "update", "delete"])
@@ -4266,7 +4462,11 @@ deleteCollection: 删除集合`),
4266
4462
  update: zod_1.z
4267
4463
  .union([zod_1.z.object({}).passthrough(), zod_1.z.string()])
4268
4464
  .optional()
4269
- .describe("更新内容(对象或字符串,推荐对象)(update 操作必填)。按 MongoDB 更新语义传入 MgoUpdate:部分更新请使用 `$set`、`$inc`、`$unset`、`$push` 等操作符,例如使用 `$set` 更新 `status`;不要直接传“字段到值的普通对象”,否则可能替换整条文档。更新嵌套字段时必须使用点号路径,例如通过 `$set` 更新 `address.city`;不要把整个 `address` 对象作为 `$set` 的值传入,否则会替换整个 `address` 对象。"),
4465
+ .describe(`更新内容(对象或字符串,推荐对象)(update 操作必填)。按 MongoDB 更新语义传入 MgoUpdate:部分更新请使用 \`$set\`、\`$inc\`、\`$unset\`、\`$push\` 等操作符,例如使用 \`$set\` 更新 \`status\`;不要直接传"字段到值的普通对象",否则可能替换整条文档。
4466
+
4467
+ ⚠️ 嵌套字段必须用点号路径(如 \`shipping.city\`),禁止整对象替换:
4468
+ - ❌ 错误:{ "$set": { "shipping": { "city": "guangzhou" } } } — shipping 被整块替换,原有 address/province 等字段全部丢失
4469
+ - ✅ 正确:{ "$set": { "shipping.city": "guangzhou" } } — 仅更新 city,shipping 下其他字段保留`),
4270
4470
  isMulti: zod_1.z
4271
4471
  .boolean()
4272
4472
  .optional()
@@ -5729,11 +5929,24 @@ function requireStringParam(value, fieldName, action) {
5729
5929
  }
5730
5930
  return value.trim();
5731
5931
  }
5932
+ function buildOptionalStringEnum(values, description) {
5933
+ const uniqueValues = [...new Set(values.filter((value) => value.trim()))];
5934
+ if (uniqueValues.length > 0) {
5935
+ return zod_1.z
5936
+ .enum(uniqueValues)
5937
+ .optional()
5938
+ .describe(description);
5939
+ }
5940
+ return zod_1.z
5941
+ .string()
5942
+ .optional()
5943
+ .describe(`${description} 当前暂时无法枚举可选值;如需查看当前可用项,可直接传入字符串,工具会在执行时返回可用列表。`);
5944
+ }
5732
5945
  // OpenAPI 文档 URL 列表
5733
5946
  const OPENAPI_SOURCES = [
5734
5947
  {
5735
5948
  name: "mysqldb",
5736
- description: "MySQL RESTful API - 云开发 MySQL 数据库 HTTP API",
5949
+ description: "关系型数据库 RESTful API (MySQL/PostgreSQL) - 云开发关系型数据库 HTTP API",
5737
5950
  url: "https://docs.cloudbase.net/openapi/mysqldb.v1.openapi.yaml",
5738
5951
  },
5739
5952
  {
@@ -5756,6 +5969,16 @@ const OPENAPI_SOURCES = [
5756
5969
  description: "Storage API - 云存储 HTTP API",
5757
5970
  url: "https://docs.cloudbase.net/openapi/storage.v1.openapi.yaml",
5758
5971
  },
5972
+ {
5973
+ name: "nosql",
5974
+ description: "NoSQL RESTful API - 文档型数据库 HTTP API",
5975
+ url: "https://docs.cloudbase.net/openapi/nosql.v1.openapi.yaml",
5976
+ },
5977
+ {
5978
+ name: "ai_model",
5979
+ description: "AI 大模型接入 API - 统一 AI 模型 HTTP API",
5980
+ url: "https://docs.cloudbase.net/openapi/ai_model.v1.openapi.yaml",
5981
+ },
5759
5982
  ];
5760
5983
  async function downloadWebTemplate() {
5761
5984
  const zipPath = path.join(CACHE_BASE_DIR, "web-cloudbase-project.zip");
@@ -6031,6 +6254,8 @@ async function registerRagTools(server) {
6031
6254
  error,
6032
6255
  });
6033
6256
  }
6257
+ const skillNames = skills.map((skill) => path.basename(path.dirname(skill.absolutePath)));
6258
+ const openapiNames = openapis.map((api) => api.name);
6034
6259
  const getDocsManager = () => (0, cloudbase_manager_js_1.createCloudBaseManagerWithOptions)(server.cloudBaseOptions ?? {})
6035
6260
  ?.docs;
6036
6261
  server.registerTool?.("searchKnowledgeBase", {
@@ -6039,6 +6264,11 @@ async function registerRagTools(server) {
6039
6264
 
6040
6265
  强烈推荐始终优先使用固定技能文档 (skill)、OpenAPI 文档 (openapi) 或 CloudBase 官方文档 (docs) 模式进行检索,仅当固定文档无法覆盖你的问题时,再使用向量查询 (vector) 模式。
6041
6266
 
6267
+ ⚠️ 重要:当 CloudBase skills 处于禁用状态或当前 IDE 不支持 skill 文件读取时,必须使用 searchKnowledgeBase(mode=skill, skillName=...) 来获取 CloudBase 技能文档内容,而不是尝试直接读取 skill 文件。直接读取可能返回 400 错误。示例:
6268
+ - 需要 auth-tool 指南时:searchKnowledgeBase(mode=skill, skillName=auth-tool)
6269
+ - 需要 auth-web 指南时:searchKnowledgeBase(mode=skill, skillName=auth-web)
6270
+ - 需要 cloudbase-agent 指南时:searchKnowledgeBase(mode=skill, skillName=cloudbase-agent)
6271
+
6042
6272
  固定技能文档 (skill) 查询当前支持 ${skills.length} 个固定文档,分别是:
6043
6273
  ${skills
6044
6274
  .map((skill) => `文档名:${path.basename(path.dirname(skill.absolutePath))} 文档介绍:${skill.description}`)
@@ -6050,14 +6280,8 @@ async function registerRagTools(server) {
6050
6280
  .join("\n")}`,
6051
6281
  inputSchema: {
6052
6282
  mode: SearchKnowledgeModeEnum,
6053
- skillName: zod_1.z
6054
- .enum(skills.map((skill) => path.basename(path.dirname(skill.absolutePath))))
6055
- .optional()
6056
- .describe("mode=skill 时指定。技能名称。"),
6057
- apiName: zod_1.z
6058
- .enum(openapis.map((api) => api.name))
6059
- .optional()
6060
- .describe("mode=openapi 时指定。API 名称。"),
6283
+ skillName: buildOptionalStringEnum(skillNames, "mode=skill 时指定。技能名称。"),
6284
+ apiName: buildOptionalStringEnum(openapiNames, "mode=openapi 时指定。API 名称。"),
6061
6285
  action: CloudBaseDocsActionEnum.optional().describe("mode=docs 时指定。CloudBase 文档操作类型:listModules=列出所有文档模块,listModuleDocs=获取指定模块的目录结构,findByName=按名称/路径/URL 智能查找,readDoc=读取指定文档 Markdown,searchDocs=全文搜索官方文档。"),
6062
6286
  moduleName: zod_1.z
6063
6287
  .string()
@@ -6150,7 +6374,7 @@ async function registerRagTools(server) {
6150
6374
  content: [
6151
6375
  {
6152
6376
  type: "text",
6153
- text: `Skill document "${skillName}" not found. Available skill docs: ${skills.map((item) => path.basename(path.dirname(item.absolutePath))).join(", ") || "none"}`,
6377
+ text: `Skill document "${skillName}" not found. Available skill docs: ${skillNames.join(", ") || "none"}`,
6154
6378
  },
6155
6379
  ],
6156
6380
  };
@@ -6171,7 +6395,7 @@ async function registerRagTools(server) {
6171
6395
  content: [
6172
6396
  {
6173
6397
  type: "text",
6174
- text: `OpenAPI document "${apiName}" not found. Available APIs: ${openapis.map((a) => a.name).join(", ") || "none"}`,
6398
+ text: `OpenAPI document "${apiName}" not found. Available APIs: ${openapiNames.join(", ") || "none"}`,
6175
6399
  },
6176
6400
  ],
6177
6401
  };
@@ -6323,17 +6547,10 @@ function normalizeAccessPath(path) {
6323
6547
  }
6324
6548
  return path.startsWith("/") ? path : `/${path}`;
6325
6549
  }
6326
- function ensureGatewayEnvId(cloudBaseOptions) {
6327
- const envId = cloudBaseOptions?.envId;
6328
- if (!envId) {
6329
- throw new Error("当前网关操作需要已绑定 envId");
6330
- }
6331
- return envId;
6332
- }
6333
6550
  function registerGatewayTools(server) {
6334
6551
  const cloudBaseOptions = server.cloudBaseOptions;
6335
6552
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
6336
- const getGatewayEnvId = () => ensureGatewayEnvId(cloudBaseOptions);
6553
+ const resolveEnvId = () => (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
6337
6554
  const buildEnvelope = (data, message, nextActions) => ({
6338
6555
  success: true,
6339
6556
  data,
@@ -6370,7 +6587,7 @@ function registerGatewayTools(server) {
6370
6587
  const listHttpServiceRoutes = async (domain) => {
6371
6588
  const cloudbase = await getManager();
6372
6589
  const result = await cloudbase.env.describeHttpServiceRoute({
6373
- EnvId: getGatewayEnvId(),
6590
+ EnvId: await resolveEnvId(),
6374
6591
  ...(domain
6375
6592
  ? {
6376
6593
  Filters: [
@@ -6406,7 +6623,7 @@ function registerGatewayTools(server) {
6406
6623
  throw new Error("route.serviceType 和 route.serviceName 为必填项");
6407
6624
  }
6408
6625
  return {
6409
- EnvId: getGatewayEnvId(),
6626
+ EnvId: await resolveEnvId(),
6410
6627
  Domain: {
6411
6628
  Domain: await resolveRouteDomain(domain),
6412
6629
  Routes: [
@@ -6516,6 +6733,9 @@ function registerGatewayTools(server) {
6516
6733
  if (!input.targetName || !input.targetType) {
6517
6734
  throw new Error("action=createAccess 时必须提供 targetType 和 targetName");
6518
6735
  }
6736
+ if (!input.type) {
6737
+ throw new Error("action=createAccess 且 targetType=function 时必须显式提供 type。HTTP 云函数传 HTTP;Event 函数传 Event。省略会默认按 Event 路由处理,可能让 HTTP 云函数访问后返回 FUNCTION_PARAM_INVALID。");
6738
+ }
6519
6739
  const cloudbase = await getManager();
6520
6740
  const accessPath = normalizeAccessPath(input.path || `/${input.targetName}`);
6521
6741
  let result;
@@ -6523,7 +6743,7 @@ function registerGatewayTools(server) {
6523
6743
  result = await cloudbase.access.createAccess({
6524
6744
  name: input.targetName,
6525
6745
  path: accessPath,
6526
- type: ((input.type || "Event") === "HTTP" ? 6 : 1),
6746
+ type: (input.type === "HTTP" ? 6 : 1),
6527
6747
  auth: input.auth,
6528
6748
  });
6529
6749
  }
@@ -6531,7 +6751,7 @@ function registerGatewayTools(server) {
6531
6751
  if (err.message && err.message.includes("An error has occurred")) {
6532
6752
  let hint = "为目标资源配置访问路由失败(后端内部错误)。请确保:1) 目标云函数已成功创建并处于 Active 状态;2) 环境默认 HTTP 域名已完成初始化;3) 该访问路径未被占用。";
6533
6753
  if (input.type === "HTTP") {
6534
- hint += "此外注意:如果目标函数最初是作为 Event 函数创建的,这里 type 必须依然传 Event(或省略),传 HTTP 会导致此错误。";
6754
+ hint += "此外注意:如果目标函数最初是作为 Event 函数创建的,这里 type 仍必须传 Event;误传 HTTP 会导致此错误。";
6535
6755
  }
6536
6756
  throw new Error(`${hint} 原始错误:${err.message}`);
6537
6757
  }
@@ -6544,12 +6764,22 @@ function registerGatewayTools(server) {
6544
6764
  targetName: input.targetName,
6545
6765
  path: accessPath,
6546
6766
  raw: result,
6547
- }, `已为目标 ${input.targetName} 创建网关访问路径。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。`, [
6767
+ }, `已为目标 ${input.targetName} 创建网关访问路径。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。该操作只创建网关入口,不会自动放开函数安全规则;若需要匿名或浏览器直接访问,请继续检查函数资源权限。`, [
6548
6768
  {
6549
6769
  tool: "queryGateway",
6550
6770
  action: "getAccess",
6551
6771
  reason: "等待 30 秒到 3 分钟后再确认访问入口是否已生效",
6552
6772
  },
6773
+ {
6774
+ tool: "queryPermissions",
6775
+ action: "getResourcePermission",
6776
+ reason: "确认函数安全规则是否允许预期访问方;网关 auth=false 不等于函数已允许匿名访问",
6777
+ },
6778
+ {
6779
+ tool: "managePermissions",
6780
+ action: "updateResourcePermission",
6781
+ reason: "只有在确认需要匿名或浏览器直连访问时,才按实际安全要求更新函数权限",
6782
+ },
6553
6783
  ]);
6554
6784
  }
6555
6785
  case "createRoute": {
@@ -6580,7 +6810,7 @@ function registerGatewayTools(server) {
6580
6810
  const cloudbase = await getManager();
6581
6811
  const domain = await resolveRouteDomain(input.domain);
6582
6812
  const result = await cloudbase.env.deleteHttpServiceRoute({
6583
- EnvId: getGatewayEnvId(),
6813
+ EnvId: await resolveEnvId(),
6584
6814
  Domain: domain,
6585
6815
  Paths: [normalizeAccessPath(routePath)],
6586
6816
  });
@@ -6598,7 +6828,7 @@ function registerGatewayTools(server) {
6598
6828
  }
6599
6829
  const cloudbase = await getManager();
6600
6830
  const result = await cloudbase.env.bindCustomDomain({
6601
- EnvId: getGatewayEnvId(),
6831
+ EnvId: await resolveEnvId(),
6602
6832
  Domain: {
6603
6833
  Domain: input.domain,
6604
6834
  CertId: input.certificateId,
@@ -6618,7 +6848,7 @@ function registerGatewayTools(server) {
6618
6848
  }
6619
6849
  const cloudbase = await getManager();
6620
6850
  const result = await cloudbase.env.deleteCustomDomain({
6621
- EnvId: getGatewayEnvId(),
6851
+ EnvId: await resolveEnvId(),
6622
6852
  Domain: input.domain,
6623
6853
  });
6624
6854
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
@@ -6702,18 +6932,32 @@ function registerGatewayTools(server) {
6702
6932
  },
6703
6933
  }, async (input) => withEnvelope(() => handleQueryGateway(input)));
6704
6934
  server.registerTool?.("manageGateway", {
6705
- title: "管理网关域资源",
6706
- description: "网关域统一写入口。通过 action 创建目标访问入口,后续承接更通用的网关配置能力。",
6935
+ title: "管理网关域资源(含自定义域名绑定)",
6936
+ description: "网关域统一写入口。通过 action 创建目标访问入口,后续承接更通用的网关配置能力。为已存在的 HTTP 云函数补默认域名访问时,通常使用 createAccess 并提供 targetType=\"function\"、targetName、type=\"HTTP\" 与期望 path。注意 createAccess 只创建网关入口,不会自动修改函数资源权限。⚠️ 如需绑定带 SSL 证书的自定义域名供公网 HTTPS 访问,使用 action=\"bindCustomDomain\"(需要 domain 和 certificateId 参数);如需配置 CORS/安全域名(无证书),请使用 envDomainManagement 工具。",
6707
6937
  inputSchema: {
6708
- action: zod_1.z.enum(MANAGE_GATEWAY_ACTIONS).describe("写操作类型,例如 createAccess"),
6938
+ action: zod_1.z
6939
+ .enum(MANAGE_GATEWAY_ACTIONS)
6940
+ .describe('写操作类型,例如 createAccess。为已有函数补默认域名访问入口时使用 createAccess;若 action=createAccess 且 targetType=function,必须显式提供 type。'),
6709
6941
  targetType: zod_1.z
6710
6942
  .enum(["function"])
6711
6943
  .optional()
6712
6944
  .describe("目标资源类型。当前支持 function,后续可扩展"),
6713
- targetName: zod_1.z.string().optional().describe("目标资源名称"),
6714
- path: zod_1.z.string().optional().describe("访问路径,默认 /{targetName}"),
6715
- type: zod_1.z.enum(["Event", "HTTP"]).optional().describe("目标函数的本身类型(非接入形式)。如果被访问的函数是 Event 型(默认),此处必须传 Event;只有当被访问函数在创建时就是 HTTP 函数时才传 HTTP。"),
6716
- auth: zod_1.z.boolean().optional().describe("是否开启鉴权"),
6945
+ targetName: zod_1.z
6946
+ .string()
6947
+ .optional()
6948
+ .describe("目标资源名称。createAccess 到云函数时填写函数名"),
6949
+ path: zod_1.z
6950
+ .string()
6951
+ .optional()
6952
+ .describe("访问路径,默认 /{targetName}。例如为 HTTP 函数暴露 /api/hello 时传 /api/hello。该参数只创建网关入口,不会自动放开函数资源权限。"),
6953
+ type: zod_1.z
6954
+ .enum(["Event", "HTTP"])
6955
+ .optional()
6956
+ .describe("目标函数的运行时类型,不是接入协议。createAccess 到已创建的 HTTP 云函数时传 HTTP;给 Event 函数补网关访问时传 Event 或省略。省略会默认按 Event 路由处理,可能让 HTTP 云函数访问后返回 FUNCTION_PARAM_INVALID。"),
6957
+ auth: zod_1.z
6958
+ .boolean()
6959
+ .optional()
6960
+ .describe("是否开启网关路径鉴权。若要走默认域名做匿名或浏览器访问,通常设为 false;该开关仅控制网关入口本身,不会修改函数资源权限,仍需检查并按需调整函数安全规则。"),
6717
6961
  route: zod_1.z
6718
6962
  .object({
6719
6963
  routeId: zod_1.z.string().optional(),
@@ -6847,16 +7091,54 @@ function registerInviteCodeTools(server) {
6847
7091
  /***/ }),
6848
7092
 
6849
7093
  /***/ 4848:
6850
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
7094
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
6851
7095
 
6852
7096
 
7097
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7098
+ if (k2 === undefined) k2 = k;
7099
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7100
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7101
+ desc = { enumerable: true, get: function() { return m[k]; } };
7102
+ }
7103
+ Object.defineProperty(o, k2, desc);
7104
+ }) : (function(o, m, k, k2) {
7105
+ if (k2 === undefined) k2 = k;
7106
+ o[k2] = m[k];
7107
+ }));
7108
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
7109
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
7110
+ }) : function(o, v) {
7111
+ o["default"] = v;
7112
+ });
7113
+ var __importStar = (this && this.__importStar) || (function () {
7114
+ var ownKeys = function(o) {
7115
+ ownKeys = Object.getOwnPropertyNames || function (o) {
7116
+ var ar = [];
7117
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
7118
+ return ar;
7119
+ };
7120
+ return ownKeys(o);
7121
+ };
7122
+ return function (mod) {
7123
+ if (mod && mod.__esModule) return mod;
7124
+ var result = {};
7125
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
7126
+ __setModuleDefault(result, mod);
7127
+ return result;
7128
+ };
7129
+ })();
6853
7130
  Object.defineProperty(exports, "__esModule", ({ value: true }));
6854
7131
  exports.registerStorageTools = registerStorageTools;
7132
+ const fs = __importStar(__webpack_require__(7932));
7133
+ const os = __importStar(__webpack_require__(8116));
7134
+ const path = __importStar(__webpack_require__(2521));
6855
7135
  const zod_1 = __webpack_require__(2971);
6856
7136
  const cloudbase_manager_js_1 = __webpack_require__(3431);
7137
+ const MAX_INLINE_TEXT_BYTES = 256 * 1024;
7138
+ const STORAGE_READ_TEMP_PREFIX = 'cloudbase-mcp-storage-read-';
6857
7139
  // Input schema for queryStorage tool
6858
7140
  const queryStorageInputSchema = {
6859
- action: zod_1.z.enum(['list', 'info', 'url']).describe('查询操作类型:list=列出目录下的所有文件,info=获取指定文件的详细信息,url=获取文件的临时下载链接'),
7141
+ action: zod_1.z.enum(['list', 'info', 'url', 'read']).describe('查询操作类型:list=列出目录下的所有文件,info=获取指定文件的详细信息,url=获取文件的临时下载链接,read=读取文本文件内容'),
6860
7142
  cloudPath: zod_1.z.string().describe('云端文件路径,例如 files/data.txt 或 files/(目录)'),
6861
7143
  maxAge: zod_1.z.number().min(1).max(86400).optional().default(3600).describe('临时链接有效期,单位为秒,取值范围:1-86400,默认值:3600(1小时)')
6862
7144
  };
@@ -6868,6 +7150,63 @@ const manageStorageInputSchema = {
6868
7150
  force: zod_1.z.boolean().optional().default(false).describe('强制操作开关,删除操作时建议设置为true以确认删除,默认false'),
6869
7151
  isDirectory: zod_1.z.boolean().optional().default(false).describe('是否为目录操作,true=目录操作,false=文件操作,默认false')
6870
7152
  };
7153
+ function getStorageTempFileName(cloudPath) {
7154
+ const baseName = path.posix.basename(cloudPath);
7155
+ return baseName || 'storage-file';
7156
+ }
7157
+ function decodeInlineTextContent(buffer) {
7158
+ const inlineBuffer = buffer.subarray(0, MAX_INLINE_TEXT_BYTES);
7159
+ if (inlineBuffer.includes(0)) {
7160
+ throw new Error('queryStorage action=read 仅支持读取文本文件内容;二进制文件请改用 action=url 获取下载链接,或使用 manageStorage(action="download") 下载到本地。');
7161
+ }
7162
+ return {
7163
+ content: inlineBuffer.toString('utf8'),
7164
+ truncated: buffer.length > MAX_INLINE_TEXT_BYTES,
7165
+ sizeBytes: buffer.length,
7166
+ encoding: 'utf8',
7167
+ };
7168
+ }
7169
+ function getNonEmptyString(value) {
7170
+ if (typeof value !== 'string') {
7171
+ return null;
7172
+ }
7173
+ const trimmed = value.trim();
7174
+ return trimmed.length > 0 ? trimmed : null;
7175
+ }
7176
+ function buildStoragePublicUrl(cdnDomain, cloudPath) {
7177
+ if (!cdnDomain) {
7178
+ return null;
7179
+ }
7180
+ const normalizedCloudPath = cloudPath.replace(/^\/+/, '');
7181
+ if (!normalizedCloudPath) {
7182
+ return `https://${cdnDomain}`;
7183
+ }
7184
+ return `https://${cdnDomain}/${normalizedCloudPath}`;
7185
+ }
7186
+ async function resolveStoragePublicAccess(params) {
7187
+ const { cloudPath, cloudBaseOptions, manager } = params;
7188
+ try {
7189
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
7190
+ const describeEnvsResult = await manager.commonService('tcb', '2018-06-08').call({
7191
+ Action: 'DescribeEnvs',
7192
+ Param: {
7193
+ EnvId: envId,
7194
+ },
7195
+ });
7196
+ const storageList = describeEnvsResult?.EnvList?.[0]?.Storages ?? describeEnvsResult?.EnvInfo?.Storages ?? [];
7197
+ const storageCdnDomain = getNonEmptyString(storageList[0]?.CdnDomain);
7198
+ return {
7199
+ storageCdnDomain,
7200
+ publicUrl: buildStoragePublicUrl(storageCdnDomain, cloudPath),
7201
+ };
7202
+ }
7203
+ catch {
7204
+ return {
7205
+ storageCdnDomain: null,
7206
+ publicUrl: null,
7207
+ };
7208
+ }
7209
+ }
6871
7210
  function registerStorageTools(server) {
6872
7211
  // 获取 cloudBaseOptions,如果没有则为 undefined
6873
7212
  const cloudBaseOptions = server.cloudBaseOptions;
@@ -6876,7 +7215,7 @@ function registerStorageTools(server) {
6876
7215
  // Tool 1: queryStorage - 查询存储信息(只读操作)
6877
7216
  server.registerTool("queryStorage", {
6878
7217
  title: "查询存储信息",
6879
- description: "查询云存储信息,支持列出目录文件、获取文件信息、获取临时下载链接等只读操作。返回的文件信息包括文件名、大小、修改时间、下载链接等。",
7218
+ description: "查询云存储信息,支持列出目录文件、获取文件信息、获取临时下载链接等只读操作。返回的文件信息包括文件名、大小、修改时间、下载链接等。注意:action=url 返回的 temporaryUrl 是临时签名链接,有效期由 maxAge 参数决定(默认1小时),不要当作永久公网地址使用。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导出 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。",
6880
7219
  inputSchema: queryStorageInputSchema,
6881
7220
  annotations: {
6882
7221
  readOnlyHint: true,
@@ -6935,6 +7274,11 @@ function registerStorageTools(server) {
6935
7274
  cloudPath: input.cloudPath,
6936
7275
  maxAge: input.maxAge || 3600
6937
7276
  }]);
7277
+ const publicAccess = await resolveStoragePublicAccess({
7278
+ cloudPath: input.cloudPath,
7279
+ cloudBaseOptions,
7280
+ manager,
7281
+ });
6938
7282
  return {
6939
7283
  content: [
6940
7284
  {
@@ -6946,7 +7290,10 @@ function registerStorageTools(server) {
6946
7290
  cloudPath: input.cloudPath,
6947
7291
  temporaryUrl: result[0]?.url || "",
6948
7292
  expireTime: `${input.maxAge || 3600}秒`,
6949
- fileId: result[0]?.fileId || ""
7293
+ fileId: result[0]?.fileId || "",
7294
+ storageCdnDomain: publicAccess.storageCdnDomain,
7295
+ publicUrl: publicAccess.publicUrl,
7296
+ note: "temporaryUrl 是临时签名链接,会按 expireTime 过期。publicUrl 基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导,⚠️ 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先将目标路径设置为公有读。"
6950
7297
  },
6951
7298
  message: `Successfully generated temporary URL for '${input.cloudPath}'`
6952
7299
  }, null, 2)
@@ -6954,6 +7301,42 @@ function registerStorageTools(server) {
6954
7301
  ]
6955
7302
  };
6956
7303
  }
7304
+ case 'read': {
7305
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), STORAGE_READ_TEMP_PREFIX));
7306
+ const localPath = path.join(tempDir, getStorageTempFileName(input.cloudPath));
7307
+ try {
7308
+ await storageService.downloadFile({
7309
+ cloudPath: input.cloudPath,
7310
+ localPath
7311
+ });
7312
+ const buffer = await fs.readFile(localPath);
7313
+ const decoded = decodeInlineTextContent(buffer);
7314
+ return {
7315
+ content: [
7316
+ {
7317
+ type: "text",
7318
+ text: JSON.stringify({
7319
+ success: true,
7320
+ data: {
7321
+ action: 'read',
7322
+ cloudPath: input.cloudPath,
7323
+ content: decoded.content,
7324
+ encoding: decoded.encoding,
7325
+ sizeBytes: decoded.sizeBytes,
7326
+ truncated: decoded.truncated
7327
+ },
7328
+ message: decoded.truncated
7329
+ ? `Successfully read text content for '${input.cloudPath}' (truncated to ${MAX_INLINE_TEXT_BYTES} bytes)`
7330
+ : `Successfully read text content for '${input.cloudPath}'`
7331
+ }, null, 2)
7332
+ }
7333
+ ]
7334
+ };
7335
+ }
7336
+ finally {
7337
+ await fs.rm(tempDir, { recursive: true, force: true });
7338
+ }
7339
+ }
6957
7340
  default:
6958
7341
  throw new Error(`Unsupported action: ${input.action}`);
6959
7342
  }
@@ -6961,7 +7344,7 @@ function registerStorageTools(server) {
6961
7344
  // Tool 2: manageStorage - 管理存储文件(写操作)
6962
7345
  server.registerTool("manageStorage", {
6963
7346
  title: "管理存储文件",
6964
- description: "管理云存储文件,仅用于 COS/Storage 对象,不用于静态网站托管。支持上传文件/目录、下载文件/目录、删除文件/目录等操作。删除操作需要设置force=true进行确认,防止误删除重要文件。",
7347
+ description: "管理云存储文件,仅用于 COS/Storage 对象,不用于静态网站托管。支持上传文件/目录、下载文件/目录、删除文件/目录等操作。删除操作需要设置force=true进行确认,防止误删除重要文件。注意:上传后返回的 temporaryUrl 是临时签名链接,1小时后过期,不要当作永久公网地址写入配置或持久化存储。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。",
6965
7348
  inputSchema: manageStorageInputSchema,
6966
7349
  annotations: {
6967
7350
  readOnlyHint: false,
@@ -7001,6 +7384,11 @@ function registerStorageTools(server) {
7001
7384
  cloudPath: input.cloudPath,
7002
7385
  maxAge: 3600
7003
7386
  }]);
7387
+ const publicAccess = await resolveStoragePublicAccess({
7388
+ cloudPath: input.cloudPath,
7389
+ cloudBaseOptions,
7390
+ manager,
7391
+ });
7004
7392
  return {
7005
7393
  content: [
7006
7394
  {
@@ -7013,7 +7401,10 @@ function registerStorageTools(server) {
7013
7401
  cloudPath: input.cloudPath,
7014
7402
  isDirectory: input.isDirectory,
7015
7403
  temporaryUrl: fileUrls[0]?.url || "",
7016
- expireTime: "1小时"
7404
+ expireTime: "1小时",
7405
+ storageCdnDomain: publicAccess.storageCdnDomain,
7406
+ publicUrl: publicAccess.publicUrl,
7407
+ note: "temporaryUrl 是临时签名链接,1小时后过期,不要当作永久公网地址写入配置或持久化存储。publicUrl 基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导,⚠️ 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先将目标路径设置为公有读。"
7017
7408
  },
7018
7409
  message: `Successfully uploaded ${input.isDirectory ? 'directory' : 'file'} from '${input.localPath}' to '${input.cloudPath}'`
7019
7410
  }, null, 2)
@@ -8353,7 +8744,7 @@ function registerAppAuthTools(server) {
8353
8744
  };
8354
8745
  server.registerTool?.("queryAppAuth", {
8355
8746
  title: "查询应用认证配置",
8356
- description: "应用侧认证配置只读入口。用于查询登录方式、provider、publishable key、API key、client 配置和静态域名等认证准备状态。若业务要接受普通用户名样式标识符,先查询 action=getLoginConfig;若 usernamePassword=false,下一步应立即调用 manageAppAuth(action=patchLoginStrategy, patch={ usernamePassword: true }),不要直接写 email 登录 API。",
8747
+ description: "应用侧认证配置只读入口。用于查询登录方式、provider、publishable key、API key、client 配置和静态域名等认证准备状态。⚠️ 本工具为管理端配置查询工具,不执行用户登录。当任务要求编写客户端登录代码时(例如「用 JS SDK 登录」),应先通过本工具确认配置状态,再在项目代码中编写 @cloudbase/js-sdk 客户端登录代码(如 auth.signInWithPassword()),而非使用本工具完成登录。若业务要接受普通用户名样式标识符,先查询 action=getLoginConfig;若 usernamePassword=false,下一步应立即调用 manageAppAuth(action=patchLoginStrategy, patch={ usernamePassword: true }),不要直接写 email 登录 API。",
8357
8748
  inputSchema: {
8358
8749
  action: zod_1.z.enum(QUERY_APP_AUTH_ACTIONS),
8359
8750
  providerId: zod_1.z.string().optional().describe("provider 标识,如 email、google"),
@@ -8464,7 +8855,7 @@ function registerAppAuthTools(server) {
8464
8855
  }));
8465
8856
  server.registerTool?.("manageAppAuth", {
8466
8857
  title: "管理应用认证配置",
8467
- description: "应用侧认证配置写入口。用于修改登录方式、provider、client 配置,确保 publishable key,以及创建或删除 API key、自定义登录密钥。若前端要接受普通用户名样式标识符,应先执行 action=patchLoginStrategy 并传入 patch={ usernamePassword: true },再实现对应前端登录逻辑。",
8858
+ description: "应用侧认证配置写入口。用于修改登录方式、provider、client 配置,确保 publishable key,以及创建或删除 API key、自定义登录密钥。⚠️ 本工具为管理端配置工具,不执行用户登录。当任务要求编写客户端登录代码时(例如「用 JS SDK 登录」),应先通过本工具完成配置(如启用 usernamePassword、获取 publishable key),再在项目代码中编写 @cloudbase/js-sdk 客户端登录代码(如 auth.signInWithPassword()),而非使用本工具完成登录。若前端要接受普通用户名样式标识符,应先执行 action=patchLoginStrategy 并传入 patch={ usernamePassword: true },再实现对应前端登录逻辑。",
8468
8859
  inputSchema: {
8469
8860
  action: zod_1.z.enum(MANAGE_APP_AUTH_ACTIONS),
8470
8861
  patch: zod_1.z
@@ -8731,7 +9122,7 @@ class TelemetryReporter {
8731
9122
  const nodeVersion = process.version; // Node.js版本
8732
9123
  const arch = os_1.default.arch(); // 系统架构
8733
9124
  // 从构建时注入的版本号获取MCP版本信息
8734
- const mcpVersion = process.env.npm_package_version || "2.18.0" || 0;
9125
+ const mcpVersion = process.env.npm_package_version || "2.19.5" || 0;
8735
9126
  return {
8736
9127
  userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
8737
9128
  deviceId: this.deviceId,
@@ -9114,6 +9505,7 @@ exports.MANAGE_FUNCTION_ACTIONS = [
9114
9505
  "updateFunctionCode",
9115
9506
  "updateFunctionConfig",
9116
9507
  "invokeFunction",
9508
+ "deleteFunction",
9117
9509
  "createFunctionTrigger",
9118
9510
  "deleteFunctionTrigger",
9119
9511
  "createLayerVersion",
@@ -9257,14 +9649,32 @@ function buildFunctionOperationErrorMessage(operation, functionName, functionRoo
9257
9649
  if (/路径不存在/i.test(baseMessage) && expectedFunctionPath) {
9258
9650
  suggestions.push(`当前工具会从 \`functionRootPath + 函数名\` 查找代码目录,期望目录是 \`${expectedFunctionPath}\`。`);
9259
9651
  suggestions.push("如果你传入的已经是函数目录本身,请改为传它的父目录。");
9652
+ if (functionRootPath) {
9653
+ const lastDir = path_1.default.basename(path_1.default.normalize(functionRootPath));
9654
+ if (lastDir !== "cloudfunctions" && lastDir !== "functions") {
9655
+ suggestions.push(`functionRootPath 应该是直接包含函数文件夹的目录(如 cloudfunctions 或 functions),而不是项目根目录。` +
9656
+ `请将 functionRootPath 改为 \`${path_1.default.join(path_1.default.normalize(functionRootPath), "cloudfunctions")}\` ` +
9657
+ `或 \`${path_1.default.join(path_1.default.normalize(functionRootPath), "functions")}\`。`);
9658
+ }
9659
+ }
9260
9660
  }
9261
9661
  if (/paths\[0\].*undefined/i.test(baseMessage)) {
9262
- suggestions.push("HTTP 函数创建时需要提供 functionRootPath(指向 cloudfunctions 父目录)或 zipFile,否则 SDK 无法定位函数目录。");
9662
+ suggestions.push("HTTP 函数创建时需要提供 functionRootPath(指向 cloudfunctions 或 functions 目录的绝对路径,不是项目根目录)或 zipFile,否则 SDK 无法定位函数目录。");
9263
9663
  }
9264
9664
  if (/依赖安装失败|package\.json/i.test(baseMessage)) {
9265
9665
  suggestions.push("如果 HTTP 函数只使用原生 Node.js API 且没有第三方依赖,可以保留函数目录中的 index.js 和 scf_bootstrap,工具会跳过依赖安装。");
9266
9666
  suggestions.push("如果你确实依赖 npm 包,请在函数目录下补充 package.json 后重试。");
9267
9667
  }
9668
+ // Handle invalid parameter value errors from CloudBase API
9669
+ if (/invalid parameter value/i.test(baseMessage)) {
9670
+ suggestions.push("检测到参数值格式错误。请重点检查以下配置项:");
9671
+ suggestions.push("1. runtime: 请使用支持的运行时版本,如 Nodejs18.15、Nodejs16.13、Nodejs20.19 等(区分大小写,不要加空格)");
9672
+ suggestions.push("2. handler: Event 函数默认使用 index.main,HTTP 函数默认使用 app.handler 或 scf_bootstrap 启动");
9673
+ suggestions.push("3. functionName: 函数名称只能包含字母、数字、下划线、连字符,不能以数字开头");
9674
+ suggestions.push("4. timeout: 超时时间需为整数,单位为秒,范围 1-900");
9675
+ suggestions.push("5. envVariables: 环境变量键值对不能为空字符串");
9676
+ suggestions.push("6. type: 函数类型只能是 Event 或 HTTP(区分大小写)");
9677
+ }
9268
9678
  if (suggestions.length === 0) {
9269
9679
  suggestions.push("请检查函数名、目录结构和环境中的函数状态后重试。");
9270
9680
  }
@@ -9321,10 +9731,17 @@ function registerFunctionTools(server) {
9321
9731
  throw new Error("createLayerVersion 在 cloud mode 下不支持 contentPath,本地文件内容请改为 base64Content 或改用本地模式执行。");
9322
9732
  }
9323
9733
  };
9734
+ const TIME_FORMAT_REGEX = /^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/;
9324
9735
  const validateLogRange = (startTime, endTime, offset, limit) => {
9325
9736
  if ((offset || 0) + (limit || 0) > 10000) {
9326
9737
  throw new Error("offset+limit 不能大于 10000");
9327
9738
  }
9739
+ if (startTime && !TIME_FORMAT_REGEX.test(startTime)) {
9740
+ throw new Error(`startTime 格式错误: "${startTime}"。必须使用 YYYY-MM-DD HH:mm:ss 格式(如 2024-01-01 00:00:00)`);
9741
+ }
9742
+ if (endTime && !TIME_FORMAT_REGEX.test(endTime)) {
9743
+ throw new Error(`endTime 格式错误: "${endTime}"。必须使用 YYYY-MM-DD HH:mm:ss 格式(如 2024-01-01 23:59:59)`);
9744
+ }
9328
9745
  if (startTime && endTime) {
9329
9746
  const start = new Date(startTime).getTime();
9330
9747
  const end = new Date(endTime).getTime();
@@ -9404,15 +9821,29 @@ function registerFunctionTools(server) {
9404
9821
  }
9405
9822
  validateLogRange(input.startTime, input.endTime, input.offset, input.limit);
9406
9823
  const cloudbase = await getManager();
9407
- const result = await cloudbase.functions.getFunctionLogsV2({
9408
- name: input.functionName,
9409
- offset: input.offset,
9410
- limit: input.limit,
9411
- startTime: input.startTime,
9412
- endTime: input.endTime,
9413
- requestId: input.requestId,
9414
- qualifier: input.qualifier,
9415
- });
9824
+ let result;
9825
+ try {
9826
+ result = await cloudbase.functions.getFunctionLogsV2({
9827
+ name: input.functionName,
9828
+ offset: input.offset,
9829
+ limit: input.limit,
9830
+ startTime: input.startTime,
9831
+ endTime: input.endTime,
9832
+ requestId: input.requestId,
9833
+ qualifier: input.qualifier,
9834
+ });
9835
+ }
9836
+ catch (error) {
9837
+ const errMsg = error instanceof Error ? error.message : String(error);
9838
+ if (/invalid parameter/i.test(errMsg)) {
9839
+ throw new Error(`${errMsg}\n\n常见原因:\n` +
9840
+ `1. startTime/endTime 格式错误,必须为 YYYY-MM-DD HH:mm:ss(如 2024-01-01 00:00:00),不支持 ISO 8601 或时间戳\n` +
9841
+ `2. startTime 和 endTime 间隔超过一天\n` +
9842
+ `3. functionName 不存在或格式不正确\n` +
9843
+ `建议:不传 startTime/endTime 时默认查询最近一天的日志。`);
9844
+ }
9845
+ throw error;
9846
+ }
9416
9847
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
9417
9848
  return buildEnvelope({
9418
9849
  action: input.action,
@@ -9434,11 +9865,24 @@ function registerFunctionTools(server) {
9434
9865
  }
9435
9866
  validateLogRange(input.startTime, input.endTime);
9436
9867
  const cloudbase = await getManager();
9437
- const result = await cloudbase.functions.getFunctionLogDetail({
9438
- startTime: input.startTime,
9439
- endTime: input.endTime,
9440
- logRequestId: input.requestId,
9441
- });
9868
+ let result;
9869
+ try {
9870
+ result = await cloudbase.functions.getFunctionLogDetail({
9871
+ startTime: input.startTime,
9872
+ endTime: input.endTime,
9873
+ logRequestId: input.requestId,
9874
+ });
9875
+ }
9876
+ catch (error) {
9877
+ const errMsg = error instanceof Error ? error.message : String(error);
9878
+ if (/invalid parameter/i.test(errMsg)) {
9879
+ throw new Error(`${errMsg}\n\n常见原因:\n` +
9880
+ `1. startTime/endTime 格式错误,必须为 YYYY-MM-DD HH:mm:ss(如 2024-01-01 00:00:00),不支持 ISO 8601 或时间戳\n` +
9881
+ `2. startTime 和 endTime 间隔超过一天\n` +
9882
+ `建议:不传 startTime/endTime 时默认查询最近一天的日志。`);
9883
+ }
9884
+ throw error;
9885
+ }
9442
9886
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
9443
9887
  return buildEnvelope({
9444
9888
  action: input.action,
@@ -9634,7 +10078,7 @@ function registerFunctionTools(server) {
9634
10078
  const functionType = typeof func.type === "string" ? func.type : undefined;
9635
10079
  const expectedFunctionPath = getExpectedFunctionPath(processedRootPath, functionName);
9636
10080
  if (functionType === "HTTP" && !processedRootPath && !input.zipFile) {
9637
- throw new Error("createFunction 创建 HTTP 函数时,需要提供 functionRootPath(指向 cloudfunctions 父目录)或 zipFile。");
10081
+ throw new Error("createFunction 创建 HTTP 函数时,需要提供 functionRootPath(指向 cloudfunctions 或 functions 目录的绝对路径,不是项目根目录)或 zipFile。");
9638
10082
  }
9639
10083
  const hasPackageJson = expectedFunctionPath !== undefined
9640
10084
  ? (0, fs_1.existsSync)(path_1.default.join(expectedFunctionPath, "package.json"))
@@ -9673,7 +10117,7 @@ function registerFunctionTools(server) {
9673
10117
  nextActions.push({
9674
10118
  tool: "manageGateway",
9675
10119
  action: "createAccess",
9676
- reason: "如果需要通过 URL 访问 HTTP 函数,请按实际路径和鉴权需求显式创建访问入口,不要默认假设 /函数名 已存在",
10120
+ reason: "如果需要通过 URL 访问 HTTP 函数,请调用 manageGateway(action=\"createAccess\") 并显式传 type=\"HTTP\",再按实际路径和鉴权需求创建访问入口,不要默认假设 /函数名 已存在",
9677
10121
  });
9678
10122
  nextActions.push({
9679
10123
  tool: "queryGateway",
@@ -9692,7 +10136,7 @@ function registerFunctionTools(server) {
9692
10136
  });
9693
10137
  }
9694
10138
  const message = func.type === "HTTP"
9695
- ? `已创建 HTTP 函数 ${functionName}。如果后续需要通过 URL 访问,请显式调用 manageGateway(action="createAccess") 按实际路径和鉴权需求创建访问入口。评测或其他外部调用方可能会以匿名身份访问,而且失败后不一定会把 EXCEED_AUTHORITY 再反馈给 AI;交付前请主动确认访问路径和函数安全规则,若已出现 EXCEED_AUTHORITY,请先调用 queryPermissions(action="getResourcePermission", resourceType="function", resourceId="${functionName}") 查看当前规则,再按需要使用 managePermissions(action="updateResourcePermission") 调整权限。`
10139
+ ? `已创建 HTTP 函数 ${functionName}。如果后续需要通过 URL 访问,请显式调用 manageGateway(action="createAccess"),并把 type="HTTP" 一起传入,再按实际路径和鉴权需求创建访问入口。评测或其他外部调用方可能会以匿名身份访问,而且失败后不一定会把 EXCEED_AUTHORITY 再反馈给 AI;交付前请主动确认访问路径和函数安全规则,若已出现 EXCEED_AUTHORITY,请先调用 queryPermissions(action="getResourcePermission", resourceType="function", resourceId="${functionName}") 查看当前规则,再按需要使用 managePermissions(action="updateResourcePermission") 调整权限。`
9696
10140
  : `已创建函数 ${functionName}`;
9697
10141
  return buildEnvelope({
9698
10142
  action: input.action,
@@ -9807,7 +10251,27 @@ function registerFunctionTools(server) {
9807
10251
  throw error;
9808
10252
  }
9809
10253
  }
9810
- case "createFunctionTrigger": {
10254
+ case "deleteFunction": {
10255
+ if (!input.functionName) {
10256
+ throw new Error("deleteFunction 操作时,functionName 参数是必需的");
10257
+ }
10258
+ requireConfirm(input.action, input.confirm);
10259
+ const cloudbase = await getManager();
10260
+ const result = await cloudbase.functions.deleteFunction(input.functionName);
10261
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
10262
+ return buildEnvelope({
10263
+ action: input.action,
10264
+ functionName: input.functionName,
10265
+ raw: result,
10266
+ }, `已删除函数 ${input.functionName}`, [
10267
+ {
10268
+ tool: "queryFunctions",
10269
+ action: "listFunctions",
10270
+ reason: "确认函数已被删除",
10271
+ },
10272
+ ]);
10273
+ }
10274
+ case "createFunctionTrigger": {
9811
10275
  if (!input.functionName) {
9812
10276
  throw new Error("createFunctionTrigger 操作时,functionName 参数是必需的");
9813
10277
  }
@@ -10019,23 +10483,58 @@ function registerFunctionTools(server) {
10019
10483
  };
10020
10484
  server.registerTool?.("queryFunctions", {
10021
10485
  title: "查询云函数域资源",
10022
- description: "函数域统一只读入口。通过更自解释的 action 查询函数列表、函数详情、日志、层、触发器和代码下载地址。",
10486
+ description: "函数域统一只读入口。通过更自解释的 action 查询 CloudBase 云函数列表、函数详情、执行日志、层、触发器和代码下载地址。" +
10487
+ "\n\n**分页说明**:`listFunctions`、`listLayers` 支持 `limit` 和 `offset` 参数。" +
10488
+ "\n- `limit`: 分页数量,默认值由后端决定" +
10489
+ "\n- `offset`: 分页偏移,从 0 开始" +
10490
+ "\n- 示例:`queryFunctions(action=\"listFunctions\", offset=10, limit=10)`" +
10491
+ "\n\n**查询 CloudBase 云函数日志**:使用 `action=\"listFunctionLogs\"`,需要提供 `functionName` 参数。" +
10492
+ "\n- 示例:`queryFunctions(action=\"listFunctionLogs\", functionName=\"my-function\")`" +
10493
+ "\n- 如需查看日志详情:`queryFunctions(action=\"getFunctionLogDetail\", requestId=\"xxx\")`" +
10494
+ "\n\n**定时任务 / cron / 定时跑**:使用 `listFunctionTriggers` 查询函数的 timer 触发器配置。" +
10495
+ "\n\n**区分 `queryLogs` 工具**:" +
10496
+ "\n- 本工具用于查询特定 CloudBase 云函数的执行日志" +
10497
+ "\n- `queryLogs` 工具用于搜索 CLS 日志服务(跨服务日志聚合)",
10023
10498
  inputSchema: {
10024
10499
  action: zod_1.z
10025
10500
  .enum(exports.QUERY_FUNCTION_ACTIONS)
10026
- .describe("只读操作类型,例如 listFunctions、getFunctionDetail、listFunctionLogs"),
10027
- functionName: zod_1.z.string().optional().describe("函数名称。函数相关 action 必填"),
10028
- limit: zod_1.z.number().optional().describe("分页数量。列表类 action 可选"),
10029
- offset: zod_1.z.number().optional().describe("分页偏移。列表类 action 可选"),
10030
- codeSecret: zod_1.z.string().optional().describe("代码保护密钥"),
10031
- startTime: zod_1.z.string().optional().describe("日志查询开始时间"),
10032
- endTime: zod_1.z.string().optional().describe("日志查询结束时间"),
10033
- requestId: zod_1.z.string().optional().describe("日志 requestId。获取日志详情时必填"),
10034
- qualifier: zod_1.z.string().optional().describe("函数版本,日志查询时可选"),
10035
- runtime: zod_1.z.string().optional().describe("层查询的运行时筛选"),
10501
+ .describe("只读操作类型:" +
10502
+ "\n- `listFunctions`: 列出所有 CloudBase 云函数" +
10503
+ "\n- `getFunctionDetail`: 获取 CloudBase 云函数详情(需要 functionName)" +
10504
+ "\n- `listFunctionLogs`: 查询 CloudBase 云函数执行日志(需要 functionName)" +
10505
+ "\n- `getFunctionLogDetail`: 获取日志详情(需要 requestId)" +
10506
+ "\n- `listFunctionLayers`: 列出函数绑定的层" +
10507
+ "\n- `listLayers`: 列出所有层" +
10508
+ "\n- `listLayerVersions`: 列出层的版本(注意:是 Versions 不是 Version)" +
10509
+ "\n- `getLayerVersionDetail`: 获取层版本详情" +
10510
+ "\n- `listFunctionTriggers`: 列出函数触发器(用于查看定时任务 / cron / timer 配置)" +
10511
+ "\n- `getFunctionDownloadUrl`: 获取函数代码下载地址"),
10512
+ functionName: zod_1.z
10513
+ .string()
10514
+ .optional()
10515
+ .describe("CloudBase 云函数名称。`getFunctionDetail`、`listFunctionLogs`、`listFunctionLayers`、`listFunctionTriggers`、`getFunctionDownloadUrl` 时必填"),
10516
+ limit: zod_1.z.number().optional().describe("分页数量(limit)。列表类 action 可选,默认值由后端决定"),
10517
+ offset: zod_1.z.number().optional().describe("分页偏移(offset)。列表类 action 可选,默认 0"),
10518
+ codeSecret: zod_1.z.string().optional().describe("代码保护密钥,用于解密函数代码"),
10519
+ startTime: zod_1.z
10520
+ .string()
10521
+ .optional()
10522
+ .describe("日志查询开始时间,格式必须为 YYYY-MM-DD HH:mm:ss(如 2024-01-01 00:00:00)。" +
10523
+ "与 endTime 间隔不能超过一天。不传时默认查询最近一天"),
10524
+ endTime: zod_1.z
10525
+ .string()
10526
+ .optional()
10527
+ .describe("日志查询结束时间,格式必须为 YYYY-MM-DD HH:mm:ss(如 2024-01-01 23:59:59)。" +
10528
+ "与 startTime 间隔不能超过一天。不传时默认为当前时间"),
10529
+ requestId: zod_1.z
10530
+ .string()
10531
+ .optional()
10532
+ .describe("日志请求 ID。`getFunctionLogDetail` 操作必填,可从 `listFunctionLogs` 结果中获取"),
10533
+ qualifier: zod_1.z.string().optional().describe("函数版本别名,如 $LATEST、$DEFAULT。日志查询时可选"),
10534
+ runtime: zod_1.z.string().optional().describe("层查询的运行时筛选,如 Nodejs18.15"),
10036
10535
  searchKey: zod_1.z.string().optional().describe("层名称搜索关键字"),
10037
- layerName: zod_1.z.string().optional().describe("层名称。层相关 action 必填"),
10038
- layerVersion: zod_1.z.number().optional().describe("层版本号。获取层版本详情时必填"),
10536
+ layerName: zod_1.z.string().optional().describe("层名称。`listLayerVersions`、`getLayerVersionDetail` 操作必填"),
10537
+ layerVersion: zod_1.z.number().optional().describe("层版本号。`getLayerVersionDetail` 操作必填"),
10039
10538
  },
10040
10539
  annotations: {
10041
10540
  readOnlyHint: true,
@@ -10045,15 +10544,20 @@ function registerFunctionTools(server) {
10045
10544
  }, async (input) => withEnvelope(() => handleQueryFunctions(input)));
10046
10545
  server.registerTool?.("manageFunctions", {
10047
10546
  title: "管理云函数域资源",
10048
- description: "函数域统一写入口。通过 action 管理函数创建、代码更新、配置更新、调用函数、触发器和层绑定。危险操作需要显式 confirm=true。",
10547
+ description: "函数域统一写入口。支持创建函数、更新代码、更新配置、调用函数、管理定时跑 / 定时任务 / scheduled job 的 timer 触发器和层绑定。" +
10548
+ "如果要创建 cron 定时任务,先用 createFunction 创建函数,再用 createFunctionTrigger 创建 timer 触发器(支持7段cron表达式),deleteFunctionTrigger 删除触发器。" +
10549
+ "危险操作需要显式 confirm=true。",
10049
10550
  inputSchema: {
10050
10551
  action: zod_1.z
10051
10552
  .enum(exports.MANAGE_FUNCTION_ACTIONS)
10052
- .describe("写操作类型,例如 createFunction、invokeFunction、attachLayer"),
10553
+ .describe("写操作类型,例如 createFunction、updateFunctionCode、invokeFunction、deleteFunction、" +
10554
+ "createFunctionTrigger(定时任务 / cron / timer)、deleteFunctionTrigger、attachLayer、detachLayer"),
10053
10555
  func: CREATE_FUNCTION_SCHEMA.optional().describe("createFunction 操作的函数配置"),
10054
- functionRootPath: zod_1.z.string().optional().describe("创建或更新函数代码时默认推荐的本地目录方式。函数根目录(父目录绝对路径)。" +
10055
- "本地应按 cloudfunctions/<functionName>/index.js 布局," +
10056
- "此参数传 cloudfunctions 目录的绝对路径(如 /abs/path/cloudfunctions),不要传到函数名子目录。" +
10556
+ functionRootPath: zod_1.z.string().optional().describe("创建或更新函数代码时默认推荐的本地目录方式。" +
10557
+ "必须是直接包含函数文件夹的目录绝对路径(如 /abs/path/cloudfunctions 或 /abs/path/functions)," +
10558
+ "不要传项目根目录(如 /abs/path),也不要传到函数名子目录(如 /abs/path/cloudfunctions/hello)。" +
10559
+ "本地应按 cloudfunctions/<functionName>/index.js 或 functions/<functionName>/index.js 布局," +
10560
+ "此参数传 cloudfunctions 或 functions 目录的绝对路径。" +
10057
10561
  "SDK 会自动拼接函数名子目录,无需预先压缩 zip 或 base64 编码。"),
10058
10562
  force: zod_1.z.boolean().optional().describe("createFunction 时是否覆盖"),
10059
10563
  functionName: zod_1.z.string().optional().describe("函数名称。大多数 action 使用该字段作为统一目标"),
@@ -10063,7 +10567,11 @@ function registerFunctionTools(server) {
10063
10567
  envVariables: zod_1.z.record(zod_1.z.string()).optional().describe("配置更新时要合并的环境变量"),
10064
10568
  vpc: VPC_SCHEMA.optional().describe("配置更新时的 VPC 信息"),
10065
10569
  params: zod_1.z.record(zod_1.z.any()).optional().describe("invokeFunction 的调用参数"),
10066
- triggers: zod_1.z.array(TRIGGER_SCHEMA).optional().describe("createFunctionTrigger 的触发器列表"),
10570
+ triggers: zod_1.z
10571
+ .array(TRIGGER_SCHEMA)
10572
+ .optional()
10573
+ .describe("createFunctionTrigger 的触发器列表,用于定时跑 / 定时任务 / scheduled job。timer 触发器使用7段 cron 表达式(秒 分 时 日 月 星期 年)," +
10574
+ '如 "0 */5 * * * * *" 表示每5分钟执行一次'),
10067
10575
  triggerName: zod_1.z.string().optional().describe("deleteFunctionTrigger 的目标触发器名称"),
10068
10576
  layerName: zod_1.z.string().optional().describe("层名称"),
10069
10577
  layerVersion: zod_1.z.number().optional().describe("层版本号"),
@@ -10077,7 +10585,7 @@ function registerFunctionTools(server) {
10077
10585
  .optional()
10078
10586
  .describe("updateFunctionLayers 的目标层列表,顺序即最终顺序"),
10079
10587
  codeSecret: zod_1.z.string().optional().describe("层绑定时的代码保护密钥"),
10080
- confirm: zod_1.z.boolean().optional().describe("危险操作确认开关"),
10588
+ confirm: zod_1.z.boolean().optional().describe("危险操作确认开关。deleteFunction、deleteFunctionTrigger、deleteLayerVersion、detachLayer 等删除类操作需要显式传入 confirm=true"),
10081
10589
  },
10082
10590
  annotations: {
10083
10591
  readOnlyHint: false,
@@ -10303,6 +10811,23 @@ function buildPermissionHints(securityRule, resourceId) {
10303
10811
  buildTemplateLiteralRuleHint(securityRule, resourceId),
10304
10812
  ].filter(Boolean);
10305
10813
  }
10814
+ async function ensureStorageBucketsExist(cloudbase, resourceIds) {
10815
+ if (!resourceIds.length) {
10816
+ return;
10817
+ }
10818
+ const envInfo = await cloudbase.env.getEnvInfo();
10819
+ const existingBuckets = new Set((envInfo?.EnvInfo?.Storages ?? [])
10820
+ .map((item) => item?.Bucket)
10821
+ .filter((bucket) => Boolean(bucket)));
10822
+ const missingBuckets = resourceIds.filter((resourceId) => !existingBuckets.has(resourceId));
10823
+ if (missingBuckets.length === 0) {
10824
+ return;
10825
+ }
10826
+ if (missingBuckets.length === 1) {
10827
+ throw new Error(`存储 Bucket ${missingBuckets[0]} 不存在`);
10828
+ }
10829
+ throw new Error(`以下存储 Bucket 不存在: ${missingBuckets.join(", ")}`);
10830
+ }
10306
10831
  function registerPermissionTools(server) {
10307
10832
  const cloudBaseOptions = server.cloudBaseOptions;
10308
10833
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
@@ -10345,20 +10870,24 @@ function registerPermissionTools(server) {
10345
10870
  if (!resourceType || !resourceId) {
10346
10871
  throw new Error("action=getResourcePermission 时必须提供 resourceType 和 resourceId");
10347
10872
  }
10873
+ if (resourceType === "storage") {
10874
+ await ensureStorageBucketsExist(cloudbase, [resourceId]);
10875
+ }
10348
10876
  const result = await cloudbase.permission.describeResourcePermission({
10349
10877
  resourceType: mapResourceType(resourceType),
10350
10878
  resources: [resourceId],
10351
10879
  });
10352
10880
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
10353
10881
  const permissions = result.Data.PermissionList ?? [];
10354
- const securityRule = permissions.find((item) => item.Resource === resourceId)?.SecurityRule ??
10355
- permissions[0]?.SecurityRule;
10882
+ const matchedPermission = permissions.find((item) => item.Resource === resourceId) ?? permissions[0];
10883
+ const securityRule = matchedPermission?.SecurityRule;
10356
10884
  const hints = buildPermissionHints(securityRule, resourceId);
10357
10885
  return buildEnvelope({
10358
10886
  action,
10359
10887
  envId,
10360
10888
  resourceType,
10361
10889
  resourceId,
10890
+ aclTag: matchedPermission?.Permission,
10362
10891
  permissions,
10363
10892
  hints,
10364
10893
  raw: result,
@@ -10368,6 +10897,9 @@ function registerPermissionTools(server) {
10368
10897
  if (!resourceType) {
10369
10898
  throw new Error("action=listResourcePermissions 时必须提供 resourceType");
10370
10899
  }
10900
+ if (resourceType === "storage" && resourceIds?.length) {
10901
+ await ensureStorageBucketsExist(cloudbase, resourceIds);
10902
+ }
10371
10903
  const result = await cloudbase.permission.describeResourcePermission({
10372
10904
  resourceType: mapResourceType(resourceType),
10373
10905
  resources: resourceIds,
@@ -10720,7 +11252,9 @@ const http = __importStar(__webpack_require__(3782));
10720
11252
  const https = __importStar(__webpack_require__(3871));
10721
11253
  const os = __importStar(__webpack_require__(8116));
10722
11254
  const path = __importStar(__webpack_require__(2521));
11255
+ const url_1 = __webpack_require__(6887);
10723
11256
  const zod_1 = __webpack_require__(2971);
11257
+ const remote_url_safety_js_1 = __webpack_require__(9192);
10724
11258
  // CloudBase 模板配置
10725
11259
  const TEMPLATES = {
10726
11260
  react: {
@@ -10913,13 +11447,18 @@ function resolveDownloadTemplateIDE(ide, integrationIDE) {
10913
11447
  supportedIDEs: exports.IDE_TYPES.filter((t) => t !== "all"),
10914
11448
  };
10915
11449
  }
11450
+ const MAX_REDIRECT_HOPS = 5;
10916
11451
  // 根据 INTEGRATION_IDE 环境变量获取默认 IDE 类型
10917
11452
  // 下载文件到临时目录
10918
- async function downloadFile(url, filePath) {
11453
+ async function downloadFile(url, filePath, redirectCount = 0) {
11454
+ if (redirectCount > MAX_REDIRECT_HOPS) {
11455
+ throw new Error(`重定向次数超过 ${MAX_REDIRECT_HOPS} 次限制`);
11456
+ }
11457
+ const { requestUrl, requestOptions } = await (0, remote_url_safety_js_1.prepareSafeRemoteRequest)(url);
11458
+ const client = requestUrl.protocol === "https:" ? https : http;
10919
11459
  return new Promise((resolve, reject) => {
10920
- const client = url.startsWith("https:") ? https : http;
10921
11460
  client
10922
- .get(url, (res) => {
11461
+ .get(requestUrl, requestOptions ?? {}, (res) => {
10923
11462
  if (res.statusCode === 200) {
10924
11463
  const file = fs.createWriteStream(filePath);
10925
11464
  res.pipe(file);
@@ -10929,10 +11468,11 @@ async function downloadFile(url, filePath) {
10929
11468
  });
10930
11469
  file.on("error", reject);
10931
11470
  }
10932
- else if (res.statusCode === 302 || res.statusCode === 301) {
11471
+ else if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400) {
10933
11472
  // 处理重定向
10934
11473
  if (res.headers.location) {
10935
- downloadFile(res.headers.location, filePath)
11474
+ const redirectUrl = new url_1.URL(res.headers.location, requestUrl).toString();
11475
+ downloadFile(redirectUrl, filePath, redirectCount + 1)
10936
11476
  .then(resolve)
10937
11477
  .catch(reject);
10938
11478
  }
@@ -11128,9 +11668,8 @@ function registerSetupTools(server) {
11128
11668
  // downloadTemplate - 下载项目模板 (cloud-incompatible)
11129
11669
  server.registerTool("downloadTemplate", {
11130
11670
  title: "下载项目模板",
11131
- description: `自动下载并部署CloudBase项目模板。⚠️ **MANDATORY FOR NEW PROJECTS** ⚠️
11132
-
11133
- **CRITICAL**: This tool MUST be called FIRST when starting a new project.\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.18.0" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
11671
+ description: `自动下载并部署CloudBase项目模板。
11672
+ **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.19.5" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
11134
11673
  inputSchema: {
11135
11674
  template: zod_1.z
11136
11675
  .enum(["react", "vue", "miniprogram", "uniapp", "rules"])
@@ -11327,6 +11866,7 @@ const cloudbase_manager_js_1 = __webpack_require__(3431);
11327
11866
  const json_content_js_1 = __webpack_require__(4365);
11328
11867
  const QUERY_APP_ACTIONS = ["listApps", "getApp", "listAppVersions", "getAppVersion"];
11329
11868
  const MANAGE_APP_ACTIONS = ["deployApp", "deleteApp", "deleteAppVersion"];
11869
+ const APP_FRAMEWORKS = ["vue", "react", "next", "nuxt", "vite", "angular", "static"];
11330
11870
  function buildEnvelope(data, message) {
11331
11871
  return {
11332
11872
  success: true,
@@ -11349,15 +11889,24 @@ function registerAppTools(server) {
11349
11889
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
11350
11890
  server.registerTool?.("queryApps", {
11351
11891
  title: "查询 CloudApp",
11352
- description: "CloudApp 域统一只读入口。支持应用、版本列表与版本详情查询。",
11892
+ description: "CloudApp 域统一只读入口。可先查应用/版本,再在重新部署后用 listAppVersions 或 getAppVersion 按 serviceName 验证是否生成了新版本与最新构建状态。",
11353
11893
  inputSchema: {
11354
11894
  action: zod_1.z.enum(QUERY_APP_ACTIONS),
11355
- serviceName: zod_1.z.string().optional(),
11356
- searchKey: zod_1.z.string().optional(),
11357
- pageNo: zod_1.z.number().optional(),
11358
- pageSize: zod_1.z.number().optional(),
11359
- versionName: zod_1.z.string().optional(),
11360
- buildId: zod_1.z.string().optional(),
11895
+ serviceName: zod_1.z
11896
+ .string()
11897
+ .optional()
11898
+ .describe("CloudApp 服务名。getApp / listAppVersions / getAppVersion 时必填;重新部署后复用同一个 serviceName 查询版本历史。"),
11899
+ searchKey: zod_1.z.string().optional().describe("按应用服务名模糊搜索关键词,仅 action=listApps 时使用。"),
11900
+ pageNo: zod_1.z.number().optional().describe("分页页码,从 1 开始。"),
11901
+ pageSize: zod_1.z.number().optional().describe("分页大小。"),
11902
+ versionName: zod_1.z
11903
+ .string()
11904
+ .optional()
11905
+ .describe("版本名称。getAppVersion 时可与 buildId 二选一;已知版本号时优先传该值。"),
11906
+ buildId: zod_1.z
11907
+ .string()
11908
+ .optional()
11909
+ .describe("构建 ID。getAppVersion 时可与 versionName 二选一;部署返回 BuildId 后可直接用它轮询状态。"),
11361
11910
  },
11362
11911
  annotations: {
11363
11912
  readOnlyHint: true,
@@ -11436,20 +11985,49 @@ function registerAppTools(server) {
11436
11985
  });
11437
11986
  server.registerTool?.("manageApps", {
11438
11987
  title: "管理 CloudApp",
11439
- description: "CloudApp 域统一写入口。首轮仅支持静态部署、删除应用和删除版本。",
11988
+ description: "CloudApp 域统一写入口。action=deployApp 会先 uploadCode 再 createApp;首次调用创建应用,后续复用同一个 serviceName 会直接触发重新部署并生成新版本,无需先删除旧应用。",
11440
11989
  inputSchema: {
11441
11990
  action: zod_1.z.enum(MANAGE_APP_ACTIONS),
11442
- serviceName: zod_1.z.string(),
11443
- localPath: zod_1.z.string().optional(),
11444
- appPath: zod_1.z.string().optional(),
11445
- buildPath: zod_1.z.string().optional(),
11446
- framework: zod_1.z.string().optional(),
11447
- nodeJsVersion: zod_1.z.string().optional(),
11448
- installCmd: zod_1.z.string().optional(),
11449
- buildCmd: zod_1.z.string().optional(),
11450
- deployCmd: zod_1.z.string().optional(),
11451
- ignore: zod_1.z.array(zod_1.z.string()).optional(),
11452
- versionName: zod_1.z.string().optional(),
11991
+ serviceName: zod_1.z
11992
+ .string()
11993
+ .describe("CloudApp 服务名。deployApp 时复用现有 serviceName 会新增一个部署版本并触发重新部署,而不是删除重建。"),
11994
+ filePath: zod_1.z
11995
+ .string()
11996
+ .optional()
11997
+ .describe("要上传并部署的本地项目根目录或 zip 文件绝对路径。deployApp 时必填;通常传源码所在目录而不是 build 产物目录,构建产物目录请用 buildPath 指定。"),
11998
+ appPath: zod_1.z
11999
+ .string()
12000
+ .optional()
12001
+ .describe("应用线上访问路径(hosting mount path),例如 /my-web-app。不是本地目录路径;省略时默认为 /serviceName。"),
12002
+ buildPath: zod_1.z
12003
+ .string()
12004
+ .optional()
12005
+ .describe("构建产物目录,相对于 filePath,例如 dist 或 build。纯静态 HTML 如果入口文件直接在项目根目录,可省略。"),
12006
+ framework: zod_1.z
12007
+ .enum(APP_FRAMEWORKS)
12008
+ .optional()
12009
+ .describe("前端框架类型。可选值:vue、react、next、nuxt、vite、angular、static;纯 HTML/静态站点请传 static。"),
12010
+ nodeJsVersion: zod_1.z
12011
+ .string()
12012
+ .optional()
12013
+ .describe("构建时使用的 Node.js 版本;不传时由 CloudApp 使用默认值。"),
12014
+ installCmd: zod_1.z
12015
+ .string()
12016
+ .optional()
12017
+ .describe("依赖安装命令,例如 npm install;静态资源无需安装依赖时可省略。"),
12018
+ buildCmd: zod_1.z
12019
+ .string()
12020
+ .optional()
12021
+ .describe("构建命令,例如 npm run build;纯静态 HTML 无构建步骤时可省略。"),
12022
+ deployCmd: zod_1.z
12023
+ .string()
12024
+ .optional()
12025
+ .describe("自定义部署命令。通常无需填写,只有在默认部署步骤不满足要求时才传。"),
12026
+ ignore: zod_1.z.array(zod_1.z.string()).optional().describe("上传时忽略的文件/目录 glob 模式,例如 **/node_modules/**。"),
12027
+ versionName: zod_1.z
12028
+ .string()
12029
+ .optional()
12030
+ .describe("要删除的历史版本名,仅 action=deleteAppVersion 时必填。"),
11453
12031
  },
11454
12032
  annotations: {
11455
12033
  readOnlyHint: false,
@@ -11458,7 +12036,7 @@ function registerAppTools(server) {
11458
12036
  openWorldHint: true,
11459
12037
  category: "apps",
11460
12038
  },
11461
- }, async ({ action, serviceName, localPath, appPath, buildPath, framework, nodeJsVersion, installCmd, buildCmd, deployCmd, ignore, versionName, }) => {
12039
+ }, async ({ action, serviceName, filePath, appPath, buildPath, framework, nodeJsVersion, installCmd, buildCmd, deployCmd, ignore, versionName, }) => {
11462
12040
  try {
11463
12041
  const cloudbase = await getManager();
11464
12042
  const appService = getCloudAppService(cloudbase);
@@ -11466,13 +12044,13 @@ function registerAppTools(server) {
11466
12044
  throw new Error("当前 manager 未提供 cloudAppService");
11467
12045
  }
11468
12046
  if (action === "deployApp") {
11469
- if (!localPath) {
11470
- throw new Error("action=deployApp 时必须提供 localPath");
12047
+ if (!filePath) {
12048
+ throw new Error("action=deployApp 时必须提供 filePath");
11471
12049
  }
11472
12050
  const uploadResult = await appService.uploadCode({
11473
12051
  deployType: "static-hosting",
11474
12052
  serviceName,
11475
- localPath,
12053
+ localPath: filePath,
11476
12054
  ignore,
11477
12055
  });
11478
12056
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, uploadResult);
@@ -12031,7 +12609,65 @@ const fs_1 = __importDefault(__webpack_require__(4421));
12031
12609
  const path_1 = __importDefault(__webpack_require__(2521));
12032
12610
  const zod_1 = __webpack_require__(2971);
12033
12611
  const cloudbase_manager_js_1 = __webpack_require__(3431);
12612
+ const cloud_mode_js_1 = __webpack_require__(9684);
12034
12613
  const notification_js_1 = __webpack_require__(2720);
12614
+ const tool_result_js_1 = __webpack_require__(9835);
12615
+ const routingRuleSchema = zod_1.z.object({
12616
+ keyPrefixEquals: zod_1.z.string().optional().describe('匹配前缀规则,例如 app/ 或 assets/。与 httpErrorCodeReturnedEquals 二选一或按 CloudBase 规则组合使用。'),
12617
+ httpErrorCodeReturnedEquals: zod_1.z.string().optional().describe('匹配 HTTP 错误码,例如 404。SPA 回退常用 404。'),
12618
+ replaceKeyWith: zod_1.z.string().optional().describe('把匹配结果替换为固定文件路径,例如 index.html。'),
12619
+ replaceKeyPrefixWith: zod_1.z.string().optional().describe('把匹配前缀替换成新的前缀路径。'),
12620
+ });
12621
+ const domainConfigSchema = zod_1.z.object({
12622
+ Refer: zod_1.z.object({
12623
+ Switch: zod_1.z.string().describe('Referer 防盗链开关,例如 on/off。'),
12624
+ RefererRules: zod_1.z.array(zod_1.z.object({
12625
+ RefererType: zod_1.z.string().describe('Referer 规则类型。'),
12626
+ Referers: zod_1.z.array(zod_1.z.string()).describe('Referer 规则值列表。'),
12627
+ AllowEmpty: zod_1.z.boolean().describe('是否允许空 Referer。'),
12628
+ })).optional().describe('Referer 规则列表。'),
12629
+ }).optional().describe('Referer 防盗链配置。'),
12630
+ Cache: zod_1.z.array(zod_1.z.object({
12631
+ RuleType: zod_1.z.string().describe('缓存规则类型,例如 fileType/path。'),
12632
+ RuleValue: zod_1.z.string().describe('规则匹配值。'),
12633
+ CacheTtl: zod_1.z.number().describe('缓存 TTL,单位秒。'),
12634
+ })).optional().describe('CDN 缓存规则列表。'),
12635
+ IpFilter: zod_1.z.object({
12636
+ Switch: zod_1.z.string().describe('IP 访问控制开关,例如 on/off。'),
12637
+ FilterType: zod_1.z.string().optional().describe('过滤类型,例如 blacklist/whitelist。'),
12638
+ Filters: zod_1.z.array(zod_1.z.string()).optional().describe('IP 规则列表。'),
12639
+ }).optional().describe('IP 访问控制配置。'),
12640
+ IpFreqLimit: zod_1.z.object({
12641
+ Switch: zod_1.z.string().describe('IP 频控开关,例如 on/off。'),
12642
+ Qps: zod_1.z.number().optional().describe('每个 IP 的 QPS 上限。'),
12643
+ }).optional().describe('IP 频控配置。'),
12644
+ });
12645
+ const queryHostingInputSchema = {
12646
+ action: zod_1.z.enum(['websiteConfig', 'status', 'findFiles', 'listFiles', 'domainStatus']).describe('查询类型:websiteConfig=查询静态托管网站文档配置与站点域名信息,status=查询静态托管服务状态,findFiles=按前缀查找托管文件,listFiles=列出静态托管中的全部文件,domainStatus=查询自定义域名配置与生效状态。该工具严格只读,不会修改任何资源。'),
12647
+ prefix: zod_1.z.string().optional().describe('文件前缀过滤条件。仅 action=findFiles 时使用,例如 app/ 或 assets/logo。'),
12648
+ marker: zod_1.z.string().optional().describe('分页起始标记。仅 action=findFiles 时使用,用于续查上一页之后的结果。'),
12649
+ maxKeys: zod_1.z.number().int().positive().optional().describe('单次返回的最大文件条数。仅 action=findFiles 时使用。'),
12650
+ domains: zod_1.z.array(zod_1.z.string()).optional().describe('要查询的自定义域名列表。仅 action=domainStatus 时使用,例如 ["www.example.com"]。'),
12651
+ };
12652
+ const manageHostingInputSchema = {
12653
+ action: zod_1.z.enum(['upload', 'delete', 'setWebsiteDocument', 'enableService', 'bindDomain', 'unbindDomain', 'updateDomain', 'downloadFile', 'downloadDirectory']).describe('管理类型:upload=上传本地构建产物到静态托管,delete=删除静态托管文件或目录,setWebsiteDocument=设置首页/错误页/路由规则,enableService=开通静态托管服务,bindDomain=绑定自定义域名,unbindDomain=解绑自定义域名,updateDomain=更新域名缓存/防盗链/IP 规则,downloadFile=下载单个托管文件到本地,downloadDirectory=下载托管目录到本地。'),
12654
+ localPath: zod_1.z.string().optional().describe('本地路径。action=upload 时表示要上传的本地文件/目录路径;action=downloadFile 或 downloadDirectory 时表示下载到本地的目标路径。建议传绝对路径。'),
12655
+ cloudPath: zod_1.z.string().optional().describe('静态托管中的目标路径。action=upload 时表示上传后的托管路径;action=delete/downloadFile/downloadDirectory 时表示托管侧文件或目录路径。'),
12656
+ files: zod_1.z.array(zod_1.z.object({
12657
+ localPath: zod_1.z.string().describe('单个待上传文件的本地绝对路径。'),
12658
+ cloudPath: zod_1.z.string().describe('该文件上传到静态托管后的托管路径。'),
12659
+ })).default([]).describe('多文件上传配置。仅 action=upload 时可选;传入后会逐项上传,不再依赖单个 localPath/cloudPath。'),
12660
+ ignore: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe('上传时忽略的文件模式。仅 action=upload 时可选,例如 node_modules 或 ["**/*.map", "**/.DS_Store"]。'),
12661
+ isDir: zod_1.z.boolean().optional().default(false).describe('是否把 cloudPath 视为目录。仅 action=delete 时使用;true=删除目录,false=删除单个文件。'),
12662
+ confirm: zod_1.z.boolean().optional().default(false).describe('高风险操作确认开关。action=delete 和 action=unbindDomain 时必须显式传 true,避免误删文件或误解绑域名。'),
12663
+ indexDocument: zod_1.z.string().optional().describe('网站首页文档名称。仅 action=setWebsiteDocument 时必填,例如 index.html。'),
12664
+ errorDocument: zod_1.z.string().optional().describe('错误页文档名称。仅 action=setWebsiteDocument 时可选,例如 404.html。'),
12665
+ routingRules: zod_1.z.array(routingRuleSchema).optional().describe('网站路由规则列表。仅 action=setWebsiteDocument 时可选。SPA 常见配置是将 404 重写到 index.html。'),
12666
+ domain: zod_1.z.string().optional().describe('自定义域名。action=bindDomain / unbindDomain / updateDomain 时使用,例如 www.example.com。'),
12667
+ certId: zod_1.z.string().optional().describe('证书 ID。仅 action=bindDomain 时必填。'),
12668
+ domainId: zod_1.z.number().optional().describe('域名 ID。仅 action=updateDomain 时必填,用于精确更新指定域名配置。'),
12669
+ domainConfig: domainConfigSchema.optional().describe('域名配置。仅 action=updateDomain 时必填,支持缓存、Referer、防盗链、IP 规则与频控。'),
12670
+ };
12035
12671
  function isDirectoryUploadTarget(localPath, cloudPath) {
12036
12672
  if (localPath) {
12037
12673
  try {
@@ -12061,277 +12697,661 @@ function buildHostingAccessUrl(staticDomain, cloudPath, localPath) {
12061
12697
  const pathname = isDirectory ? `${normalizedCloudPath}/` : normalizedCloudPath;
12062
12698
  return `https://${staticDomain}/${pathname}`;
12063
12699
  }
12064
- function buildUploadFilesErrorMessage(error, localPath) {
12700
+ function buildUploadErrorMessage(error, localPath) {
12065
12701
  const baseMessage = error instanceof Error ? error.message : String(error);
12066
12702
  const suggestions = [];
12067
12703
  if (/路径不存在|无读写权限/i.test(baseMessage)) {
12068
12704
  if (localPath) {
12069
12705
  suggestions.push(`请先确认本地路径 \`${localPath}\` 存在且当前进程有读取权限。`);
12070
12706
  }
12071
- suggestions.push("如果报错的是构建产物中的某个静态资源文件,请检查构建后的资源引用路径是否正确。");
12072
- suggestions.push("若站点部署到子路径,请确认 `publicPath`、`base`、`assetPrefix` 等配置没有把资源指向不存在的位置。");
12707
+ suggestions.push('如果报错的是构建产物中的某个静态资源文件,请检查构建后的资源引用路径是否正确。');
12708
+ suggestions.push('若站点部署到子路径,请确认 publicPath、base、assetPrefix 等配置没有把资源指向不存在的位置。');
12073
12709
  }
12074
12710
  if (suggestions.length === 0) {
12075
- suggestions.push("请检查上传目录、文件权限和构建产物完整性后重试。");
12711
+ suggestions.push('请检查上传目录、文件权限和构建产物完整性后重试。');
12712
+ }
12713
+ return `[manageHosting(upload)] ${baseMessage}\n建议:${suggestions.join(' ')}`;
12714
+ }
12715
+ function isRecord(value) {
12716
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
12717
+ }
12718
+ function getRecordString(record, keys) {
12719
+ for (const key of keys) {
12720
+ const value = record[key];
12721
+ if (typeof value === 'string' && value.trim()) {
12722
+ return value;
12723
+ }
12724
+ }
12725
+ return undefined;
12726
+ }
12727
+ function collectDomainRecords(value, seen = new Set(), depth = 0) {
12728
+ if (depth > 5 || !value || typeof value !== 'object' || seen.has(value)) {
12729
+ return [];
12730
+ }
12731
+ seen.add(value);
12732
+ if (Array.isArray(value)) {
12733
+ return value.flatMap((item) => collectDomainRecords(item, seen, depth + 1));
12734
+ }
12735
+ const record = value;
12736
+ const current = getRecordString(record, ['Domain', 'domain'])
12737
+ ? [record]
12738
+ : [];
12739
+ return current.concat(Object.values(record).flatMap((item) => collectDomainRecords(item, seen, depth + 1)));
12740
+ }
12741
+ function summarizeHostingDomainCheck(domains, result) {
12742
+ const targetSet = new Set(domains);
12743
+ const matchedRecords = collectDomainRecords(result).filter((record) => {
12744
+ const domain = getRecordString(record, ['Domain', 'domain']);
12745
+ return domain ? targetSet.has(domain) : false;
12746
+ });
12747
+ const matchedDomains = Array.from(new Set(matchedRecords
12748
+ .map((record) => getRecordString(record, ['Domain', 'domain']))
12749
+ .filter((domain) => Boolean(domain))));
12750
+ return {
12751
+ matchedDomains,
12752
+ missingDomains: domains.filter((domain) => !matchedDomains.includes(domain)),
12753
+ domainDetails: matchedRecords,
12754
+ };
12755
+ }
12756
+ function extractTaskStatus(result) {
12757
+ const candidates = [result];
12758
+ if (isRecord(result)) {
12759
+ candidates.push(result.Data, result.Task, result.Result);
12760
+ }
12761
+ for (const candidate of candidates) {
12762
+ if (!isRecord(candidate)) {
12763
+ continue;
12764
+ }
12765
+ const value = getRecordString(candidate, ['Status', 'status', 'TaskStatus', 'State']);
12766
+ if (value) {
12767
+ return value;
12768
+ }
12769
+ }
12770
+ return undefined;
12771
+ }
12772
+ function buildDomainStatusNextStep(domains) {
12773
+ return {
12774
+ tool: 'queryHosting',
12775
+ action: 'domainStatus',
12776
+ suggested_args: {
12777
+ action: 'domainStatus',
12778
+ domains,
12779
+ },
12780
+ };
12781
+ }
12782
+ function buildStatusNextStep() {
12783
+ return {
12784
+ tool: 'queryHosting',
12785
+ action: 'status',
12786
+ suggested_args: {
12787
+ action: 'status',
12788
+ },
12789
+ };
12790
+ }
12791
+ function buildWebsiteConfigNextStep() {
12792
+ return {
12793
+ tool: 'queryHosting',
12794
+ action: 'websiteConfig',
12795
+ suggested_args: {
12796
+ action: 'websiteConfig',
12797
+ },
12798
+ };
12799
+ }
12800
+ function buildFindFilesNextStep(prefix) {
12801
+ return {
12802
+ tool: 'queryHosting',
12803
+ action: 'findFiles',
12804
+ suggested_args: {
12805
+ action: 'findFiles',
12806
+ prefix,
12807
+ },
12808
+ };
12809
+ }
12810
+ async function callTcbHostingAction(cloudbase, action, param, logger) {
12811
+ const service = cloudbase.commonService?.('tcb', '2018-06-08');
12812
+ if (!service?.call) {
12813
+ throw new Error(`当前 CloudBase Manager 实例不支持 commonService.call,无法执行 ${action}。`);
12814
+ }
12815
+ const result = await service.call({
12816
+ Action: action,
12817
+ Param: param,
12818
+ });
12819
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(logger, result);
12820
+ return result;
12821
+ }
12822
+ function extractStaticStores(value) {
12823
+ if (!value || typeof value !== 'object') {
12824
+ return [];
12825
+ }
12826
+ const payload = value;
12827
+ const stores = payload.Data;
12828
+ return Array.isArray(stores) ? stores.filter(isRecord) : [];
12829
+ }
12830
+ async function describeHostingDomainTask(cloudbase, cloudBaseOptions, logger) {
12831
+ try {
12832
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
12833
+ const result = await callTcbHostingAction(cloudbase, 'DescribeHostingDomainTask', { EnvId: envId }, logger);
12834
+ return {
12835
+ rawStatus: extractTaskStatus(result),
12836
+ raw: result,
12837
+ };
12838
+ }
12839
+ catch {
12840
+ return undefined;
12841
+ }
12842
+ }
12843
+ function buildDomainMutationResult(params) {
12844
+ const { action, domain, certId, domainId, domainConfig, result, taskStatus } = params;
12845
+ const actionLabel = action === 'bindDomain'
12846
+ ? '绑定'
12847
+ : action === 'unbindDomain'
12848
+ ? '解绑'
12849
+ : '修改';
12850
+ const successIndicator = action === 'bindDomain'
12851
+ ? `继续调用 queryHosting(action="domainStatus", domains=["${domain}"]),直到返回中出现该域名,并且相关状态字段显示为已生效。`
12852
+ : action === 'unbindDomain'
12853
+ ? `继续调用 queryHosting(action="domainStatus", domains=["${domain}"]),直到返回中不再出现该域名。`
12854
+ : `继续调用 queryHosting(action="domainStatus", domains=["${domain}"]),确认返回中的配置字段已更新为最新值。`;
12855
+ return {
12856
+ success: true,
12857
+ data: {
12858
+ action,
12859
+ targetDomains: [domain],
12860
+ ...(certId ? { certId } : {}),
12861
+ ...(domainId !== undefined ? { domainId } : {}),
12862
+ ...(domainConfig ? { domainConfig } : {}),
12863
+ asyncState: 'PENDING',
12864
+ ...(taskStatus ? { taskStatus } : {}),
12865
+ propagation: {
12866
+ requiresPolling: true,
12867
+ pollTool: 'queryHosting',
12868
+ pollAction: 'domainStatus',
12869
+ pollIntervalSuggestionSeconds: 30,
12870
+ timeoutSuggestionSeconds: 600,
12871
+ successIndicator,
12872
+ },
12873
+ nextActions: [buildDomainStatusNextStep([domain])],
12874
+ result,
12875
+ },
12876
+ message: `静态托管域名${actionLabel}请求已提交。域名配置、证书校验和边缘侧传播通常需要 30 秒到 10 分钟,请继续调用 queryHosting(action="domainStatus") 确认最终结果。`,
12877
+ };
12878
+ }
12879
+ async function getHostingWebsiteConfig(cloudbase, logger) {
12880
+ const websiteConfig = await cloudbase.hosting.getWebsiteConfig();
12881
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(logger, websiteConfig);
12882
+ const hostingResult = {
12883
+ ...websiteConfig,
12884
+ CdnDomain: websiteConfig.CdnDomain ?? null,
12885
+ Bucket: websiteConfig.Bucket ?? null,
12886
+ };
12887
+ try {
12888
+ const envInfo = await cloudbase.env.getEnvInfo();
12889
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(logger, envInfo);
12890
+ hostingResult.CdnDomain = envInfo.EnvInfo?.StaticStorages?.[0]?.StaticDomain ?? hostingResult.CdnDomain;
12891
+ hostingResult.Bucket = envInfo.EnvInfo?.StaticStorages?.[0]?.Bucket ?? hostingResult.Bucket;
12892
+ }
12893
+ catch {
12894
+ // Ignore enrichment failures and return the website config as-is.
12895
+ }
12896
+ return hostingResult;
12897
+ }
12898
+ async function resolveHostingStaticDomain(cloudbase, logger) {
12899
+ try {
12900
+ const envInfo = await cloudbase.env.getEnvInfo();
12901
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(logger, envInfo);
12902
+ return envInfo.EnvInfo?.StaticStorages?.[0]?.StaticDomain;
12903
+ }
12904
+ catch {
12905
+ return undefined;
12906
+ }
12907
+ }
12908
+ function buildFailureResult(action, error) {
12909
+ return (0, tool_result_js_1.buildJsonToolResult)({
12910
+ success: false,
12911
+ errorCode: `HOSTING_${action.toUpperCase()}_FAILED`,
12912
+ message: error instanceof Error ? error.message : String(error),
12913
+ });
12914
+ }
12915
+ function normalizeFileFields(files) {
12916
+ if (!Array.isArray(files))
12917
+ return [];
12918
+ return files.map(file => {
12919
+ if (typeof file !== 'object' || file === null)
12920
+ return file;
12921
+ const record = file;
12922
+ return {
12923
+ key: record.Key ?? record.key ?? '',
12924
+ size: record.Size ?? record.size ?? 0,
12925
+ lastModified: record.LastModified ?? record.lastModified ?? '',
12926
+ ...record,
12927
+ };
12928
+ });
12929
+ }
12930
+ function normalizeHostingStatus(current) {
12931
+ if (!current)
12932
+ return null;
12933
+ return {
12934
+ ...current,
12935
+ status: current.Status ?? current.status ?? 'unknown',
12936
+ staticDomain: current.StaticDomain ?? current.staticDomain ?? null,
12937
+ bucket: current.Bucket ?? current.bucket ?? null,
12938
+ };
12939
+ }
12940
+ function ensureManageHostingActionAllowedInCloudMode(input) {
12941
+ if (!(0, cloud_mode_js_1.isCloudMode)()) {
12942
+ return;
12943
+ }
12944
+ if (input.action === 'upload' || input.action === 'downloadFile' || input.action === 'downloadDirectory') {
12945
+ throw new Error(`manageHosting(action="${input.action}") 在 cloud mode 下不可用,因为该操作依赖本地文件路径。请改用本地模式执行;若只需要远端管理静态托管,可继续使用 delete / setWebsiteDocument / enableService / bindDomain / unbindDomain / updateDomain。`);
12076
12946
  }
12077
- return `[uploadFiles] ${baseMessage}\n建议:${suggestions.join(" ")}`;
12078
12947
  }
12079
12948
  function registerHostingTools(server) {
12080
- // 获取 cloudBaseOptions,如果没有则为 undefined
12081
12949
  const cloudBaseOptions = server.cloudBaseOptions;
12082
- // 创建闭包函数来获取 CloudBase Manager
12083
12950
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
12084
- // uploadFiles - 上传文件到静态网站托管 (cloud-incompatible)
12085
- server.registerTool("uploadFiles", {
12086
- title: "上传静态文件",
12087
- description: "上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建;如果站点会部署到子路径,请检查构建配置中的 publicPath、base、assetPrefix 等是否使用相对路径,避免静态资源加载失败。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。",
12088
- inputSchema: {
12089
- localPath: zod_1.z.string().optional().describe("本地文件或文件夹路径,需要是绝对路径,例如 /tmp/files/data.txt。"),
12090
- cloudPath: zod_1.z.string().optional().describe("静态托管云端文件或文件夹路径,例如 files/data.txt。若部署到子路径,请同时检查构建配置中的 publicPath、base、assetPrefix 等是否为相对路径。云存储对象路径请改用 manageStorage。"),
12091
- files: zod_1.z.array(zod_1.z.object({
12092
- localPath: zod_1.z.string(),
12093
- cloudPath: zod_1.z.string()
12094
- })).default([]).describe("多文件上传配置"),
12095
- ignore: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("忽略文件模式")
12096
- },
12951
+ server.registerTool('queryHosting', {
12952
+ title: '查询静态托管',
12953
+ description: '查询 CloudBase 静态托管的只读信息。适合 AI 先做发现再决定下一步:action=websiteConfig 查询首页/错误页/路由规则与站点域名信息;action=status 查询托管服务状态;action=findFiles 按前缀查找文件;action=listFiles 列出全部托管文件;action=domainStatus 查询自定义域名的当前状态与配置。该工具不会产生任何副作用。',
12954
+ inputSchema: queryHostingInputSchema,
12097
12955
  annotations: {
12098
- readOnlyHint: false,
12099
- destructiveHint: false,
12100
- idempotentHint: false,
12956
+ readOnlyHint: true,
12101
12957
  openWorldHint: true,
12102
- category: "hosting"
12103
- }
12104
- }, async ({ localPath, cloudPath, files = [], ignore }) => {
12105
- const cloudbase = await getManager();
12106
- let result;
12107
- try {
12108
- result = await cloudbase.hosting.uploadFiles({
12109
- localPath,
12110
- cloudPath,
12111
- files,
12112
- ignore
12113
- });
12114
- }
12115
- catch (error) {
12116
- throw new Error(buildUploadFilesErrorMessage(error, localPath));
12117
- }
12118
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12119
- const uploadResult = result;
12120
- // 获取环境信息
12121
- const envInfo = await cloudbase.env.getEnvInfo();
12122
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, envInfo);
12123
- const staticDomain = envInfo.EnvInfo?.StaticStorages?.[0]?.StaticDomain;
12124
- const accessUrl = buildHostingAccessUrl(staticDomain, cloudPath, localPath);
12125
- // Send deployment notification to CodeBuddy IDE
12958
+ category: 'hosting',
12959
+ },
12960
+ }, async (args) => {
12126
12961
  try {
12127
- const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
12128
- // Extract project name from localPath
12129
- let projectName = "unknown";
12130
- if (localPath) {
12131
- try {
12132
- // If localPath is a file, get parent directory name; if it's a directory, get directory name
12133
- const stats = fs_1.default.statSync(localPath);
12134
- if (stats.isFile()) {
12135
- projectName = path_1.default.basename(path_1.default.dirname(localPath));
12136
- }
12137
- else {
12138
- projectName = path_1.default.basename(localPath);
12962
+ const input = args;
12963
+ const cloudbase = await getManager();
12964
+ switch (input.action) {
12965
+ case 'websiteConfig': {
12966
+ const websiteConfig = await getHostingWebsiteConfig(cloudbase, server.logger);
12967
+ return (0, tool_result_js_1.buildJsonToolResult)({
12968
+ success: true,
12969
+ data: {
12970
+ action: 'websiteConfig',
12971
+ websiteConfig,
12972
+ },
12973
+ message: '已获取静态托管网站文档配置与站点域名信息。',
12974
+ });
12975
+ }
12976
+ case 'status': {
12977
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
12978
+ const result = await callTcbHostingAction(cloudbase, 'DescribeStaticStore', { EnvId: envId }, server.logger);
12979
+ const hostingInfo = extractStaticStores(result);
12980
+ const current = normalizeHostingStatus(hostingInfo[0] ?? null);
12981
+ const normalizedHostingInfo = hostingInfo.map(item => normalizeHostingStatus(item));
12982
+ return (0, tool_result_js_1.buildJsonToolResult)({
12983
+ success: true,
12984
+ data: {
12985
+ action: 'status',
12986
+ enabled: hostingInfo.length > 0,
12987
+ current,
12988
+ hostingInfo: normalizedHostingInfo,
12989
+ result,
12990
+ },
12991
+ message: hostingInfo.length > 0
12992
+ ? '已获取静态托管服务状态。'
12993
+ : '静态托管服务当前未返回可用实例信息,可能尚未开通。',
12994
+ });
12995
+ }
12996
+ case 'findFiles': {
12997
+ if (!input.prefix) {
12998
+ throw new Error('queryHosting(action="findFiles") 需要提供 prefix,用于按前缀查找托管文件。');
12139
12999
  }
13000
+ const result = await cloudbase.hosting.findFiles({
13001
+ prefix: input.prefix,
13002
+ marker: input.marker,
13003
+ maxKeys: input.maxKeys,
13004
+ });
13005
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13006
+ const normalizedFiles = normalizeFileFields(result);
13007
+ return (0, tool_result_js_1.buildJsonToolResult)({
13008
+ success: true,
13009
+ data: {
13010
+ action: 'findFiles',
13011
+ prefix: input.prefix,
13012
+ marker: input.marker,
13013
+ maxKeys: input.maxKeys,
13014
+ files: normalizedFiles,
13015
+ result,
13016
+ },
13017
+ message: `已按前缀 \`${input.prefix}\` 查询静态托管文件,共 ${Array.isArray(normalizedFiles) ? normalizedFiles.length : 0} 个。`,
13018
+ });
13019
+ }
13020
+ case 'listFiles': {
13021
+ const result = await cloudbase.hosting.listFiles();
13022
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13023
+ const normalizedFiles = normalizeFileFields(result);
13024
+ const maxKeys = input.maxKeys ?? 100;
13025
+ const start = input.marker ? parseInt(input.marker, 10) : 0;
13026
+ const paginatedFiles = normalizedFiles.slice(start, start + maxKeys);
13027
+ const nextMarker = start + maxKeys < normalizedFiles.length ? String(start + maxKeys) : undefined;
13028
+ return (0, tool_result_js_1.buildJsonToolResult)({
13029
+ success: true,
13030
+ data: {
13031
+ action: 'listFiles',
13032
+ files: paginatedFiles,
13033
+ totalCount: normalizedFiles.length,
13034
+ marker: input.marker,
13035
+ maxKeys,
13036
+ nextMarker,
13037
+ isTruncated: nextMarker !== undefined,
13038
+ },
13039
+ message: `已列出静态托管中的文件(第 ${start + 1}-${start + paginatedFiles.length} 个,共 ${normalizedFiles.length} 个)。${nextMarker ? ' 还有更多文件,可使用 nextMarker 继续查询。' : ''}`,
13040
+ });
12140
13041
  }
12141
- catch (statErr) {
12142
- // If stat fails, try to extract from path directly
12143
- projectName = path_1.default.basename(localPath);
13042
+ case 'domainStatus': {
13043
+ if (!input.domains || input.domains.length === 0) {
13044
+ throw new Error('queryHosting(action="domainStatus") 需要提供 domains 数组,例如 ["www.example.com"]。');
13045
+ }
13046
+ const result = await cloudbase.hosting.tcbCheckResource({
13047
+ domains: input.domains,
13048
+ });
13049
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13050
+ const summary = summarizeHostingDomainCheck(input.domains, result);
13051
+ const allMatched = summary.missingDomains.length === 0;
13052
+ return (0, tool_result_js_1.buildJsonToolResult)({
13053
+ success: true,
13054
+ data: {
13055
+ action: 'domainStatus',
13056
+ queriedDomains: input.domains,
13057
+ matchedDomains: summary.matchedDomains,
13058
+ missingDomains: summary.missingDomains,
13059
+ domainDetails: summary.domainDetails,
13060
+ ...(allMatched
13061
+ ? {}
13062
+ : {
13063
+ propagation: {
13064
+ requiresPolling: true,
13065
+ pollTool: 'queryHosting',
13066
+ pollAction: 'domainStatus',
13067
+ pollIntervalSuggestionSeconds: 30,
13068
+ timeoutSuggestionSeconds: 600,
13069
+ successIndicator: '目标域名出现在返回结果中,并且相关状态字段显示为已生效。',
13070
+ },
13071
+ nextActions: [buildDomainStatusNextStep(summary.missingDomains)],
13072
+ }),
13073
+ result,
13074
+ },
13075
+ message: allMatched
13076
+ ? '已查询到目标静态托管域名配置。若这是绑定或修改后的确认步骤,请继续核对状态字段、证书信息和配置内容是否符合预期。'
13077
+ : '部分目标静态托管域名尚未在查询结果中出现。若这是绑定后的确认步骤,请继续调用 queryHosting(action="domainStatus") 直到结果收敛或达到超时。',
13078
+ });
12144
13079
  }
12145
13080
  }
12146
- // Build console URL
12147
- const consoleUrl = `https://tcb.cloud.tencent.com/dev?envId=${envId}#/static-hosting`;
12148
- // Send notification
12149
- await (0, notification_js_1.sendDeployNotification)(server, {
12150
- deployType: 'hosting',
12151
- url: accessUrl,
12152
- projectId: envId,
12153
- projectName: projectName,
12154
- consoleUrl: consoleUrl
12155
- });
12156
13081
  }
12157
- catch (notifyErr) {
12158
- // Notification failure should not affect deployment flow
12159
- // Error is already logged in sendDeployNotification
13082
+ catch (error) {
13083
+ const toolPayloadResult = (0, tool_result_js_1.toolPayloadErrorToResult)(error);
13084
+ if (toolPayloadResult) {
13085
+ return toolPayloadResult;
13086
+ }
13087
+ return buildFailureResult(args.action, error);
12160
13088
  }
12161
- return {
12162
- content: [
12163
- {
12164
- type: "text",
12165
- text: JSON.stringify({
12166
- ...uploadResult,
12167
- staticDomain,
12168
- message: "文件上传成功",
12169
- accessUrl: accessUrl
12170
- }, null, 2)
12171
- }
12172
- ]
12173
- };
12174
13089
  });
12175
- // deleteFiles - 删除静态网站托管文件
12176
- server.registerTool?.("deleteFiles", {
12177
- title: "删除静态文件",
12178
- description: "删除静态网站托管的文件或文件夹",
12179
- inputSchema: {
12180
- cloudPath: zod_1.z.string().describe("云端文件或文件夹路径"),
12181
- isDir: zod_1.z.boolean().default(false).describe("是否为文件夹")
12182
- },
13090
+ server.registerTool('manageHosting', {
13091
+ title: '管理静态托管',
13092
+ description: '管理 CloudBase 静态托管的变更操作。action=upload 上传本地构建产物;action=delete 删除托管文件或目录(必须 confirm=true);action=setWebsiteDocument 设置首页/错误页/路由规则;action=enableService 开通静态托管;action=bindDomain / unbindDomain / updateDomain 管理自定义域名;action=downloadFile / downloadDirectory 下载托管内容到本地。若任务只是查看配置、文件或域名状态,请改用 queryHosting。',
13093
+ inputSchema: manageHostingInputSchema,
12183
13094
  annotations: {
12184
13095
  readOnlyHint: false,
12185
13096
  destructiveHint: true,
12186
- idempotentHint: true,
13097
+ idempotentHint: false,
12187
13098
  openWorldHint: true,
12188
- category: "hosting"
12189
- }
12190
- }, async ({ cloudPath, isDir = false }) => {
12191
- const cloudbase = await getManager();
12192
- const result = await cloudbase.hosting.deleteFiles({
12193
- cloudPath,
12194
- isDir
12195
- });
12196
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12197
- return {
12198
- content: [
12199
- {
12200
- type: "text",
12201
- text: JSON.stringify(result, null, 2)
12202
- }
12203
- ]
12204
- };
12205
- });
12206
- // findFiles - 搜索文件
12207
- server.registerTool?.("findFiles", {
12208
- title: "搜索静态文件",
12209
- description: "搜索静态网站托管的文件",
12210
- inputSchema: {
12211
- prefix: zod_1.z.string().describe("匹配前缀"),
12212
- marker: zod_1.z.string().optional().describe("起始对象键标记"),
12213
- maxKeys: zod_1.z.number().optional().describe("单次返回最大条目数")
13099
+ category: 'hosting',
12214
13100
  },
12215
- annotations: {
12216
- readOnlyHint: true,
12217
- openWorldHint: true,
12218
- category: "hosting"
12219
- }
12220
- }, async ({ prefix, marker, maxKeys }) => {
12221
- const cloudbase = await getManager();
12222
- const result = await cloudbase.hosting.findFiles({
12223
- prefix,
12224
- marker,
12225
- maxKeys
12226
- });
12227
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12228
- return {
12229
- content: [
12230
- {
12231
- type: "text",
12232
- text: JSON.stringify(result, null, 2)
13101
+ }, async (args) => {
13102
+ try {
13103
+ const input = args;
13104
+ ensureManageHostingActionAllowedInCloudMode(input);
13105
+ const cloudbase = await getManager();
13106
+ switch (input.action) {
13107
+ case 'upload': {
13108
+ if ((!input.localPath || !input.cloudPath) && (!input.files || input.files.length === 0)) {
13109
+ throw new Error('manageHosting(action="upload") 需要提供 localPath + cloudPath,或提供 files 多文件上传列表。');
13110
+ }
13111
+ let result;
13112
+ try {
13113
+ result = await cloudbase.hosting.uploadFiles({
13114
+ localPath: input.localPath,
13115
+ cloudPath: input.cloudPath,
13116
+ files: input.files ?? [],
13117
+ ignore: input.ignore,
13118
+ });
13119
+ }
13120
+ catch (error) {
13121
+ throw new Error(buildUploadErrorMessage(error, input.localPath));
13122
+ }
13123
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13124
+ const staticDomain = await resolveHostingStaticDomain(cloudbase, server.logger);
13125
+ const accessUrl = buildHostingAccessUrl(staticDomain, input.cloudPath, input.localPath);
13126
+ try {
13127
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
13128
+ let projectName = 'unknown';
13129
+ if (input.localPath) {
13130
+ try {
13131
+ const stats = fs_1.default.statSync(input.localPath);
13132
+ projectName = stats.isFile()
13133
+ ? path_1.default.basename(path_1.default.dirname(input.localPath))
13134
+ : path_1.default.basename(input.localPath);
13135
+ }
13136
+ catch {
13137
+ projectName = path_1.default.basename(input.localPath);
13138
+ }
13139
+ }
13140
+ await (0, notification_js_1.sendDeployNotification)(server, {
13141
+ deployType: 'hosting',
13142
+ url: accessUrl,
13143
+ projectId: envId,
13144
+ projectName,
13145
+ consoleUrl: `https://tcb.cloud.tencent.com/dev?envId=${envId}#/static-hosting`,
13146
+ });
13147
+ }
13148
+ catch {
13149
+ // Notification failure should not block uploads.
13150
+ }
13151
+ const uploadPrefix = input.cloudPath ?? input.files?.[0]?.cloudPath ?? '';
13152
+ return (0, tool_result_js_1.buildJsonToolResult)({
13153
+ success: true,
13154
+ data: {
13155
+ action: 'upload',
13156
+ localPath: input.localPath,
13157
+ cloudPath: input.cloudPath,
13158
+ files: input.files ?? [],
13159
+ ignore: input.ignore,
13160
+ staticDomain,
13161
+ accessUrl,
13162
+ result,
13163
+ nextActions: uploadPrefix ? [buildFindFilesNextStep(uploadPrefix.replace(/^\/+/, ''))] : undefined,
13164
+ },
13165
+ message: '静态托管文件上传成功。若需要校验上传结果,请继续调用 queryHosting(action="findFiles") 或 queryHosting(action="listFiles")。',
13166
+ });
12233
13167
  }
12234
- ]
12235
- };
12236
- });
12237
- // domainManagement - 统一的域名管理工具
12238
- server.registerTool?.("domainManagement", {
12239
- title: "静态托管域名管理",
12240
- description: "统一的域名管理工具,支持绑定、解绑、查询和修改域名配置",
12241
- inputSchema: {
12242
- action: zod_1.z.enum(["create", "delete", "check", "modify"]).describe("操作类型: create=绑定域名, delete=解绑域名, check=查询域名配置, modify=修改域名配置"),
12243
- // 绑定域名参数
12244
- domain: zod_1.z.string().optional().describe("域名"),
12245
- certId: zod_1.z.string().optional().describe("证书ID(绑定域名时必需)"),
12246
- domains: zod_1.z.array(zod_1.z.string()).optional().describe("域名列表(查询配置时使用)"),
12247
- // 修改域名参数
12248
- domainId: zod_1.z.number().optional().describe("域名ID(修改配置时必需)"),
12249
- domainConfig: zod_1.z.object({
12250
- Refer: zod_1.z.object({
12251
- Switch: zod_1.z.string(),
12252
- RefererRules: zod_1.z.array(zod_1.z.object({
12253
- RefererType: zod_1.z.string(),
12254
- Referers: zod_1.z.array(zod_1.z.string()),
12255
- AllowEmpty: zod_1.z.boolean()
12256
- })).optional()
12257
- }).optional(),
12258
- Cache: zod_1.z.array(zod_1.z.object({
12259
- RuleType: zod_1.z.string(),
12260
- RuleValue: zod_1.z.string(),
12261
- CacheTtl: zod_1.z.number()
12262
- })).optional(),
12263
- IpFilter: zod_1.z.object({
12264
- Switch: zod_1.z.string(),
12265
- FilterType: zod_1.z.string().optional(),
12266
- Filters: zod_1.z.array(zod_1.z.string()).optional()
12267
- }).optional(),
12268
- IpFreqLimit: zod_1.z.object({
12269
- Switch: zod_1.z.string(),
12270
- Qps: zod_1.z.number().optional()
12271
- }).optional()
12272
- }).optional().describe("域名配置(修改配置时使用)")
12273
- },
12274
- annotations: {
12275
- readOnlyHint: false,
12276
- destructiveHint: false,
12277
- idempotentHint: false,
12278
- openWorldHint: true,
12279
- category: "hosting"
12280
- }
12281
- }, async ({ action, domain, certId, domains, domainId, domainConfig }) => {
12282
- const cloudbase = await getManager();
12283
- let result;
12284
- switch (action) {
12285
- case "create":
12286
- if (!domain || !certId) {
12287
- throw new Error("绑定域名需要提供域名和证书ID");
13168
+ case 'delete': {
13169
+ if (!input.cloudPath) {
13170
+ throw new Error('manageHosting(action="delete") 需要提供 cloudPath。');
13171
+ }
13172
+ if (!input.confirm) {
13173
+ throw new Error('manageHosting(action="delete") 是破坏性操作,必须显式传 confirm=true。');
13174
+ }
13175
+ const result = await cloudbase.hosting.deleteFiles({
13176
+ cloudPath: input.cloudPath,
13177
+ isDir: input.isDir ?? false,
13178
+ });
13179
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13180
+ // Post-validation: verify deletion was successful
13181
+ let deleteVerified = true;
13182
+ let verificationError;
13183
+ try {
13184
+ const checkResult = await cloudbase.hosting.findFiles({
13185
+ prefix: input.cloudPath,
13186
+ maxKeys: 1,
13187
+ });
13188
+ if (Array.isArray(checkResult) && checkResult.length > 0) {
13189
+ deleteVerified = false;
13190
+ verificationError = `删除后验证失败:文件仍在静态托管中`;
13191
+ }
13192
+ }
13193
+ catch (error) {
13194
+ // If query fails, assume deletion was successful
13195
+ // (file might have been deleted, causing query to return empty)
13196
+ }
13197
+ return (0, tool_result_js_1.buildJsonToolResult)({
13198
+ success: deleteVerified,
13199
+ data: {
13200
+ action: 'delete',
13201
+ cloudPath: input.cloudPath,
13202
+ isDir: input.isDir ?? false,
13203
+ result,
13204
+ verified: deleteVerified,
13205
+ ...(verificationError ? { error: verificationError } : {}),
13206
+ },
13207
+ message: deleteVerified
13208
+ ? `已删除静态托管${input.isDir ? '目录' : '文件'} \`${input.cloudPath}\`。`
13209
+ : `删除操作已提交,但验证发现文件可能未完全删除,请手动确认。`,
13210
+ });
12288
13211
  }
12289
- result = await cloudbase.hosting.CreateHostingDomain({
12290
- domain,
12291
- certId
12292
- });
12293
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12294
- break;
12295
- case "delete":
12296
- if (!domain) {
12297
- throw new Error("解绑域名需要提供域名");
13212
+ case 'setWebsiteDocument': {
13213
+ if (!input.indexDocument) {
13214
+ throw new Error('manageHosting(action="setWebsiteDocument") 需要提供 indexDocument,例如 index.html。');
13215
+ }
13216
+ const result = await cloudbase.hosting.setWebsiteDocument({
13217
+ indexDocument: input.indexDocument,
13218
+ errorDocument: input.errorDocument,
13219
+ routingRules: input.routingRules,
13220
+ });
13221
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13222
+ return (0, tool_result_js_1.buildJsonToolResult)({
13223
+ success: true,
13224
+ data: {
13225
+ action: 'setWebsiteDocument',
13226
+ indexDocument: input.indexDocument,
13227
+ errorDocument: input.errorDocument,
13228
+ routingRules: input.routingRules,
13229
+ result,
13230
+ nextActions: [buildWebsiteConfigNextStep()],
13231
+ },
13232
+ message: '静态托管网站文档配置已提交。若需要确认最终配置,请继续调用 queryHosting(action="websiteConfig")。',
13233
+ });
12298
13234
  }
12299
- result = await cloudbase.hosting.deleteHostingDomain({
12300
- domain
12301
- });
12302
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12303
- break;
12304
- case "check":
12305
- if (!domains || domains.length === 0) {
12306
- throw new Error("查询域名配置需要提供域名列表");
13235
+ case 'enableService': {
13236
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
13237
+ const result = await callTcbHostingAction(cloudbase, 'CreateStaticStore', { EnvId: envId }, server.logger);
13238
+ return (0, tool_result_js_1.buildJsonToolResult)({
13239
+ success: true,
13240
+ data: {
13241
+ action: 'enableService',
13242
+ asyncState: 'PENDING',
13243
+ result,
13244
+ nextActions: [buildStatusNextStep()],
13245
+ },
13246
+ message: '静态托管服务开通请求已提交。请继续调用 queryHosting(action="status") 确认服务是否已可用。',
13247
+ });
12307
13248
  }
12308
- result = await cloudbase.hosting.tcbCheckResource({
12309
- domains
12310
- });
12311
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12312
- break;
12313
- case "modify":
12314
- if (!domain || domainId === undefined || !domainConfig) {
12315
- throw new Error("修改域名配置需要提供域名、域名ID和配置信息");
13249
+ case 'bindDomain': {
13250
+ if (!input.domain || !input.certId) {
13251
+ throw new Error('manageHosting(action="bindDomain") 需要提供 domain 和 certId。');
13252
+ }
13253
+ const result = await cloudbase.hosting.CreateHostingDomain({
13254
+ domain: input.domain,
13255
+ certId: input.certId,
13256
+ });
13257
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13258
+ const taskStatus = await describeHostingDomainTask(cloudbase, cloudBaseOptions, server.logger);
13259
+ return (0, tool_result_js_1.buildJsonToolResult)(buildDomainMutationResult({
13260
+ action: 'bindDomain',
13261
+ domain: input.domain,
13262
+ certId: input.certId,
13263
+ result,
13264
+ taskStatus,
13265
+ }));
12316
13266
  }
12317
- result = await cloudbase.hosting.tcbModifyAttribute({
12318
- domain,
12319
- domainId,
12320
- domainConfig
12321
- });
12322
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12323
- break;
12324
- default:
12325
- throw new Error(`不支持的操作类型: ${action}`);
12326
- }
12327
- return {
12328
- content: [
12329
- {
12330
- type: "text",
12331
- text: JSON.stringify(result, null, 2)
13267
+ case 'unbindDomain': {
13268
+ if (!input.domain) {
13269
+ throw new Error('manageHosting(action="unbindDomain") 需要提供 domain。');
13270
+ }
13271
+ if (!input.confirm) {
13272
+ throw new Error('manageHosting(action="unbindDomain") 会解绑现有自定义域名,必须显式传 confirm=true。');
13273
+ }
13274
+ const result = await cloudbase.hosting.deleteHostingDomain({
13275
+ domain: input.domain,
13276
+ });
13277
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13278
+ const taskStatus = await describeHostingDomainTask(cloudbase, cloudBaseOptions, server.logger);
13279
+ return (0, tool_result_js_1.buildJsonToolResult)(buildDomainMutationResult({
13280
+ action: 'unbindDomain',
13281
+ domain: input.domain,
13282
+ result,
13283
+ taskStatus,
13284
+ }));
12332
13285
  }
12333
- ]
12334
- };
13286
+ case 'updateDomain': {
13287
+ if (!input.domain || input.domainId === undefined || !input.domainConfig) {
13288
+ throw new Error('manageHosting(action="updateDomain") 需要同时提供 domain、domainId 和 domainConfig。');
13289
+ }
13290
+ const result = await cloudbase.hosting.tcbModifyAttribute({
13291
+ domain: input.domain,
13292
+ domainId: input.domainId,
13293
+ domainConfig: input.domainConfig,
13294
+ });
13295
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13296
+ const taskStatus = await describeHostingDomainTask(cloudbase, cloudBaseOptions, server.logger);
13297
+ return (0, tool_result_js_1.buildJsonToolResult)(buildDomainMutationResult({
13298
+ action: 'updateDomain',
13299
+ domain: input.domain,
13300
+ domainId: input.domainId,
13301
+ domainConfig: input.domainConfig,
13302
+ result,
13303
+ taskStatus,
13304
+ }));
13305
+ }
13306
+ case 'downloadFile': {
13307
+ if (!input.cloudPath || !input.localPath) {
13308
+ throw new Error('manageHosting(action="downloadFile") 需要同时提供 cloudPath 和 localPath,localPath 应包含目标文件名。');
13309
+ }
13310
+ const result = await cloudbase.hosting.downloadFile({
13311
+ cloudPath: input.cloudPath,
13312
+ localPath: input.localPath,
13313
+ });
13314
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13315
+ return (0, tool_result_js_1.buildJsonToolResult)({
13316
+ success: true,
13317
+ data: {
13318
+ action: 'downloadFile',
13319
+ cloudPath: input.cloudPath,
13320
+ localPath: input.localPath,
13321
+ result,
13322
+ },
13323
+ message: `已将静态托管文件 \`${input.cloudPath}\` 下载到本地路径 \`${input.localPath}\`。`,
13324
+ });
13325
+ }
13326
+ case 'downloadDirectory': {
13327
+ if (!input.cloudPath || !input.localPath) {
13328
+ throw new Error('manageHosting(action="downloadDirectory") 需要同时提供 cloudPath 和 localPath,localPath 应为本地目录路径。');
13329
+ }
13330
+ const result = await cloudbase.hosting.downloadDirectory({
13331
+ cloudPath: input.cloudPath,
13332
+ localPath: input.localPath,
13333
+ });
13334
+ (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
13335
+ return (0, tool_result_js_1.buildJsonToolResult)({
13336
+ success: true,
13337
+ data: {
13338
+ action: 'downloadDirectory',
13339
+ cloudPath: input.cloudPath,
13340
+ localPath: input.localPath,
13341
+ result,
13342
+ },
13343
+ message: `已将静态托管目录 \`${input.cloudPath}\` 下载到本地目录 \`${input.localPath}\`。`,
13344
+ });
13345
+ }
13346
+ }
13347
+ }
13348
+ catch (error) {
13349
+ const toolPayloadResult = (0, tool_result_js_1.toolPayloadErrorToResult)(error);
13350
+ if (toolPayloadResult) {
13351
+ return toolPayloadResult;
13352
+ }
13353
+ return buildFailureResult(args.action, error);
13354
+ }
12335
13355
  });
12336
13356
  }
12337
13357
 
@@ -12753,15 +13773,14 @@ const fs = __importStar(__webpack_require__(4421));
12753
13773
  const fsPromises = __importStar(__webpack_require__(7932));
12754
13774
  const http = __importStar(__webpack_require__(3782));
12755
13775
  const https = __importStar(__webpack_require__(3871));
12756
- const net = __importStar(__webpack_require__(4357));
12757
13776
  const os = __importStar(__webpack_require__(8116));
12758
13777
  const path = __importStar(__webpack_require__(2521));
12759
13778
  const url_1 = __webpack_require__(6887);
12760
13779
  const zod_1 = __webpack_require__(2971);
12761
- const dns = __importStar(__webpack_require__(7793));
13780
+ const remote_url_safety_js_1 = __webpack_require__(9192);
12762
13781
  // 常量定义
12763
13782
  const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
12764
- const ALLOWED_PROTOCOLS = ["http:", "https:"];
13783
+ const MAX_REDIRECT_HOPS = 5;
12765
13784
  const ALLOWED_CONTENT_TYPES = [
12766
13785
  "text/",
12767
13786
  "image/",
@@ -12810,65 +13829,6 @@ function calculateDownloadPath(relativePath) {
12810
13829
  }
12811
13830
  return finalPath;
12812
13831
  }
12813
- // 检查是否为内网 IP
12814
- function isPrivateIP(ip) {
12815
- // 如果不是有效的 IP 地址,返回 true(保守处理)
12816
- if (!net.isIP(ip)) {
12817
- return true;
12818
- }
12819
- // 检查特殊地址
12820
- if (ip === '127.0.0.1' ||
12821
- ip === 'localhost' ||
12822
- ip === '::1' || // IPv6 本地回环
12823
- ip.startsWith('169.254.') || // 链路本地地址
12824
- ip.startsWith('0.')) { // 特殊用途地址
12825
- return true;
12826
- }
12827
- // 转换 IP 地址为长整数进行范围检查
12828
- const ipv4Parts = ip.split('.').map(part => parseInt(part, 10));
12829
- if (ipv4Parts.length === 4) {
12830
- const ipNum = ((ipv4Parts[0] << 24) >>> 0) + (ipv4Parts[1] << 16) + (ipv4Parts[2] << 8) + ipv4Parts[3];
12831
- // 检查私有 IP 范围
12832
- // 10.0.0.0 - 10.255.255.255
12833
- if (ipNum >= 167772160 && ipNum <= 184549375)
12834
- return true;
12835
- // 172.16.0.0 - 172.31.255.255
12836
- if (ipNum >= 2886729728 && ipNum <= 2887778303)
12837
- return true;
12838
- // 192.168.0.0 - 192.168.255.255
12839
- if (ipNum >= 3232235520 && ipNum <= 3232301055)
12840
- return true;
12841
- }
12842
- // 检查 IPv6 私有地址
12843
- if (net.isIPv6(ip)) {
12844
- const normalizedIP = ip.toLowerCase();
12845
- if (normalizedIP.startsWith('fc00:') || // 唯一本地地址
12846
- normalizedIP.startsWith('fe80:') || // 链路本地地址
12847
- normalizedIP.startsWith('fec0:') || // 站点本地地址
12848
- normalizedIP.startsWith('::1')) { // 本地回环
12849
- return true;
12850
- }
12851
- }
12852
- return false;
12853
- }
12854
- // 检查域名是否解析到内网 IP
12855
- async function doesDomainResolveToPrivateIP(hostname) {
12856
- try {
12857
- const addresses = await new Promise((resolve, reject) => {
12858
- dns.resolve(hostname, (err, addresses) => {
12859
- if (err)
12860
- reject(err);
12861
- else
12862
- resolve(addresses);
12863
- });
12864
- });
12865
- return addresses.some(ip => isPrivateIP(ip));
12866
- }
12867
- catch (error) {
12868
- // 如果解析失败,为安全起见返回 true
12869
- return true;
12870
- }
12871
- }
12872
13832
  // 生成随机文件名
12873
13833
  function generateRandomFileName(extension = '') {
12874
13834
  const randomBytes = crypto.randomBytes(16);
@@ -12919,45 +13879,52 @@ function getFileExtension(url, contentType, contentDisposition) {
12919
13879
  }
12920
13880
  return extension;
12921
13881
  }
12922
- // 验证 URL 和内容类型是否安全
12923
- async function isUrlAndContentTypeSafe(url, contentType) {
12924
- try {
12925
- const parsedUrl = new url_1.URL(url);
12926
- // 检查协议
12927
- if (!ALLOWED_PROTOCOLS.includes(parsedUrl.protocol)) {
12928
- return false;
12929
- }
12930
- // 检查主机名是否为 IP 地址
12931
- const hostname = parsedUrl.hostname;
12932
- if (net.isIP(hostname) && isPrivateIP(hostname)) {
12933
- return false;
12934
- }
12935
- // 如果是域名,检查它是否解析到内网 IP
12936
- if (!net.isIP(hostname) && await doesDomainResolveToPrivateIP(hostname)) {
12937
- return false;
12938
- }
12939
- // 检查内容类型
12940
- return ALLOWED_CONTENT_TYPES.some(allowedType => contentType.startsWith(allowedType));
12941
- }
12942
- catch {
12943
- return false;
12944
- }
13882
+ function isAllowedContentType(contentType) {
13883
+ return ALLOWED_CONTENT_TYPES.some((allowedType) => contentType.startsWith(allowedType));
13884
+ }
13885
+ // 检查是否为可重试的网络错误
13886
+ function isRetryableError(error) {
13887
+ const retryableCodes = [
13888
+ 'ECONNRESET',
13889
+ 'ECONNREFUSED',
13890
+ 'ETIMEDOUT',
13891
+ 'ESOCKETTIMEDOUT',
13892
+ 'ENOTFOUND',
13893
+ 'EHOSTUNREACH',
13894
+ 'ENETUNREACH',
13895
+ 'ECONNABORTED'
13896
+ ];
13897
+ return retryableCodes.includes(error.code || '');
12945
13898
  }
12946
- // 下载文件到指定路径
12947
- function downloadFileToPath(url, targetPath) {
13899
+ // 下载文件到指定路径(带重试逻辑)
13900
+ async function downloadFileToPath(url, targetPath, retryCount = 0, redirectCount = 0) {
13901
+ const MAX_RETRIES = 3;
13902
+ const BASE_DELAY_MS = 1000; // 1秒基础延迟
13903
+ if (redirectCount > MAX_REDIRECT_HOPS) {
13904
+ throw new Error(`重定向次数超过 ${MAX_REDIRECT_HOPS} 次限制`);
13905
+ }
13906
+ const { requestUrl, requestOptions } = await (0, remote_url_safety_js_1.prepareSafeRemoteRequest)(url);
13907
+ const client = requestUrl.protocol === "https:" ? https : http;
12948
13908
  return new Promise((resolve, reject) => {
12949
- const client = url.startsWith('https:') ? https : http;
12950
- client.get(url, async (res) => {
13909
+ const request = client.get(requestUrl, requestOptions ?? {}, async (res) => {
13910
+ // 处理重定向
13911
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
13912
+ try {
13913
+ const redirectUrl = new url_1.URL(res.headers.location, requestUrl).toString();
13914
+ return resolve(await downloadFileToPath(redirectUrl, targetPath, retryCount, redirectCount + 1));
13915
+ }
13916
+ catch (error) {
13917
+ return reject(error);
13918
+ }
13919
+ }
12951
13920
  if (res.statusCode !== 200) {
12952
13921
  reject(new Error(`HTTP Error: ${res.statusCode}`));
12953
13922
  return;
12954
13923
  }
12955
- const contentType = res.headers['content-type'] || '';
12956
- const contentLength = parseInt(res.headers['content-length'] || '0', 10);
12957
- const contentDisposition = res.headers['content-disposition'];
12958
- // 安全检查
12959
- if (!await isUrlAndContentTypeSafe(url, contentType)) {
12960
- reject(new Error('不安全的 URL 或内容类型,或者目标为内网地址'));
13924
+ const contentType = res.headers["content-type"] || "";
13925
+ const contentLength = parseInt(res.headers["content-length"] || "0", 10);
13926
+ if (!isAllowedContentType(contentType)) {
13927
+ reject(new Error(`不允许的内容类型: ${contentType || "unknown"}`));
12961
13928
  return;
12962
13929
  }
12963
13930
  // 文件大小检查
@@ -12971,13 +13938,13 @@ function downloadFileToPath(url, targetPath) {
12971
13938
  await fsPromises.mkdir(targetDir, { recursive: true });
12972
13939
  }
12973
13940
  catch (error) {
12974
- reject(new Error(`无法创建目标目录: ${error instanceof Error ? error.message : '未知错误'}`));
13941
+ reject(new Error(`无法创建目标目录: ${error instanceof Error ? error.message : "未知错误"}`));
12975
13942
  return;
12976
13943
  }
12977
13944
  // 创建写入流
12978
13945
  const fileStream = fs.createWriteStream(targetPath);
12979
13946
  let downloadedSize = 0;
12980
- res.on('data', (chunk) => {
13947
+ res.on("data", (chunk) => {
12981
13948
  downloadedSize += chunk.length;
12982
13949
  if (downloadedSize > MAX_FILE_SIZE) {
12983
13950
  fileStream.destroy();
@@ -12986,37 +13953,65 @@ function downloadFileToPath(url, targetPath) {
12986
13953
  }
12987
13954
  });
12988
13955
  res.pipe(fileStream);
12989
- fileStream.on('finish', () => {
13956
+ fileStream.on("finish", () => {
12990
13957
  resolve({
12991
13958
  filePath: targetPath,
12992
13959
  contentType,
12993
- fileSize: downloadedSize
13960
+ fileSize: downloadedSize,
12994
13961
  });
12995
13962
  });
12996
- fileStream.on('error', (error) => {
13963
+ fileStream.on("error", (error) => {
12997
13964
  fsPromises.unlink(targetPath).catch(() => { });
12998
13965
  reject(error);
12999
13966
  });
13000
- }).on('error', (error) => {
13967
+ });
13968
+ request.on("error", async (error) => {
13969
+ // 检查是否可重试
13970
+ if (isRetryableError(error) && retryCount < MAX_RETRIES) {
13971
+ const delay = BASE_DELAY_MS * Math.pow(2, retryCount); // 指数退避
13972
+ console.warn(`下载失败 (${error.code}),${delay}ms 后重试 (${retryCount + 1}/${MAX_RETRIES})...`);
13973
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, delay));
13974
+ try {
13975
+ return resolve(await downloadFileToPath(url, targetPath, retryCount + 1, redirectCount));
13976
+ }
13977
+ catch (retryError) {
13978
+ return reject(retryError);
13979
+ }
13980
+ }
13001
13981
  reject(error);
13002
13982
  });
13003
13983
  });
13004
13984
  }
13005
- // 下载文件到临时目录(保持向后兼容)
13006
- function downloadFile(url) {
13985
+ // 下载文件到临时目录(保持向后兼容,带重试逻辑)
13986
+ async function downloadFile(url, retryCount = 0, redirectCount = 0) {
13987
+ const MAX_RETRIES = 3;
13988
+ const BASE_DELAY_MS = 1000; // 1秒基础延迟
13989
+ if (redirectCount > MAX_REDIRECT_HOPS) {
13990
+ throw new Error(`重定向次数超过 ${MAX_REDIRECT_HOPS} 次限制`);
13991
+ }
13992
+ const { requestUrl, requestOptions } = await (0, remote_url_safety_js_1.prepareSafeRemoteRequest)(url);
13993
+ const client = requestUrl.protocol === "https:" ? https : http;
13007
13994
  return new Promise((resolve, reject) => {
13008
- const client = url.startsWith('https:') ? https : http;
13009
- client.get(url, async (res) => {
13995
+ const request = client.get(requestUrl, requestOptions ?? {}, async (res) => {
13996
+ // 处理重定向
13997
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
13998
+ try {
13999
+ const redirectUrl = new url_1.URL(res.headers.location, requestUrl).toString();
14000
+ return resolve(await downloadFile(redirectUrl, retryCount, redirectCount + 1));
14001
+ }
14002
+ catch (error) {
14003
+ return reject(error);
14004
+ }
14005
+ }
13010
14006
  if (res.statusCode !== 200) {
13011
14007
  reject(new Error(`HTTP Error: ${res.statusCode}`));
13012
14008
  return;
13013
14009
  }
13014
- const contentType = res.headers['content-type'] || '';
13015
- const contentLength = parseInt(res.headers['content-length'] || '0', 10);
13016
- const contentDisposition = res.headers['content-disposition'];
13017
- // 安全检查
13018
- if (!await isUrlAndContentTypeSafe(url, contentType)) {
13019
- reject(new Error('不安全的 URL 或内容类型,或者目标为内网地址'));
14010
+ const contentType = res.headers["content-type"] || "";
14011
+ const contentLength = parseInt(res.headers["content-length"] || "0", 10);
14012
+ const contentDisposition = res.headers["content-disposition"];
14013
+ if (!isAllowedContentType(contentType)) {
14014
+ reject(new Error(`不允许的内容类型: ${contentType || "unknown"}`));
13020
14015
  return;
13021
14016
  }
13022
14017
  // 文件大小检查
@@ -13025,13 +14020,13 @@ function downloadFile(url) {
13025
14020
  return;
13026
14021
  }
13027
14022
  // 生成临时文件路径
13028
- const extension = getFileExtension(url, contentType, contentDisposition);
14023
+ const extension = getFileExtension(requestUrl.toString(), contentType, contentDisposition);
13029
14024
  const fileName = generateRandomFileName(extension);
13030
14025
  const filePath = getSafeTempFilePath(fileName);
13031
14026
  // 创建写入流
13032
14027
  const fileStream = fs.createWriteStream(filePath);
13033
14028
  let downloadedSize = 0;
13034
- res.on('data', (chunk) => {
14029
+ res.on("data", (chunk) => {
13035
14030
  downloadedSize += chunk.length;
13036
14031
  if (downloadedSize > MAX_FILE_SIZE) {
13037
14032
  fileStream.destroy();
@@ -13040,18 +14035,31 @@ function downloadFile(url) {
13040
14035
  }
13041
14036
  });
13042
14037
  res.pipe(fileStream);
13043
- fileStream.on('finish', () => {
14038
+ fileStream.on("finish", () => {
13044
14039
  resolve({
13045
14040
  filePath,
13046
14041
  contentType,
13047
- fileSize: downloadedSize
14042
+ fileSize: downloadedSize,
13048
14043
  });
13049
14044
  });
13050
- fileStream.on('error', (error) => {
14045
+ fileStream.on("error", (error) => {
13051
14046
  fsPromises.unlink(filePath).catch(() => { });
13052
14047
  reject(error);
13053
14048
  });
13054
- }).on('error', (error) => {
14049
+ });
14050
+ request.on("error", async (error) => {
14051
+ // 检查是否可重试
14052
+ if (isRetryableError(error) && retryCount < MAX_RETRIES) {
14053
+ const delay = BASE_DELAY_MS * Math.pow(2, retryCount); // 指数退避
14054
+ console.warn(`下载失败 (${error.code}),${delay}ms 后重试 (${retryCount + 1}/${MAX_RETRIES})...`);
14055
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, delay));
14056
+ try {
14057
+ return resolve(await downloadFile(url, retryCount + 1, redirectCount));
14058
+ }
14059
+ catch (retryError) {
14060
+ return reject(retryError);
14061
+ }
14062
+ }
13055
14063
  reject(error);
13056
14064
  });
13057
14065
  });
@@ -13064,7 +14072,8 @@ function buildDownloadRemoteFileErrorMessage(url, error) {
13064
14072
  suggestions.push("请先在浏览器中确认该链接可访问,再重新执行下载。");
13065
14073
  }
13066
14074
  if (/ENOTFOUND|ECONNRESET|ETIMEDOUT|socket hang up/i.test(baseMessage)) {
13067
- suggestions.push("下载过程中出现网络异常,建议检查本地网络或代理后重试。");
14075
+ suggestions.push("下载过程中出现网络异常,系统已自动重试 3 次但均失败。");
14076
+ suggestions.push("建议:1) 检查本地网络或代理设置;2) 该网站可能限频,请尝试更换其他图片源(如 Unsplash、Pexels);3) 稍后重试。");
13068
14077
  }
13069
14078
  if (suggestions.length === 0) {
13070
14079
  suggestions.push("请检查 URL 是否可访问,以及目标资源是否允许直接下载。");
@@ -13135,6 +14144,191 @@ function registerDownloadTools(server) {
13135
14144
  }
13136
14145
 
13137
14146
 
14147
+ /***/ }),
14148
+
14149
+ /***/ 9192:
14150
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
14151
+
14152
+
14153
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14154
+ if (k2 === undefined) k2 = k;
14155
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14156
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14157
+ desc = { enumerable: true, get: function() { return m[k]; } };
14158
+ }
14159
+ Object.defineProperty(o, k2, desc);
14160
+ }) : (function(o, m, k, k2) {
14161
+ if (k2 === undefined) k2 = k;
14162
+ o[k2] = m[k];
14163
+ }));
14164
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14165
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
14166
+ }) : function(o, v) {
14167
+ o["default"] = v;
14168
+ });
14169
+ var __importStar = (this && this.__importStar) || (function () {
14170
+ var ownKeys = function(o) {
14171
+ ownKeys = Object.getOwnPropertyNames || function (o) {
14172
+ var ar = [];
14173
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
14174
+ return ar;
14175
+ };
14176
+ return ownKeys(o);
14177
+ };
14178
+ return function (mod) {
14179
+ if (mod && mod.__esModule) return mod;
14180
+ var result = {};
14181
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
14182
+ __setModuleDefault(result, mod);
14183
+ return result;
14184
+ };
14185
+ })();
14186
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
14187
+ exports.isPrivateIP = isPrivateIP;
14188
+ exports.prepareSafeRemoteRequest = prepareSafeRemoteRequest;
14189
+ exports.assertRemoteUrlSafe = assertRemoteUrlSafe;
14190
+ exports.getRemoteUrlSafetyErrorMessage = getRemoteUrlSafetyErrorMessage;
14191
+ const dns = __importStar(__webpack_require__(7793));
14192
+ const net = __importStar(__webpack_require__(4357));
14193
+ const url_1 = __webpack_require__(6887);
14194
+ const ALLOWED_PROTOCOLS = new Set(["http:", "https:"]);
14195
+ const DISALLOWED_ERROR_MESSAGE = "不安全的 URL 或目标为内网地址";
14196
+ function ipv4ToNumber(ip) {
14197
+ const parts = ip.split(".").map((part) => Number.parseInt(part, 10));
14198
+ return ((parts[0] << 24) >>> 0) + (parts[1] << 16) + (parts[2] << 8) + parts[3];
14199
+ }
14200
+ function isIpv4InRange(ip, start, end) {
14201
+ const ipNumber = ipv4ToNumber(ip);
14202
+ return ipNumber >= ipv4ToNumber(start) && ipNumber <= ipv4ToNumber(end);
14203
+ }
14204
+ function normalizeIpAddress(ip) {
14205
+ const normalized = ip.toLowerCase();
14206
+ if (normalized.startsWith("::ffff:")) {
14207
+ const mappedIpv4 = normalized.slice(7);
14208
+ if (net.isIPv4(mappedIpv4)) {
14209
+ return mappedIpv4;
14210
+ }
14211
+ }
14212
+ return normalized;
14213
+ }
14214
+ function isPrivateIP(ip) {
14215
+ const normalizedIp = normalizeIpAddress(ip);
14216
+ if (!net.isIP(normalizedIp)) {
14217
+ return true;
14218
+ }
14219
+ if (net.isIPv4(normalizedIp)) {
14220
+ return [
14221
+ ["0.0.0.0", "0.255.255.255"],
14222
+ ["10.0.0.0", "10.255.255.255"],
14223
+ ["100.64.0.0", "100.127.255.255"],
14224
+ ["127.0.0.0", "127.255.255.255"],
14225
+ ["169.254.0.0", "169.254.255.255"],
14226
+ ["172.16.0.0", "172.31.255.255"],
14227
+ ["192.0.0.0", "192.0.0.255"],
14228
+ ["192.0.2.0", "192.0.2.255"],
14229
+ ["192.168.0.0", "192.168.255.255"],
14230
+ ["198.18.0.0", "198.19.255.255"],
14231
+ ["198.51.100.0", "198.51.100.255"],
14232
+ ["203.0.113.0", "203.0.113.255"],
14233
+ ["224.0.0.0", "255.255.255.255"],
14234
+ ].some(([start, end]) => isIpv4InRange(normalizedIp, start, end));
14235
+ }
14236
+ return normalizedIp === "::" ||
14237
+ normalizedIp === "::1" ||
14238
+ normalizedIp.startsWith("fc") ||
14239
+ normalizedIp.startsWith("fd") ||
14240
+ normalizedIp.startsWith("fe8") ||
14241
+ normalizedIp.startsWith("fe9") ||
14242
+ normalizedIp.startsWith("fea") ||
14243
+ normalizedIp.startsWith("feb") ||
14244
+ normalizedIp.startsWith("fec0:") ||
14245
+ normalizedIp.startsWith("ff") ||
14246
+ normalizedIp.startsWith("2001:db8:");
14247
+ }
14248
+ async function resolveSafeAddresses(hostname) {
14249
+ let resolvedAddresses;
14250
+ try {
14251
+ resolvedAddresses = await dns.promises.lookup(hostname, {
14252
+ all: true,
14253
+ verbatim: true,
14254
+ });
14255
+ }
14256
+ catch {
14257
+ throw new Error(DISALLOWED_ERROR_MESSAGE);
14258
+ }
14259
+ if (resolvedAddresses.length === 0) {
14260
+ throw new Error(DISALLOWED_ERROR_MESSAGE);
14261
+ }
14262
+ const normalizedAddresses = resolvedAddresses.map(({ address, family }) => ({
14263
+ address: normalizeIpAddress(address),
14264
+ family,
14265
+ }));
14266
+ if (normalizedAddresses.some(({ address }) => isPrivateIP(address))) {
14267
+ throw new Error(DISALLOWED_ERROR_MESSAGE);
14268
+ }
14269
+ return normalizedAddresses.filter((candidate, index, collection) => collection.findIndex(({ address, family }) => address === candidate.address && family === candidate.family) === index);
14270
+ }
14271
+ function createPinnedLookup(addresses) {
14272
+ return ((hostname, options, callback) => {
14273
+ const normalizedOptions = typeof options === "number" ? { family: options } : (options ?? {});
14274
+ const family = normalizedOptions.family;
14275
+ const candidates = family === 4 || family === 6
14276
+ ? addresses.filter((address) => address.family === family)
14277
+ : addresses;
14278
+ if (candidates.length === 0) {
14279
+ const error = Object.assign(new Error(`No safe address resolved for ${hostname}`), {
14280
+ code: "ENOTFOUND",
14281
+ });
14282
+ callback(error);
14283
+ return;
14284
+ }
14285
+ if (normalizedOptions.all) {
14286
+ callback(null, candidates);
14287
+ return;
14288
+ }
14289
+ callback(null, candidates[0].address, candidates[0].family);
14290
+ });
14291
+ }
14292
+ async function prepareSafeRemoteRequest(url) {
14293
+ let requestUrl;
14294
+ try {
14295
+ requestUrl = new url_1.URL(url);
14296
+ }
14297
+ catch {
14298
+ throw new Error(DISALLOWED_ERROR_MESSAGE);
14299
+ }
14300
+ if (!ALLOWED_PROTOCOLS.has(requestUrl.protocol)) {
14301
+ throw new Error(DISALLOWED_ERROR_MESSAGE);
14302
+ }
14303
+ const hostname = requestUrl.hostname;
14304
+ if (!hostname) {
14305
+ throw new Error(DISALLOWED_ERROR_MESSAGE);
14306
+ }
14307
+ if (hostname.toLowerCase() === "localhost") {
14308
+ throw new Error(DISALLOWED_ERROR_MESSAGE);
14309
+ }
14310
+ if (net.isIP(hostname)) {
14311
+ if (isPrivateIP(hostname)) {
14312
+ throw new Error(DISALLOWED_ERROR_MESSAGE);
14313
+ }
14314
+ return { requestUrl };
14315
+ }
14316
+ const safeAddresses = await resolveSafeAddresses(hostname);
14317
+ return {
14318
+ requestUrl,
14319
+ requestOptions: {
14320
+ lookup: createPinnedLookup(safeAddresses),
14321
+ },
14322
+ };
14323
+ }
14324
+ async function assertRemoteUrlSafe(url) {
14325
+ await prepareSafeRemoteRequest(url);
14326
+ }
14327
+ function getRemoteUrlSafetyErrorMessage() {
14328
+ return DISALLOWED_ERROR_MESSAGE;
14329
+ }
14330
+
14331
+
13138
14332
  /***/ }),
13139
14333
 
13140
14334
  /***/ 9322:
@@ -13400,8 +14594,7 @@ function shouldRegisterTool(toolName) {
13400
14594
  'auth',
13401
14595
  // Storage tools - local file uploads
13402
14596
  'uploadFile',
13403
- // Hosting tools - local file uploads
13404
- 'uploadFiles',
14597
+ // Hosting tools - action-level cloud gating is handled inside manageHosting
13405
14598
  // Function tools - local code uploads
13406
14599
  'updateFunctionCode',
13407
14600
  'createFunction', // also involves local files