@cloudbase/cloudbase-mcp 2.17.1 → 2.19.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -161,16 +161,76 @@ 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 "";
173
232
  }
233
+ const verificationUriComplete = (0, auth_js_1.buildVerificationUriComplete)(deviceAuthInfo);
174
234
  const lines = [
175
235
  "",
176
236
  "### Device Flow 授权信息",
@@ -179,8 +239,11 @@ function formatDeviceAuthHint(deviceAuthInfo) {
179
239
  if (deviceAuthInfo.verification_uri) {
180
240
  lines.push(`- verification_uri: ${deviceAuthInfo.verification_uri}`);
181
241
  }
242
+ if (verificationUriComplete) {
243
+ lines.push(`- verification_uri_complete: ${verificationUriComplete}`);
244
+ }
182
245
  lines.push(`- expires_in: ${deviceAuthInfo.expires_in}s`);
183
- lines.push("", "请在另一台可用浏览器设备打开 `verification_uri` 并输入 `user_code` 完成授权。");
246
+ lines.push("", "请优先向用户展示完整的 `verification_uri_complete`,不要截断或改写 URL。");
184
247
  return lines.join("\n");
185
248
  }
186
249
  function emitDeviceAuthNotice(server, deviceAuthInfo) {
@@ -499,6 +562,49 @@ function normalizeOptionalToolString(value) {
499
562
  ? value.trim()
500
563
  : undefined;
501
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
+ }
502
608
  function normalizeOptionalToolBoolean(value) {
503
609
  return typeof value === "boolean" ? value : undefined;
504
610
  }
@@ -606,13 +712,7 @@ function registerEnvTools(server) {
606
712
  (0, auth_js_1.setPendingAuthProgressState)(info, "device");
607
713
  // emitDeviceAuthNotice(server, info);
608
714
  };
609
- const authChallenge = () => deviceAuthInfo
610
- ? {
611
- user_code: deviceAuthInfo.user_code,
612
- verification_uri: deviceAuthInfo.verification_uri,
613
- expires_in: deviceAuthInfo.expires_in,
614
- }
615
- : undefined;
715
+ const authChallenge = () => (0, auth_js_1.buildDeviceAuthChallengePayload)(deviceAuthInfo);
616
716
  try {
617
717
  if (!supportedAuthActions.includes(action)) {
618
718
  return (0, tool_result_js_1.buildJsonToolResult)({
@@ -659,12 +759,8 @@ function registerEnvTools(server) {
659
759
  current_env_id: null,
660
760
  ...buildEnvCandidatePayload([]),
661
761
  }),
662
- auth_challenge: authFlowState.status === "PENDING" && authFlowState.authChallenge
663
- ? {
664
- user_code: authFlowState.authChallenge.user_code,
665
- verification_uri: authFlowState.authChallenge.verification_uri,
666
- expires_in: authFlowState.authChallenge.expires_in,
667
- }
762
+ auth_challenge: authFlowState.status === "PENDING"
763
+ ? (0, auth_js_1.buildDeviceAuthChallengePayload)(authFlowState.authChallenge)
668
764
  : undefined,
669
765
  message,
670
766
  next_step: authStatus === "REQUIRED"
@@ -685,11 +781,7 @@ function registerEnvTools(server) {
685
781
  ok: true,
686
782
  code: "AUTH_PENDING",
687
783
  message: "设备码授权进行中,请在浏览器中打开 verification_uri 并输入 user_code 完成授权。",
688
- auth_challenge: {
689
- user_code: authFlowState.authChallenge.user_code,
690
- verification_uri: authFlowState.authChallenge.verification_uri,
691
- expires_in: authFlowState.authChallenge.expires_in,
692
- },
784
+ auth_challenge: (0, auth_js_1.buildDeviceAuthChallengePayload)(authFlowState.authChallenge),
693
785
  next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
694
786
  suggestedArgs: { action: "status" },
695
787
  }),
@@ -977,17 +1069,17 @@ function registerEnvTools(server) {
977
1069
  });
978
1070
  }
979
1071
  });
980
- // envQuery - 环境查询(合并 listEnvs + getEnvInfo + getEnvAuthDomains + getWebsiteConfig)
1072
+ // envQuery - 环境查询(合并 listEnvs + getEnvInfo + getEnvAuthDomains)
981
1073
  server.registerTool?.("envQuery", {
982
1074
  title: "环境查询",
983
- 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 等计费字段)。",
984
1076
  inputSchema: {
985
1077
  action: zod_1.z
986
- .enum(["list", "info", "domains", "hosting"])
987
- .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=安全域名列表"),
988
1080
  alias: zod_1.z.string().optional().describe("按环境别名筛选。action=list 时可选"),
989
1081
  aliasExact: zod_1.z.boolean().optional().describe("按环境别名精确筛选。action=list 时可选;与 alias 配合使用"),
990
- 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"),
991
1083
  limit: zod_1.z.number().int().positive().optional().describe("返回数量上限。action=list 时可选"),
992
1084
  offset: zod_1.z.number().int().min(0).optional().describe("分页偏移。action=list 时可选"),
993
1085
  fields: zod_1.z
@@ -1054,14 +1146,12 @@ function registerEnvTools(server) {
1054
1146
  return toolPayloadResult;
1055
1147
  }
1056
1148
  (0, logger_js_1.debug)("降级到 listEnvs() 也失败:", fallbackError instanceof Error ? fallbackError : new Error(String(fallbackError)));
1149
+ const enhancedMessage = buildEnvQueryErrorMessage(fallbackError, "list");
1057
1150
  return {
1058
1151
  content: [
1059
1152
  {
1060
1153
  type: "text",
1061
- text: "获取环境列表时出错: " +
1062
- (fallbackError instanceof Error
1063
- ? fallbackError.message
1064
- : String(fallbackError)),
1154
+ text: enhancedMessage,
1065
1155
  },
1066
1156
  ],
1067
1157
  };
@@ -1126,29 +1216,6 @@ function registerEnvTools(server) {
1126
1216
  };
1127
1217
  }
1128
1218
  break;
1129
- case "hosting": {
1130
- const cloudbaseHosting = await getManager();
1131
- const websiteConfig = await cloudbaseHosting.hosting.getWebsiteConfig();
1132
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, websiteConfig);
1133
- const hostingResult = {
1134
- ...websiteConfig,
1135
- CdnDomain: websiteConfig.CdnDomain ?? null,
1136
- Bucket: websiteConfig.Bucket ?? null,
1137
- };
1138
- try {
1139
- const envInfo = await cloudbaseHosting.env.getEnvInfo();
1140
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, envInfo);
1141
- hostingResult.CdnDomain = envInfo.EnvInfo?.StaticStorages?.[0]?.StaticDomain ?? hostingResult.CdnDomain;
1142
- hostingResult.Bucket = envInfo.EnvInfo?.StaticStorages?.[0]?.Bucket ?? hostingResult.Bucket;
1143
- }
1144
- catch (hostingInfoError) {
1145
- (0, logger_js_1.debug)("Failed to enrich hosting envQuery result with env info", {
1146
- error: hostingInfoError,
1147
- });
1148
- }
1149
- result = hostingResult;
1150
- break;
1151
- }
1152
1219
  default:
1153
1220
  throw new Error(`不支持的查询类型: ${action}`);
1154
1221
  }
@@ -1167,11 +1234,12 @@ function registerEnvTools(server) {
1167
1234
  if (toolPayloadResult) {
1168
1235
  return toolPayloadResult;
1169
1236
  }
1237
+ const enhancedMessage = buildEnvQueryErrorMessage(error, action);
1170
1238
  return {
1171
1239
  content: [
1172
1240
  {
1173
1241
  type: "text",
1174
- text: `环境查询失败: ${error instanceof Error ? error.message : String(error)}`,
1242
+ text: enhancedMessage,
1175
1243
  },
1176
1244
  ],
1177
1245
  };
@@ -1179,13 +1247,13 @@ function registerEnvTools(server) {
1179
1247
  });
1180
1248
  // envDomainManagement - 环境域名管理(合并 createEnvDomain + deleteEnvDomain)
1181
1249
  server.registerTool?.("envDomainManagement", {
1182
- title: "环境域名管理",
1183
- 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\")。",
1184
1252
  inputSchema: {
1185
1253
  action: zod_1.z
1186
1254
  .enum(["create", "delete"])
1187
- .describe("操作类型:create=添加域名,delete=删除域名"),
1188
- 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)。注意:不是自定义域名,不需要证书。"),
1189
1257
  },
1190
1258
  annotations: {
1191
1259
  readOnlyHint: false,
@@ -1210,14 +1278,11 @@ function registerEnvTools(server) {
1210
1278
  default:
1211
1279
  throw new Error(`不支持的操作类型: ${action}`);
1212
1280
  }
1213
- return {
1214
- content: [
1215
- {
1216
- type: "text",
1217
- text: `${JSON.stringify(result, null, 2)}\n\n请注意安全域名需要10分钟才能生效,用户也应该了解这一点。`,
1218
- },
1219
- ],
1220
- };
1281
+ return (0, tool_result_js_1.buildJsonToolResult)(buildEnvDomainManagementResult({
1282
+ action,
1283
+ domains,
1284
+ result,
1285
+ }));
1221
1286
  }
1222
1287
  catch (error) {
1223
1288
  const toolPayloadResult = (0, tool_result_js_1.toolPayloadErrorToResult)(error);
@@ -1334,6 +1399,7 @@ function buildCapiErrorMessage(service, action, error) {
1334
1399
  const tcbEntry = service === "tcb" ? findTcbActionEntry(action) : undefined;
1335
1400
  const hasInvalidActionError = /invalid or not found|does not exist|not recognized/i.test(baseMessage);
1336
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);
1337
1403
  if (hasInvalidActionError) {
1338
1404
  suggestions.push(`Action \`${action}\` 可能不存在或不对外开放。请不要继续猜测 Action 名称,先确认 service=\`${service}\` 下该 Action 在当前 API 版本是否真实存在。`);
1339
1405
  if (service === "tcb") {
@@ -1364,6 +1430,20 @@ function buildCapiErrorMessage(service, action, error) {
1364
1430
  }
1365
1431
  }
1366
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
+ }
1367
1447
  if (/ECONNRESET|socket hang up|ETIMEDOUT|ENOTFOUND/i.test(baseMessage)) {
1368
1448
  suggestions.push("网络请求异常,建议稍后重试,并检查本地网络/代理设置。");
1369
1449
  }
@@ -1542,7 +1622,7 @@ ${envIdSection}
1542
1622
  ## 环境信息
1543
1623
  - 操作系统: ${os_1.default.type()} ${os_1.default.release()}
1544
1624
  - Node.js版本: ${process.version}
1545
- - MCP 版本:${process.env.npm_package_version || "2.17.1" || 0}
1625
+ - MCP 版本:${process.env.npm_package_version || "2.19.4" || 0}
1546
1626
  - 系统架构: ${os_1.default.arch()}
1547
1627
  - 时间: ${new Date().toISOString()}
1548
1628
  - 请求ID: ${requestId}
@@ -1622,7 +1702,8 @@ function createWrappedHandler(name, handler, server) {
1622
1702
  requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
1623
1703
  ide: server.ide || process.env.INTEGRATION_IDE || ''
1624
1704
  });
1625
- const enhancedErrorMessage = `${errorMessage}\n\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
1705
+ const mcpVersion = true ? "2.19.4" : 0;
1706
+ const enhancedErrorMessage = `${errorMessage}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
1626
1707
  // 创建新的错误对象,保持原有的错误类型但更新消息
1627
1708
  const enhancedError = error instanceof Error
1628
1709
  ? new Error(enhancedErrorMessage)
@@ -1809,6 +1890,7 @@ const DEFAULT_PLUGINS = [
1809
1890
  "cloudrun",
1810
1891
  "gateway",
1811
1892
  "app-auth",
1893
+ "apps",
1812
1894
  "permissions",
1813
1895
  "logs",
1814
1896
  "agents",
@@ -1938,7 +2020,9 @@ async function createCloudBaseMcpServer(options) {
1938
2020
  }
1939
2021
  catch (error) {
1940
2022
  if ((0, tool_result_js_1.isToolPayloadError)(error)) {
1941
- 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);
1942
2026
  }
1943
2027
  throw error;
1944
2028
  }
@@ -2065,16 +2149,59 @@ function registerLogTools(server) {
2065
2149
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
2066
2150
  server.registerTool?.("queryLogs", {
2067
2151
  title: "查询日志服务",
2068
- 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\"`",
2069
2160
  inputSchema: {
2070
- action: zod_1.z.enum(QUERY_LOG_ACTIONS),
2071
- queryString: zod_1.z.string().optional(),
2072
- service: zod_1.z.enum(["tcb", "tcbr"]).optional(),
2073
- startTime: zod_1.z.string().optional(),
2074
- endTime: zod_1.z.string().optional(),
2075
- limit: zod_1.z.number().optional(),
2076
- context: zod_1.z.string().optional(),
2077
- 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` 降序"),
2078
2205
  },
2079
2206
  annotations: {
2080
2207
  readOnlyHint: true,
@@ -2092,7 +2219,14 @@ function registerLogTools(server) {
2092
2219
  }, enabled ? "日志服务已开通" : "日志服务未开通或仍在初始化中"));
2093
2220
  }
2094
2221
  if (!queryString) {
2095
- 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\")`。");
2096
2230
  }
2097
2231
  const result = await cloudbase.log.searchClsLog({
2098
2232
  queryString,
@@ -3249,13 +3383,7 @@ async function throwPendingAuthError() {
3249
3383
  ok: false,
3250
3384
  code: "AUTH_PENDING",
3251
3385
  message: authState.lastError || "设备码授权进行中,请先完成登录后再重试当前工具。",
3252
- auth_challenge: authState.authChallenge
3253
- ? {
3254
- user_code: authState.authChallenge.user_code,
3255
- verification_uri: authState.authChallenge.verification_uri,
3256
- expires_in: authState.authChallenge.expires_in,
3257
- }
3258
- : undefined,
3386
+ auth_challenge: (0, auth_js_1.buildDeviceAuthChallengePayload)(authState.authChallenge),
3259
3387
  next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
3260
3388
  suggestedArgs: {
3261
3389
  action: "status",
@@ -3568,20 +3696,9 @@ async function getCloudBaseManager(options = {}) {
3568
3696
  }
3569
3697
  // envId priority: explicit option > envManager cache > loginState.envId
3570
3698
  const resolvedEnvId = finalEnvId || loginEnvId;
3571
- let region = fallbackRegion;
3572
- if (resolvedEnvId && !cloudBaseOptions?.region) {
3573
- try {
3574
- const envCandidates = await listAvailableEnvCandidates({ loginState });
3575
- const matchedEnv = envCandidates.find((candidate) => candidate.envId === resolvedEnvId);
3576
- if (matchedEnv?.region) {
3577
- region = matchedEnv.region;
3578
- (0, logger_js_1.debug)('使用环境实际 region:', { envId: resolvedEnvId, region });
3579
- }
3580
- }
3581
- catch {
3582
- (0, logger_js_1.debug)('无法获取环境列表,使用 fallback region');
3583
- }
3584
- }
3699
+ // region 直接使用 fallbackRegion(来自 cloudBaseOptions?.region ?? TCB_REGION ?? 'ap-shanghai')
3700
+ // 不再通过 DescribeEnvs 查询 region,以兼容 STS 临时密钥场景
3701
+ const region = fallbackRegion;
3585
3702
  const manager = new manager_node_1.default({
3586
3703
  secretId,
3587
3704
  secretKey,
@@ -3787,13 +3904,8 @@ async function resolveNoSqlInstanceId(options) {
3787
3904
  return resolved;
3788
3905
  }
3789
3906
  async function callNoSqlContentApi(options) {
3790
- const resolvedInstance = await resolveNoSqlInstanceId({
3791
- toolName: options.toolName,
3792
- cloudbase: options.cloudbase,
3793
- cloudBaseOptions: options.cloudBaseOptions,
3794
- instanceIdOverride: options.instanceIdOverride,
3795
- collectionName: options.collectionName,
3796
- });
3907
+ // 直接使用 EnvId 替代 Tag,无需通过 DescribeEnvs 获取 instanceId
3908
+ const envId = await (0, cloudbase_manager_js_1.getEnvId)(options.cloudBaseOptions);
3797
3909
  const startedAt = Date.now();
3798
3910
  try {
3799
3911
  const result = await options.cloudbase
@@ -3803,14 +3915,14 @@ async function callNoSqlContentApi(options) {
3803
3915
  Param: {
3804
3916
  ...options.param,
3805
3917
  TableName: options.collectionName,
3806
- Tag: resolvedInstance.instanceId,
3918
+ EnvId: envId,
3807
3919
  },
3808
3920
  });
3809
3921
  logNoSqlLatency(options.toolName, "cloudApiCall", {
3810
3922
  collectionName: options.collectionName,
3811
3923
  action: options.action,
3812
3924
  durationMs: Date.now() - startedAt,
3813
- instanceIdSource: resolvedInstance.source,
3925
+ instanceIdSource: "envId",
3814
3926
  requestId: result?.RequestId,
3815
3927
  });
3816
3928
  return result;
@@ -3820,20 +3932,9 @@ async function callNoSqlContentApi(options) {
3820
3932
  collectionName: options.collectionName,
3821
3933
  action: options.action,
3822
3934
  durationMs: Date.now() - startedAt,
3823
- instanceIdSource: resolvedInstance.source,
3935
+ instanceIdSource: "envId",
3824
3936
  message: error instanceof Error ? error.message : String(error),
3825
3937
  });
3826
- if (!options.instanceIdOverride &&
3827
- resolvedInstance.cacheKey &&
3828
- isInvalidInstanceIdError(error)) {
3829
- (0, cloudbase_manager_js_1.invalidateDatabaseInstanceIdCache)({
3830
- cacheKey: resolvedInstance.cacheKey,
3831
- });
3832
- logNoSqlLatency(options.toolName, "invalidateInstanceIdCache", {
3833
- collectionName: options.collectionName,
3834
- cacheKey: resolvedInstance.cacheKey,
3835
- });
3836
- }
3837
3938
  throw error;
3838
3939
  }
3839
3940
  }
@@ -3851,18 +3952,25 @@ async function waitForCollectionReady({ cloudbase, collectionName, logger, timeo
3851
3952
  });
3852
3953
  while (Date.now() <= deadline) {
3853
3954
  try {
3854
- 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
+ });
3855
3964
  (0, cloudbase_manager_js_1.logCloudBaseResult)(logger, result);
3856
- if (result.Exists) {
3857
- logger?.({
3858
- type: "toolInfo",
3859
- toolName: "writeNoSqlDatabaseStructure",
3860
- message: "NoSQL collection is ready for subsequent operations",
3861
- collectionName,
3862
- waitedMs: Date.now() - startedAt,
3863
- });
3864
- return;
3865
- }
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;
3866
3974
  }
3867
3975
  catch (error) {
3868
3976
  lastError = error;
@@ -3894,7 +4002,7 @@ function registerDatabaseTools(server) {
3894
4002
  // readNoSqlDatabaseStructure
3895
4003
  server.registerTool?.("readNoSqlDatabaseStructure", {
3896
4004
  title: "读取 NoSQL 数据库结构",
3897
- description: "读取 NoSQL 数据库集合与索引结构,支持列出集合、查看集合详情、列出索引以及检查索引是否存在。",
4005
+ description: "读取 NoSQL 数据库集合与索引结构,支持列出集合、查看集合详情、列出索引以及检查索引是否存在。本工具为服务端管理工具,用于管理端查询数据库结构,不用于编写客户端代码。",
3898
4006
  inputSchema: {
3899
4007
  action: zod_1.z.enum([
3900
4008
  "listCollections",
@@ -3932,9 +4040,14 @@ checkIndex: 检查指定索引是否存在`),
3932
4040
  }, async ({ action, limit, offset, collectionName, indexName }) => {
3933
4041
  const cloudbase = await getManager();
3934
4042
  if (action === "listCollections") {
3935
- const result = await cloudbase.database.listCollections({
3936
- MgoOffset: offset,
3937
- 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
+ },
3938
4051
  });
3939
4052
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
3940
4053
  return {
@@ -3956,17 +4069,30 @@ checkIndex: 检查指定索引是否存在`),
3956
4069
  if (!collectionName) {
3957
4070
  throw new Error("检查集合时必须提供 collectionName");
3958
4071
  }
3959
- const result = await cloudbase.database.checkCollectionExists(collectionName);
3960
- (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
+ }
3961
4087
  return {
3962
4088
  content: [
3963
4089
  {
3964
4090
  type: "text",
3965
4091
  text: JSON.stringify(withCollectionName(collectionName, {
3966
4092
  success: true,
3967
- exists: result.Exists,
3968
- requestId: result.RequestId,
3969
- message: result.Exists
4093
+ exists,
4094
+ requestId,
4095
+ message: exists
3970
4096
  ? "云开发数据库集合已存在"
3971
4097
  : "云开发数据库集合不存在",
3972
4098
  }), null, 2),
@@ -3978,7 +4104,11 @@ checkIndex: 检查指定索引是否存在`),
3978
4104
  if (!collectionName) {
3979
4105
  throw new Error("查看集合详情时必须提供 collectionName");
3980
4106
  }
3981
- 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
+ });
3982
4112
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
3983
4113
  return {
3984
4114
  content: [
@@ -3999,7 +4129,11 @@ checkIndex: 检查指定索引是否存在`),
3999
4129
  if (!collectionName) {
4000
4130
  throw new Error("获取索引列表时必须提供 collectionName");
4001
4131
  }
4002
- 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
+ });
4003
4137
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
4004
4138
  return {
4005
4139
  content: [
@@ -4020,8 +4154,22 @@ checkIndex: 检查指定索引是否存在`),
4020
4154
  if (!collectionName || !indexName) {
4021
4155
  throw new Error("检查索引时必须提供 collectionName 和 indexName");
4022
4156
  }
4023
- const result = await cloudbase.database.checkIndexExists(collectionName, indexName);
4024
- (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 });
4025
4173
  return {
4026
4174
  content: [
4027
4175
  {
@@ -4029,9 +4177,9 @@ checkIndex: 检查指定索引是否存在`),
4029
4177
  text: JSON.stringify(withCollectionName(collectionName, {
4030
4178
  success: true,
4031
4179
  indexName,
4032
- exists: result.Exists,
4033
- requestId: result.RequestId,
4034
- message: result.Exists ? "索引已存在" : "索引不存在",
4180
+ exists,
4181
+ requestId,
4182
+ message: exists ? "索引已存在" : "索引不存在",
4035
4183
  }), null, 2),
4036
4184
  },
4037
4185
  ],
@@ -4041,8 +4189,8 @@ checkIndex: 检查指定索引是否存在`),
4041
4189
  });
4042
4190
  // writeNoSqlDatabaseStructure
4043
4191
  server.registerTool?.("writeNoSqlDatabaseStructure", {
4044
- title: "修改 NoSQL 数据库结构",
4045
- description: "修改 NoSQL 数据库结构,支持创建/删除集合,以及通过 updateCollection 的 updateOptions.CreateIndexes / updateOptions.DropIndexes 添加索引和删除索引。",
4192
+ title: "创建并管理 NoSQL 数据库集合",
4193
+ description: "创建、删除和管理 NoSQL 数据库集合(collection)。支持创建新集合、删除现有集合,以及通过 updateCollection 的 updateOptions.CreateIndexes / updateOptions.DropIndexes 添加索引和删除索引。当需要新建集合时,使用 action=createCollection。本工具为服务端管理工具,用于管理端操作集合和索引结构,不用于编写客户端代码。",
4046
4194
  inputSchema: {
4047
4195
  action: zod_1.z.enum([
4048
4196
  "createCollection",
@@ -4089,9 +4237,22 @@ deleteCollection: 删除集合`),
4089
4237
  }, async ({ action, collectionName, updateOptions }) => {
4090
4238
  const cloudbase = await getManager();
4091
4239
  if (action === "createCollection") {
4092
- const existsResult = await cloudbase.database.checkCollectionExists(collectionName);
4093
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, existsResult);
4094
- 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) {
4095
4256
  return {
4096
4257
  content: [
4097
4258
  {
@@ -4099,7 +4260,7 @@ deleteCollection: 删除集合`),
4099
4260
  text: JSON.stringify(withCollectionName(collectionName, {
4100
4261
  success: true,
4101
4262
  action,
4102
- requestId: existsResult.RequestId,
4263
+ requestId: existsRequestId,
4103
4264
  message: "集合已存在,无需重复创建",
4104
4265
  exists: true,
4105
4266
  }), null, 2),
@@ -4107,7 +4268,13 @@ deleteCollection: 删除集合`),
4107
4268
  ],
4108
4269
  };
4109
4270
  }
4110
- 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
+ });
4111
4278
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
4112
4279
  await waitForCollectionReady({
4113
4280
  cloudbase,
@@ -4132,7 +4299,14 @@ deleteCollection: 删除集合`),
4132
4299
  if (!updateOptions) {
4133
4300
  throw new Error("更新集合时必须提供 options");
4134
4301
  }
4135
- 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
+ });
4136
4310
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
4137
4311
  return {
4138
4312
  content: [
@@ -4149,7 +4323,13 @@ deleteCollection: 删除集合`),
4149
4323
  };
4150
4324
  }
4151
4325
  if (action === "deleteCollection") {
4152
- 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
+ });
4153
4333
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
4154
4334
  const body = withCollectionName(collectionName, {
4155
4335
  success: true,
@@ -4174,7 +4354,7 @@ deleteCollection: 删除集合`),
4174
4354
  // readNoSqlDatabaseContent
4175
4355
  server.registerTool?.("readNoSqlDatabaseContent", {
4176
4356
  title: "查询并获取 NoSQL 数据库数据记录",
4177
- description: "查询并获取 NoSQL 数据库数据记录",
4357
+ description: "查询并获取 NoSQL 数据库数据记录。⚠️ 本工具为服务端管理工具,用于管理端/运维端查询。当任务要求编写客户端应用代码时(例如「用 JS SDK 读取数据」),不应使用本工具,而应在项目代码中编写 @cloudbase/js-sdk 客户端代码(如 db.collection().where().get())。",
4178
4358
  inputSchema: {
4179
4359
  collectionName: zod_1.z.string().describe("集合名称"),
4180
4360
  instanceId: zod_1.z
@@ -4261,7 +4441,7 @@ deleteCollection: 删除集合`),
4261
4441
  // writeNoSqlDatabaseContent
4262
4442
  server.registerTool?.("writeNoSqlDatabaseContent", {
4263
4443
  title: "修改 NoSQL 数据库数据记录",
4264
- 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)` 读取命中不到。",
4265
4445
  inputSchema: {
4266
4446
  action: zod_1.z
4267
4447
  .enum(["insert", "update", "delete"])
@@ -4282,7 +4462,11 @@ deleteCollection: 删除集合`),
4282
4462
  update: zod_1.z
4283
4463
  .union([zod_1.z.object({}).passthrough(), zod_1.z.string()])
4284
4464
  .optional()
4285
- .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 下其他字段保留`),
4286
4470
  isMulti: zod_1.z
4287
4471
  .boolean()
4288
4472
  .optional()
@@ -5333,10 +5517,10 @@ function registerDataModelTools(server) {
5333
5517
  throw new Error(`不支持的操作类型: ${action}`);
5334
5518
  }
5335
5519
  });
5336
- // modifyDataModel - 数据模型修改工具(创建/更新)
5520
+ // modifyDataModel - 数据模型写工具(兼容名称,当前仅支持创建)
5337
5521
  server.registerTool?.("modifyDataModel", {
5338
- title: "修改数据模型",
5339
- description: "基于Mermaid classDiagram创建或更新数据模型。支持创建新模型和更新现有模型结构。内置异步任务监控,自动轮询直至完成或超时。",
5522
+ title: "修改数据模型(当前仅支持创建)",
5523
+ description: "基于Mermaid classDiagram创建数据模型。为保持兼容性,工具名仍为 modifyDataModel;当前仅支持创建新模型,不支持更新现有模型结构。内置异步任务监控,自动轮询直至完成或超时。",
5340
5524
  inputSchema: {
5341
5525
  mermaidDiagram: zod_1.z.string()
5342
5526
  .describe(`Mermaid classDiagram代码,描述数据模型结构。
@@ -5496,10 +5680,10 @@ classDiagram
5496
5680
  status: status,
5497
5681
  action: action,
5498
5682
  message: status === "success"
5499
- ? `数据模型${action === "create" ? "创建" : "更新"}成功,共处理${models.length}个模型`
5683
+ ? `数据模型创建成功,共处理${models.length}个模型`
5500
5684
  : status === "init"
5501
5685
  ? `任务超时,任务ID: ${taskId},请稍后手动查询状态`
5502
- : `数据模型${action === "create" ? "创建" : "更新"}失败`,
5686
+ : `数据模型创建失败`,
5503
5687
  taskResult: statusResult?.Data,
5504
5688
  }, null, 2),
5505
5689
  },
@@ -5745,11 +5929,24 @@ function requireStringParam(value, fieldName, action) {
5745
5929
  }
5746
5930
  return value.trim();
5747
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
+ }
5748
5945
  // OpenAPI 文档 URL 列表
5749
5946
  const OPENAPI_SOURCES = [
5750
5947
  {
5751
5948
  name: "mysqldb",
5752
- description: "MySQL RESTful API - 云开发 MySQL 数据库 HTTP API",
5949
+ description: "关系型数据库 RESTful API (MySQL/PostgreSQL) - 云开发关系型数据库 HTTP API",
5753
5950
  url: "https://docs.cloudbase.net/openapi/mysqldb.v1.openapi.yaml",
5754
5951
  },
5755
5952
  {
@@ -5772,6 +5969,16 @@ const OPENAPI_SOURCES = [
5772
5969
  description: "Storage API - 云存储 HTTP API",
5773
5970
  url: "https://docs.cloudbase.net/openapi/storage.v1.openapi.yaml",
5774
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
+ },
5775
5982
  ];
5776
5983
  async function downloadWebTemplate() {
5777
5984
  const zipPath = path.join(CACHE_BASE_DIR, "web-cloudbase-project.zip");
@@ -6047,6 +6254,8 @@ async function registerRagTools(server) {
6047
6254
  error,
6048
6255
  });
6049
6256
  }
6257
+ const skillNames = skills.map((skill) => path.basename(path.dirname(skill.absolutePath)));
6258
+ const openapiNames = openapis.map((api) => api.name);
6050
6259
  const getDocsManager = () => (0, cloudbase_manager_js_1.createCloudBaseManagerWithOptions)(server.cloudBaseOptions ?? {})
6051
6260
  ?.docs;
6052
6261
  server.registerTool?.("searchKnowledgeBase", {
@@ -6055,6 +6264,11 @@ async function registerRagTools(server) {
6055
6264
 
6056
6265
  强烈推荐始终优先使用固定技能文档 (skill)、OpenAPI 文档 (openapi) 或 CloudBase 官方文档 (docs) 模式进行检索,仅当固定文档无法覆盖你的问题时,再使用向量查询 (vector) 模式。
6057
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
+
6058
6272
  固定技能文档 (skill) 查询当前支持 ${skills.length} 个固定文档,分别是:
6059
6273
  ${skills
6060
6274
  .map((skill) => `文档名:${path.basename(path.dirname(skill.absolutePath))} 文档介绍:${skill.description}`)
@@ -6066,14 +6280,8 @@ async function registerRagTools(server) {
6066
6280
  .join("\n")}`,
6067
6281
  inputSchema: {
6068
6282
  mode: SearchKnowledgeModeEnum,
6069
- skillName: zod_1.z
6070
- .enum(skills.map((skill) => path.basename(path.dirname(skill.absolutePath))))
6071
- .optional()
6072
- .describe("mode=skill 时指定。技能名称。"),
6073
- apiName: zod_1.z
6074
- .enum(openapis.map((api) => api.name))
6075
- .optional()
6076
- .describe("mode=openapi 时指定。API 名称。"),
6283
+ skillName: buildOptionalStringEnum(skillNames, "mode=skill 时指定。技能名称。"),
6284
+ apiName: buildOptionalStringEnum(openapiNames, "mode=openapi 时指定。API 名称。"),
6077
6285
  action: CloudBaseDocsActionEnum.optional().describe("mode=docs 时指定。CloudBase 文档操作类型:listModules=列出所有文档模块,listModuleDocs=获取指定模块的目录结构,findByName=按名称/路径/URL 智能查找,readDoc=读取指定文档 Markdown,searchDocs=全文搜索官方文档。"),
6078
6286
  moduleName: zod_1.z
6079
6287
  .string()
@@ -6166,7 +6374,7 @@ async function registerRagTools(server) {
6166
6374
  content: [
6167
6375
  {
6168
6376
  type: "text",
6169
- 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"}`,
6170
6378
  },
6171
6379
  ],
6172
6380
  };
@@ -6187,7 +6395,7 @@ async function registerRagTools(server) {
6187
6395
  content: [
6188
6396
  {
6189
6397
  type: "text",
6190
- 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"}`,
6191
6399
  },
6192
6400
  ],
6193
6401
  };
@@ -6339,17 +6547,10 @@ function normalizeAccessPath(path) {
6339
6547
  }
6340
6548
  return path.startsWith("/") ? path : `/${path}`;
6341
6549
  }
6342
- function ensureGatewayEnvId(cloudBaseOptions) {
6343
- const envId = cloudBaseOptions?.envId;
6344
- if (!envId) {
6345
- throw new Error("当前网关操作需要已绑定 envId");
6346
- }
6347
- return envId;
6348
- }
6349
6550
  function registerGatewayTools(server) {
6350
6551
  const cloudBaseOptions = server.cloudBaseOptions;
6351
6552
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
6352
- const getGatewayEnvId = () => ensureGatewayEnvId(cloudBaseOptions);
6553
+ const resolveEnvId = () => (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
6353
6554
  const buildEnvelope = (data, message, nextActions) => ({
6354
6555
  success: true,
6355
6556
  data,
@@ -6386,7 +6587,7 @@ function registerGatewayTools(server) {
6386
6587
  const listHttpServiceRoutes = async (domain) => {
6387
6588
  const cloudbase = await getManager();
6388
6589
  const result = await cloudbase.env.describeHttpServiceRoute({
6389
- EnvId: getGatewayEnvId(),
6590
+ EnvId: await resolveEnvId(),
6390
6591
  ...(domain
6391
6592
  ? {
6392
6593
  Filters: [
@@ -6422,7 +6623,7 @@ function registerGatewayTools(server) {
6422
6623
  throw new Error("route.serviceType 和 route.serviceName 为必填项");
6423
6624
  }
6424
6625
  return {
6425
- EnvId: getGatewayEnvId(),
6626
+ EnvId: await resolveEnvId(),
6426
6627
  Domain: {
6427
6628
  Domain: await resolveRouteDomain(domain),
6428
6629
  Routes: [
@@ -6532,6 +6733,9 @@ function registerGatewayTools(server) {
6532
6733
  if (!input.targetName || !input.targetType) {
6533
6734
  throw new Error("action=createAccess 时必须提供 targetType 和 targetName");
6534
6735
  }
6736
+ if (!input.type) {
6737
+ throw new Error("action=createAccess 且 targetType=function 时必须显式提供 type。HTTP 云函数传 HTTP;Event 函数传 Event。省略会默认按 Event 路由处理,可能让 HTTP 云函数访问后返回 FUNCTION_PARAM_INVALID。");
6738
+ }
6535
6739
  const cloudbase = await getManager();
6536
6740
  const accessPath = normalizeAccessPath(input.path || `/${input.targetName}`);
6537
6741
  let result;
@@ -6539,7 +6743,7 @@ function registerGatewayTools(server) {
6539
6743
  result = await cloudbase.access.createAccess({
6540
6744
  name: input.targetName,
6541
6745
  path: accessPath,
6542
- type: ((input.type || "Event") === "HTTP" ? 6 : 1),
6746
+ type: (input.type === "HTTP" ? 6 : 1),
6543
6747
  auth: input.auth,
6544
6748
  });
6545
6749
  }
@@ -6547,7 +6751,7 @@ function registerGatewayTools(server) {
6547
6751
  if (err.message && err.message.includes("An error has occurred")) {
6548
6752
  let hint = "为目标资源配置访问路由失败(后端内部错误)。请确保:1) 目标云函数已成功创建并处于 Active 状态;2) 环境默认 HTTP 域名已完成初始化;3) 该访问路径未被占用。";
6549
6753
  if (input.type === "HTTP") {
6550
- hint += "此外注意:如果目标函数最初是作为 Event 函数创建的,这里 type 必须依然传 Event(或省略),传 HTTP 会导致此错误。";
6754
+ hint += "此外注意:如果目标函数最初是作为 Event 函数创建的,这里 type 仍必须传 Event;误传 HTTP 会导致此错误。";
6551
6755
  }
6552
6756
  throw new Error(`${hint} 原始错误:${err.message}`);
6553
6757
  }
@@ -6560,12 +6764,22 @@ function registerGatewayTools(server) {
6560
6764
  targetName: input.targetName,
6561
6765
  path: accessPath,
6562
6766
  raw: result,
6563
- }, `已为目标 ${input.targetName} 创建网关访问路径。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。`, [
6767
+ }, `已为目标 ${input.targetName} 创建网关访问路径。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。该操作只创建网关入口,不会自动放开函数安全规则;若需要匿名或浏览器直接访问,请继续检查函数资源权限。`, [
6564
6768
  {
6565
6769
  tool: "queryGateway",
6566
6770
  action: "getAccess",
6567
6771
  reason: "等待 30 秒到 3 分钟后再确认访问入口是否已生效",
6568
6772
  },
6773
+ {
6774
+ tool: "queryPermissions",
6775
+ action: "getResourcePermission",
6776
+ reason: "确认函数安全规则是否允许预期访问方;网关 auth=false 不等于函数已允许匿名访问",
6777
+ },
6778
+ {
6779
+ tool: "managePermissions",
6780
+ action: "updateResourcePermission",
6781
+ reason: "只有在确认需要匿名或浏览器直连访问时,才按实际安全要求更新函数权限",
6782
+ },
6569
6783
  ]);
6570
6784
  }
6571
6785
  case "createRoute": {
@@ -6596,7 +6810,7 @@ function registerGatewayTools(server) {
6596
6810
  const cloudbase = await getManager();
6597
6811
  const domain = await resolveRouteDomain(input.domain);
6598
6812
  const result = await cloudbase.env.deleteHttpServiceRoute({
6599
- EnvId: getGatewayEnvId(),
6813
+ EnvId: await resolveEnvId(),
6600
6814
  Domain: domain,
6601
6815
  Paths: [normalizeAccessPath(routePath)],
6602
6816
  });
@@ -6614,7 +6828,7 @@ function registerGatewayTools(server) {
6614
6828
  }
6615
6829
  const cloudbase = await getManager();
6616
6830
  const result = await cloudbase.env.bindCustomDomain({
6617
- EnvId: getGatewayEnvId(),
6831
+ EnvId: await resolveEnvId(),
6618
6832
  Domain: {
6619
6833
  Domain: input.domain,
6620
6834
  CertId: input.certificateId,
@@ -6634,7 +6848,7 @@ function registerGatewayTools(server) {
6634
6848
  }
6635
6849
  const cloudbase = await getManager();
6636
6850
  const result = await cloudbase.env.deleteCustomDomain({
6637
- EnvId: getGatewayEnvId(),
6851
+ EnvId: await resolveEnvId(),
6638
6852
  Domain: input.domain,
6639
6853
  });
6640
6854
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
@@ -6718,18 +6932,32 @@ function registerGatewayTools(server) {
6718
6932
  },
6719
6933
  }, async (input) => withEnvelope(() => handleQueryGateway(input)));
6720
6934
  server.registerTool?.("manageGateway", {
6721
- title: "管理网关域资源",
6722
- description: "网关域统一写入口。通过 action 创建目标访问入口,后续承接更通用的网关配置能力。",
6935
+ title: "管理网关域资源(含自定义域名绑定)",
6936
+ description: "网关域统一写入口。通过 action 创建目标访问入口,后续承接更通用的网关配置能力。为已存在的 HTTP 云函数补默认域名访问时,通常使用 createAccess 并提供 targetType=\"function\"、targetName、type=\"HTTP\" 与期望 path。注意 createAccess 只创建网关入口,不会自动修改函数资源权限。⚠️ 如需绑定带 SSL 证书的自定义域名供公网 HTTPS 访问,使用 action=\"bindCustomDomain\"(需要 domain 和 certificateId 参数);如需配置 CORS/安全域名(无证书),请使用 envDomainManagement 工具。",
6723
6937
  inputSchema: {
6724
- 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。'),
6725
6941
  targetType: zod_1.z
6726
6942
  .enum(["function"])
6727
6943
  .optional()
6728
6944
  .describe("目标资源类型。当前支持 function,后续可扩展"),
6729
- targetName: zod_1.z.string().optional().describe("目标资源名称"),
6730
- path: zod_1.z.string().optional().describe("访问路径,默认 /{targetName}"),
6731
- type: zod_1.z.enum(["Event", "HTTP"]).optional().describe("目标函数的本身类型(非接入形式)。如果被访问的函数是 Event 型(默认),此处必须传 Event;只有当被访问函数在创建时就是 HTTP 函数时才传 HTTP。"),
6732
- 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;该开关仅控制网关入口本身,不会修改函数资源权限,仍需检查并按需调整函数安全规则。"),
6733
6961
  route: zod_1.z
6734
6962
  .object({
6735
6963
  routeId: zod_1.z.string().optional(),
@@ -6863,16 +7091,54 @@ function registerInviteCodeTools(server) {
6863
7091
  /***/ }),
6864
7092
 
6865
7093
  /***/ 4848:
6866
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
7094
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
6867
7095
 
6868
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
+ })();
6869
7130
  Object.defineProperty(exports, "__esModule", ({ value: true }));
6870
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));
6871
7135
  const zod_1 = __webpack_require__(2971);
6872
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-';
6873
7139
  // Input schema for queryStorage tool
6874
7140
  const queryStorageInputSchema = {
6875
- 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=读取文本文件内容'),
6876
7142
  cloudPath: zod_1.z.string().describe('云端文件路径,例如 files/data.txt 或 files/(目录)'),
6877
7143
  maxAge: zod_1.z.number().min(1).max(86400).optional().default(3600).describe('临时链接有效期,单位为秒,取值范围:1-86400,默认值:3600(1小时)')
6878
7144
  };
@@ -6884,6 +7150,63 @@ const manageStorageInputSchema = {
6884
7150
  force: zod_1.z.boolean().optional().default(false).describe('强制操作开关,删除操作时建议设置为true以确认删除,默认false'),
6885
7151
  isDirectory: zod_1.z.boolean().optional().default(false).describe('是否为目录操作,true=目录操作,false=文件操作,默认false')
6886
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
+ }
6887
7210
  function registerStorageTools(server) {
6888
7211
  // 获取 cloudBaseOptions,如果没有则为 undefined
6889
7212
  const cloudBaseOptions = server.cloudBaseOptions;
@@ -6892,7 +7215,7 @@ function registerStorageTools(server) {
6892
7215
  // Tool 1: queryStorage - 查询存储信息(只读操作)
6893
7216
  server.registerTool("queryStorage", {
6894
7217
  title: "查询存储信息",
6895
- description: "查询云存储信息,支持列出目录文件、获取文件信息、获取临时下载链接等只读操作。返回的文件信息包括文件名、大小、修改时间、下载链接等。",
7218
+ description: "查询云存储信息,支持列出目录文件、获取文件信息、获取临时下载链接等只读操作。返回的文件信息包括文件名、大小、修改时间、下载链接等。注意:action=url 返回的 temporaryUrl 是临时签名链接,有效期由 maxAge 参数决定(默认1小时),不要当作永久公网地址使用。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导出 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。",
6896
7219
  inputSchema: queryStorageInputSchema,
6897
7220
  annotations: {
6898
7221
  readOnlyHint: true,
@@ -6951,6 +7274,11 @@ function registerStorageTools(server) {
6951
7274
  cloudPath: input.cloudPath,
6952
7275
  maxAge: input.maxAge || 3600
6953
7276
  }]);
7277
+ const publicAccess = await resolveStoragePublicAccess({
7278
+ cloudPath: input.cloudPath,
7279
+ cloudBaseOptions,
7280
+ manager,
7281
+ });
6954
7282
  return {
6955
7283
  content: [
6956
7284
  {
@@ -6962,7 +7290,10 @@ function registerStorageTools(server) {
6962
7290
  cloudPath: input.cloudPath,
6963
7291
  temporaryUrl: result[0]?.url || "",
6964
7292
  expireTime: `${input.maxAge || 3600}秒`,
6965
- 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 或先将目标路径设置为公有读。"
6966
7297
  },
6967
7298
  message: `Successfully generated temporary URL for '${input.cloudPath}'`
6968
7299
  }, null, 2)
@@ -6970,6 +7301,42 @@ function registerStorageTools(server) {
6970
7301
  ]
6971
7302
  };
6972
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
+ }
6973
7340
  default:
6974
7341
  throw new Error(`Unsupported action: ${input.action}`);
6975
7342
  }
@@ -6977,7 +7344,7 @@ function registerStorageTools(server) {
6977
7344
  // Tool 2: manageStorage - 管理存储文件(写操作)
6978
7345
  server.registerTool("manageStorage", {
6979
7346
  title: "管理存储文件",
6980
- description: "管理云存储文件,仅用于 COS/Storage 对象,不用于静态网站托管。支持上传文件/目录、下载文件/目录、删除文件/目录等操作。删除操作需要设置force=true进行确认,防止误删除重要文件。",
7347
+ description: "管理云存储文件,仅用于 COS/Storage 对象,不用于静态网站托管。支持上传文件/目录、下载文件/目录、删除文件/目录等操作。删除操作需要设置force=true进行确认,防止误删除重要文件。注意:上传后返回的 temporaryUrl 是临时签名链接,1小时后过期,不要当作永久公网地址写入配置或持久化存储。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。",
6981
7348
  inputSchema: manageStorageInputSchema,
6982
7349
  annotations: {
6983
7350
  readOnlyHint: false,
@@ -7017,6 +7384,11 @@ function registerStorageTools(server) {
7017
7384
  cloudPath: input.cloudPath,
7018
7385
  maxAge: 3600
7019
7386
  }]);
7387
+ const publicAccess = await resolveStoragePublicAccess({
7388
+ cloudPath: input.cloudPath,
7389
+ cloudBaseOptions,
7390
+ manager,
7391
+ });
7020
7392
  return {
7021
7393
  content: [
7022
7394
  {
@@ -7029,7 +7401,10 @@ function registerStorageTools(server) {
7029
7401
  cloudPath: input.cloudPath,
7030
7402
  isDirectory: input.isDirectory,
7031
7403
  temporaryUrl: fileUrls[0]?.url || "",
7032
- 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 或先将目标路径设置为公有读。"
7033
7408
  },
7034
7409
  message: `Successfully uploaded ${input.isDirectory ? 'directory' : 'file'} from '${input.localPath}' to '${input.cloudPath}'`
7035
7410
  }, null, 2)
@@ -7139,14 +7514,15 @@ exports.CLOUDRUN_SERVICE_TYPES = ['function', 'container'];
7139
7514
  exports.CLOUDRUN_ACCESS_TYPES = ['OA', 'PUBLIC', 'MINIAPP', 'VPC'];
7140
7515
  // Input schema for queryCloudRun tool
7141
7516
  const queryCloudRunInputSchema = {
7142
- action: zod_1.z.enum(['list', 'detail', 'templates']).describe('查询操作类型:list=获取云托管服务列表(支持分页和筛选),detail=查询指定服务的详细信息(包括配置、版本、访问地址等),templates=获取可用的项目模板列表(用于初始化新项目)'),
7517
+ action: zod_1.z.enum(['list', 'detail', 'templates', 'getDeployLog']).describe('查询操作类型:list=获取云托管服务列表(支持分页和筛选),detail=查询指定服务的详细信息(包含服务配置和最新部署状态),templates=获取可用的项目模板列表(用于初始化新项目),getDeployLog=获取指定服务最近一次或指定构建的部署日志'),
7143
7518
  // List operation parameters
7144
7519
  pageSize: zod_1.z.number().min(1).max(100).optional().default(10).describe('分页大小,控制每页返回的服务数量。取值范围:1-100,默认值:10。建议根据网络性能和显示需求调整'),
7145
7520
  pageNum: zod_1.z.number().min(1).optional().default(1).describe('页码,用于分页查询。从1开始,默认值:1。配合pageSize使用可实现分页浏览'),
7146
7521
  serverName: zod_1.z.string().optional().describe('服务名称筛选条件,支持模糊匹配。例如:输入"test"可匹配"test-service"、"my-test-app"等服务名称。留空则查询所有服务'),
7147
7522
  serverType: zod_1.z.enum(exports.CLOUDRUN_SERVICE_TYPES).optional().describe('服务类型筛选条件:function=函数型云托管(仅支持Node.js,有特殊的开发要求和限制,适合简单的API服务),container=容器型服务(推荐使用,支持任意语言和框架如Java/Go/Python/PHP/.NET等,适合大多数应用场景)'),
7148
- // Detail operation parameters
7149
- detailServerName: zod_1.z.string().optional().describe('要查询详细信息的服务名称。当action为detail时必需提供,必须是已存在的服务名称。可通过list操作获取可用的服务名称列表'),
7523
+ // Detail and log operation parameters
7524
+ detailServerName: zod_1.z.string().optional().describe('要查询详细信息或部署日志的服务名称。当action为detail或getDeployLog时建议提供,必须是已存在的服务名称。可通过list操作获取可用的服务名称列表'),
7525
+ buildId: zod_1.z.number().optional().describe('构建ID,仅在action=getDeployLog时使用。不传时默认返回最近一次部署的构建日志'),
7150
7526
  };
7151
7527
  // Input schema for manageCloudRun tool
7152
7528
  const ManageCloudRunInputSchema = {
@@ -7254,6 +7630,28 @@ function buildManageCloudRunErrorMessage(action, serverName, error) {
7254
7630
  }
7255
7631
  return `[manageCloudRun/${action}] ${baseMessage}\n建议:${suggestions.join(" ")}`;
7256
7632
  }
7633
+ function getCloudRunQueryServerName(input) {
7634
+ return input.detailServerName || input.serverName;
7635
+ }
7636
+ function normalizeProcessLogText(logs) {
7637
+ return logs
7638
+ .map((log) => {
7639
+ if (typeof log === "string") {
7640
+ return log;
7641
+ }
7642
+ if (log && typeof log === "object") {
7643
+ if ("Log" in log && typeof log.Log === "string") {
7644
+ return log.Log;
7645
+ }
7646
+ if ("Text" in log && typeof log.Text === "string") {
7647
+ return log.Text;
7648
+ }
7649
+ return JSON.stringify(log);
7650
+ }
7651
+ return String(log);
7652
+ })
7653
+ .join("\n");
7654
+ }
7257
7655
  /**
7258
7656
  * Format CloudRun service info for display
7259
7657
  */
@@ -7268,7 +7666,7 @@ function registerCloudRunTools(server) {
7268
7666
  // Tool 1: Get CloudRun service information (read operations)
7269
7667
  server.registerTool("queryCloudRun", {
7270
7668
  title: "查询 CloudRun 服务信息",
7271
- description: "查询云托管服务信息,支持获取服务列表、查询服务详情和获取可用模板列表。返回的服务信息包括服务名称、状态、访问类型、配置详情等。",
7669
+ description: "查询云托管服务信息,支持获取服务列表、查询服务详情、获取可用模板列表和部署日志。返回的服务信息包括服务名称、状态、访问类型、配置详情以及最近部署上下文。",
7272
7670
  inputSchema: queryCloudRunInputSchema,
7273
7671
  annotations: {
7274
7672
  readOnlyHint: true,
@@ -7317,7 +7715,21 @@ function registerCloudRunTools(server) {
7317
7715
  };
7318
7716
  }
7319
7717
  case 'detail': {
7320
- const serverName = input.detailServerName || input.serverName;
7718
+ const serverName = getCloudRunQueryServerName(input);
7719
+ if (!serverName) {
7720
+ return {
7721
+ content: [
7722
+ {
7723
+ type: "text",
7724
+ text: JSON.stringify({
7725
+ success: false,
7726
+ error: "detailServerName or serverName is required for detail action",
7727
+ message: "Please provide detailServerName or serverName."
7728
+ }, null, 2)
7729
+ }
7730
+ ]
7731
+ };
7732
+ }
7321
7733
  const result = await cloudrunService.detail({ serverName });
7322
7734
  if (!result) {
7323
7735
  return {
@@ -7333,6 +7745,30 @@ function registerCloudRunTools(server) {
7333
7745
  ]
7334
7746
  };
7335
7747
  }
7748
+ let latestDeploy = null;
7749
+ let deployRecordsWarning;
7750
+ let message;
7751
+ try {
7752
+ const deployRecords = await cloudrunService.getDeployRecords({ serverName });
7753
+ latestDeploy = deployRecords?.DeployRecords?.[0] ?? null;
7754
+ if (!latestDeploy) {
7755
+ message = `Retrieved details for service '${serverName}'. No deploy records found yet.`;
7756
+ }
7757
+ else if (typeof latestDeploy.Status === 'string' && latestDeploy.Status.includes('failed')) {
7758
+ message = `Service '${serverName}' latest deploy failed. Please use queryCloudRun(action="getDeployLog") for details.`;
7759
+ }
7760
+ else if (typeof latestDeploy.Status === 'string' && latestDeploy.Status.includes('creating')) {
7761
+ message = `Service '${serverName}' latest deploy is still running. Please check again later or query the deploy log for progress.`;
7762
+ }
7763
+ else {
7764
+ message = `Retrieved details for service '${serverName}'. Latest service status: ${result.BaseInfo?.Status || 'unknown'}, latest deploy status: ${latestDeploy.Status || 'unknown'}.`;
7765
+ }
7766
+ }
7767
+ catch (error) {
7768
+ const baseMessage = error instanceof Error ? error.message : String(error);
7769
+ deployRecordsWarning = `Failed to fetch deploy records: ${baseMessage}`;
7770
+ message = `Retrieved details for service '${serverName}', but deploy records are currently unavailable.`;
7771
+ }
7336
7772
  return {
7337
7773
  content: [
7338
7774
  {
@@ -7340,9 +7776,11 @@ function registerCloudRunTools(server) {
7340
7776
  text: JSON.stringify({
7341
7777
  success: true,
7342
7778
  data: {
7343
- service: result
7779
+ service: result,
7780
+ latestDeploy,
7781
+ ...(deployRecordsWarning ? { deployRecordsWarning } : {})
7344
7782
  },
7345
- message: `Retrieved details for service '${serverName}'`
7783
+ message
7346
7784
  }, null, 2)
7347
7785
  }
7348
7786
  ]
@@ -7365,6 +7803,79 @@ function registerCloudRunTools(server) {
7365
7803
  ]
7366
7804
  };
7367
7805
  }
7806
+ case 'getDeployLog': {
7807
+ const serverName = getCloudRunQueryServerName(input);
7808
+ if (!serverName) {
7809
+ return {
7810
+ content: [
7811
+ {
7812
+ type: "text",
7813
+ text: JSON.stringify({
7814
+ success: false,
7815
+ error: "detailServerName or serverName is required for getDeployLog action",
7816
+ message: "Please provide detailServerName or serverName."
7817
+ }, null, 2)
7818
+ }
7819
+ ]
7820
+ };
7821
+ }
7822
+ const deployRecords = await cloudrunService.getDeployRecords({ serverName });
7823
+ const latestDeploy = deployRecords?.DeployRecords?.[0];
7824
+ if (!latestDeploy) {
7825
+ return {
7826
+ content: [
7827
+ {
7828
+ type: "text",
7829
+ text: JSON.stringify({
7830
+ success: false,
7831
+ error: `Service '${serverName}' has no deploy records.`,
7832
+ message: "Please deploy the service first, then query the deploy log again."
7833
+ }, null, 2)
7834
+ }
7835
+ ]
7836
+ };
7837
+ }
7838
+ const buildId = input.buildId ?? latestDeploy.BuildId;
7839
+ const buildLogResult = await cloudrunService.getBuildLog({
7840
+ serverName,
7841
+ buildId,
7842
+ });
7843
+ let processLogs = [];
7844
+ let processLogsWarning;
7845
+ if (latestDeploy.RunId && typeof cloudrunService.getProcessLog === 'function') {
7846
+ try {
7847
+ const processLogResult = await cloudrunService.getProcessLog({
7848
+ RunId: latestDeploy.RunId,
7849
+ });
7850
+ processLogs = processLogResult?.Logs || [];
7851
+ }
7852
+ catch (error) {
7853
+ processLogsWarning = error instanceof Error ? error.message : String(error);
7854
+ }
7855
+ }
7856
+ const buildLogText = typeof buildLogResult?.Log?.Text === 'string' ? buildLogResult.Log.Text : '';
7857
+ const processLogText = Array.isArray(processLogs) && processLogs.length > 0 ? normalizeProcessLogText(processLogs) : '';
7858
+ const combinedLogText = [buildLogText, processLogText].filter(Boolean).join('\n');
7859
+ return {
7860
+ content: [
7861
+ {
7862
+ type: "text",
7863
+ text: JSON.stringify({
7864
+ success: true,
7865
+ data: {
7866
+ buildId,
7867
+ deployRecord: latestDeploy,
7868
+ buildLog: buildLogResult?.Log || null,
7869
+ processLogs,
7870
+ combinedLogText,
7871
+ ...(processLogsWarning ? { processLogsWarning } : {})
7872
+ },
7873
+ message: `Retrieved deploy log for service '${serverName}'`
7874
+ }, null, 2)
7875
+ }
7876
+ ]
7877
+ };
7878
+ }
7368
7879
  default:
7369
7880
  throw new Error(`Unsupported action: ${input.action}`);
7370
7881
  }
@@ -7632,6 +8143,7 @@ for await (let x of res.textStream) {
7632
8143
  name: input.serverName
7633
8144
  }
7634
8145
  };
8146
+ const consoleUrl = `https://tcb.cloud.tencent.com/dev?envId=${currentEnvId}#/platform-run/service/detail?serverName=${input.serverName}&tabId=overview&envId=${currentEnvId}`;
7635
8147
  try {
7636
8148
  fs_1.default.writeFileSync(cloudbasercPath, JSON.stringify(cloudbasercContent, null, 2));
7637
8149
  }
@@ -7675,8 +8187,6 @@ for await (let x of res.textStream) {
7675
8187
  }
7676
8188
  // Extract project name from targetPath
7677
8189
  const projectName = path_1.default.basename(targetPath);
7678
- // Build console URL
7679
- const consoleUrl = `https://tcb.cloud.tencent.com/dev?envId=${currentEnvId}#/platform-run/service/detail?serverName=${input.serverName}&tabId=overview&envId=${currentEnvId}`;
7680
8190
  // Send notification
7681
8191
  await (0, notification_js_1.sendDeployNotification)(server, {
7682
8192
  deployType: 'cloudrun',
@@ -7698,12 +8208,13 @@ for await (let x of res.textStream) {
7698
8208
  success: true,
7699
8209
  data: {
7700
8210
  serviceName: input.serverName,
7701
- status: 'deployed',
8211
+ status: 'deploying',
7702
8212
  deployPath: targetPath,
7703
8213
  serverType: serverType,
7704
- cloudbasercGenerated: true
8214
+ cloudbasercGenerated: true,
8215
+ consoleUrl
7705
8216
  },
7706
- message: `Successfully deployed ${serverType} service '${input.serverName}' from ${targetPath}`
8217
+ message: `Triggered deployment for ${serverType} service '${input.serverName}' from ${targetPath}. You can follow the progress in ${consoleUrl}`
7707
8218
  }, null, 2)
7708
8219
  }
7709
8220
  ]
@@ -8233,7 +8744,7 @@ function registerAppAuthTools(server) {
8233
8744
  };
8234
8745
  server.registerTool?.("queryAppAuth", {
8235
8746
  title: "查询应用认证配置",
8236
- 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。",
8237
8748
  inputSchema: {
8238
8749
  action: zod_1.z.enum(QUERY_APP_AUTH_ACTIONS),
8239
8750
  providerId: zod_1.z.string().optional().describe("provider 标识,如 email、google"),
@@ -8344,7 +8855,7 @@ function registerAppAuthTools(server) {
8344
8855
  }));
8345
8856
  server.registerTool?.("manageAppAuth", {
8346
8857
  title: "管理应用认证配置",
8347
- 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 },再实现对应前端登录逻辑。",
8348
8859
  inputSchema: {
8349
8860
  action: zod_1.z.enum(MANAGE_APP_AUTH_ACTIONS),
8350
8861
  patch: zod_1.z
@@ -8611,7 +9122,7 @@ class TelemetryReporter {
8611
9122
  const nodeVersion = process.version; // Node.js版本
8612
9123
  const arch = os_1.default.arch(); // 系统架构
8613
9124
  // 从构建时注入的版本号获取MCP版本信息
8614
- const mcpVersion = process.env.npm_package_version || "2.17.1" || 0;
9125
+ const mcpVersion = process.env.npm_package_version || "2.19.4" || 0;
8615
9126
  return {
8616
9127
  userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
8617
9128
  deviceId: this.deviceId,
@@ -8994,6 +9505,7 @@ exports.MANAGE_FUNCTION_ACTIONS = [
8994
9505
  "updateFunctionCode",
8995
9506
  "updateFunctionConfig",
8996
9507
  "invokeFunction",
9508
+ "deleteFunction",
8997
9509
  "createFunctionTrigger",
8998
9510
  "deleteFunctionTrigger",
8999
9511
  "createLayerVersion",
@@ -9137,14 +9649,32 @@ function buildFunctionOperationErrorMessage(operation, functionName, functionRoo
9137
9649
  if (/路径不存在/i.test(baseMessage) && expectedFunctionPath) {
9138
9650
  suggestions.push(`当前工具会从 \`functionRootPath + 函数名\` 查找代码目录,期望目录是 \`${expectedFunctionPath}\`。`);
9139
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
+ }
9140
9660
  }
9141
9661
  if (/paths\[0\].*undefined/i.test(baseMessage)) {
9142
- suggestions.push("HTTP 函数创建时需要提供 functionRootPath(指向 cloudfunctions 父目录)或 zipFile,否则 SDK 无法定位函数目录。");
9662
+ suggestions.push("HTTP 函数创建时需要提供 functionRootPath(指向 cloudfunctions 或 functions 目录的绝对路径,不是项目根目录)或 zipFile,否则 SDK 无法定位函数目录。");
9143
9663
  }
9144
9664
  if (/依赖安装失败|package\.json/i.test(baseMessage)) {
9145
9665
  suggestions.push("如果 HTTP 函数只使用原生 Node.js API 且没有第三方依赖,可以保留函数目录中的 index.js 和 scf_bootstrap,工具会跳过依赖安装。");
9146
9666
  suggestions.push("如果你确实依赖 npm 包,请在函数目录下补充 package.json 后重试。");
9147
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
+ }
9148
9678
  if (suggestions.length === 0) {
9149
9679
  suggestions.push("请检查函数名、目录结构和环境中的函数状态后重试。");
9150
9680
  }
@@ -9201,10 +9731,17 @@ function registerFunctionTools(server) {
9201
9731
  throw new Error("createLayerVersion 在 cloud mode 下不支持 contentPath,本地文件内容请改为 base64Content 或改用本地模式执行。");
9202
9732
  }
9203
9733
  };
9734
+ const TIME_FORMAT_REGEX = /^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/;
9204
9735
  const validateLogRange = (startTime, endTime, offset, limit) => {
9205
9736
  if ((offset || 0) + (limit || 0) > 10000) {
9206
9737
  throw new Error("offset+limit 不能大于 10000");
9207
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
+ }
9208
9745
  if (startTime && endTime) {
9209
9746
  const start = new Date(startTime).getTime();
9210
9747
  const end = new Date(endTime).getTime();
@@ -9284,15 +9821,29 @@ function registerFunctionTools(server) {
9284
9821
  }
9285
9822
  validateLogRange(input.startTime, input.endTime, input.offset, input.limit);
9286
9823
  const cloudbase = await getManager();
9287
- const result = await cloudbase.functions.getFunctionLogsV2({
9288
- name: input.functionName,
9289
- offset: input.offset,
9290
- limit: input.limit,
9291
- startTime: input.startTime,
9292
- endTime: input.endTime,
9293
- requestId: input.requestId,
9294
- qualifier: input.qualifier,
9295
- });
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
+ }
9296
9847
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
9297
9848
  return buildEnvelope({
9298
9849
  action: input.action,
@@ -9314,11 +9865,24 @@ function registerFunctionTools(server) {
9314
9865
  }
9315
9866
  validateLogRange(input.startTime, input.endTime);
9316
9867
  const cloudbase = await getManager();
9317
- const result = await cloudbase.functions.getFunctionLogDetail({
9318
- startTime: input.startTime,
9319
- endTime: input.endTime,
9320
- logRequestId: input.requestId,
9321
- });
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
+ }
9322
9886
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
9323
9887
  return buildEnvelope({
9324
9888
  action: input.action,
@@ -9514,7 +10078,7 @@ function registerFunctionTools(server) {
9514
10078
  const functionType = typeof func.type === "string" ? func.type : undefined;
9515
10079
  const expectedFunctionPath = getExpectedFunctionPath(processedRootPath, functionName);
9516
10080
  if (functionType === "HTTP" && !processedRootPath && !input.zipFile) {
9517
- throw new Error("createFunction 创建 HTTP 函数时,需要提供 functionRootPath(指向 cloudfunctions 父目录)或 zipFile。");
10081
+ throw new Error("createFunction 创建 HTTP 函数时,需要提供 functionRootPath(指向 cloudfunctions 或 functions 目录的绝对路径,不是项目根目录)或 zipFile。");
9518
10082
  }
9519
10083
  const hasPackageJson = expectedFunctionPath !== undefined
9520
10084
  ? (0, fs_1.existsSync)(path_1.default.join(expectedFunctionPath, "package.json"))
@@ -9553,7 +10117,7 @@ function registerFunctionTools(server) {
9553
10117
  nextActions.push({
9554
10118
  tool: "manageGateway",
9555
10119
  action: "createAccess",
9556
- reason: "如果需要通过 URL 访问 HTTP 函数,请按实际路径和鉴权需求显式创建访问入口,不要默认假设 /函数名 已存在",
10120
+ reason: "如果需要通过 URL 访问 HTTP 函数,请调用 manageGateway(action=\"createAccess\") 并显式传 type=\"HTTP\",再按实际路径和鉴权需求创建访问入口,不要默认假设 /函数名 已存在",
9557
10121
  });
9558
10122
  nextActions.push({
9559
10123
  tool: "queryGateway",
@@ -9572,7 +10136,7 @@ function registerFunctionTools(server) {
9572
10136
  });
9573
10137
  }
9574
10138
  const message = func.type === "HTTP"
9575
- ? `已创建 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") 调整权限。`
9576
10140
  : `已创建函数 ${functionName}`;
9577
10141
  return buildEnvelope({
9578
10142
  action: input.action,
@@ -9687,6 +10251,26 @@ function registerFunctionTools(server) {
9687
10251
  throw error;
9688
10252
  }
9689
10253
  }
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
+ }
9690
10274
  case "createFunctionTrigger": {
9691
10275
  if (!input.functionName) {
9692
10276
  throw new Error("createFunctionTrigger 操作时,functionName 参数是必需的");
@@ -9899,23 +10483,58 @@ function registerFunctionTools(server) {
9899
10483
  };
9900
10484
  server.registerTool?.("queryFunctions", {
9901
10485
  title: "查询云函数域资源",
9902
- 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 日志服务(跨服务日志聚合)",
9903
10498
  inputSchema: {
9904
10499
  action: zod_1.z
9905
10500
  .enum(exports.QUERY_FUNCTION_ACTIONS)
9906
- .describe("只读操作类型,例如 listFunctions、getFunctionDetail、listFunctionLogs"),
9907
- functionName: zod_1.z.string().optional().describe("函数名称。函数相关 action 必填"),
9908
- limit: zod_1.z.number().optional().describe("分页数量。列表类 action 可选"),
9909
- offset: zod_1.z.number().optional().describe("分页偏移。列表类 action 可选"),
9910
- codeSecret: zod_1.z.string().optional().describe("代码保护密钥"),
9911
- startTime: zod_1.z.string().optional().describe("日志查询开始时间"),
9912
- endTime: zod_1.z.string().optional().describe("日志查询结束时间"),
9913
- requestId: zod_1.z.string().optional().describe("日志 requestId。获取日志详情时必填"),
9914
- qualifier: zod_1.z.string().optional().describe("函数版本,日志查询时可选"),
9915
- 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"),
9916
10535
  searchKey: zod_1.z.string().optional().describe("层名称搜索关键字"),
9917
- layerName: zod_1.z.string().optional().describe("层名称。层相关 action 必填"),
9918
- 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` 操作必填"),
9919
10538
  },
9920
10539
  annotations: {
9921
10540
  readOnlyHint: true,
@@ -9925,25 +10544,34 @@ function registerFunctionTools(server) {
9925
10544
  }, async (input) => withEnvelope(() => handleQueryFunctions(input)));
9926
10545
  server.registerTool?.("manageFunctions", {
9927
10546
  title: "管理云函数域资源",
9928
- description: "函数域统一写入口。通过 action 管理函数创建、代码更新、配置更新、调用函数、触发器和层绑定。危险操作需要显式 confirm=true。",
10547
+ description: "函数域统一写入口。支持创建函数、更新代码、更新配置、调用函数、管理定时跑 / 定时任务 / scheduled job 的 timer 触发器和层绑定。" +
10548
+ "如果要创建 cron 定时任务,先用 createFunction 创建函数,再用 createFunctionTrigger 创建 timer 触发器(支持7段cron表达式),deleteFunctionTrigger 删除触发器。" +
10549
+ "危险操作需要显式 confirm=true。",
9929
10550
  inputSchema: {
9930
10551
  action: zod_1.z
9931
10552
  .enum(exports.MANAGE_FUNCTION_ACTIONS)
9932
- .describe("写操作类型,例如 createFunction、invokeFunction、attachLayer"),
10553
+ .describe("写操作类型,例如 createFunction、updateFunctionCode、invokeFunction、deleteFunction、" +
10554
+ "createFunctionTrigger(定时任务 / cron / timer)、deleteFunctionTrigger、attachLayer、detachLayer"),
9933
10555
  func: CREATE_FUNCTION_SCHEMA.optional().describe("createFunction 操作的函数配置"),
9934
- functionRootPath: zod_1.z.string().optional().describe("函数根目录(父目录绝对路径)。" +
9935
- "本地应按 cloudfunctions/<functionName>/index.js 布局," +
9936
- "此参数传 cloudfunctions 目录的绝对路径(如 /abs/path/cloudfunctions),不要传到函数名子目录。" +
9937
- "SDK 会自动拼接函数名子目录。"),
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 目录的绝对路径。" +
10561
+ "SDK 会自动拼接函数名子目录,无需预先压缩 zip 或 base64 编码。"),
9938
10562
  force: zod_1.z.boolean().optional().describe("createFunction 时是否覆盖"),
9939
10563
  functionName: zod_1.z.string().optional().describe("函数名称。大多数 action 使用该字段作为统一目标"),
9940
- zipFile: zod_1.z.string().optional().describe("代码包的 base64 编码"),
10564
+ zipFile: zod_1.z.string().optional().describe("仅兼容特殊场景:预先准备好的代码包 base64 编码。普通 createFunction/updateFunctionCode 默认不要先压缩 zip,优先使用 functionRootPath。"),
9941
10565
  handler: zod_1.z.string().optional().describe("函数入口"),
9942
10566
  timeout: zod_1.z.number().optional().describe("配置更新时的超时时间"),
9943
10567
  envVariables: zod_1.z.record(zod_1.z.string()).optional().describe("配置更新时要合并的环境变量"),
9944
10568
  vpc: VPC_SCHEMA.optional().describe("配置更新时的 VPC 信息"),
9945
10569
  params: zod_1.z.record(zod_1.z.any()).optional().describe("invokeFunction 的调用参数"),
9946
- 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分钟执行一次'),
9947
10575
  triggerName: zod_1.z.string().optional().describe("deleteFunctionTrigger 的目标触发器名称"),
9948
10576
  layerName: zod_1.z.string().optional().describe("层名称"),
9949
10577
  layerVersion: zod_1.z.number().optional().describe("层版本号"),
@@ -9957,7 +10585,7 @@ function registerFunctionTools(server) {
9957
10585
  .optional()
9958
10586
  .describe("updateFunctionLayers 的目标层列表,顺序即最终顺序"),
9959
10587
  codeSecret: zod_1.z.string().optional().describe("层绑定时的代码保护密钥"),
9960
- confirm: zod_1.z.boolean().optional().describe("危险操作确认开关"),
10588
+ confirm: zod_1.z.boolean().optional().describe("危险操作确认开关。deleteFunction、deleteFunctionTrigger、deleteLayerVersion、detachLayer 等删除类操作需要显式传入 confirm=true"),
9961
10589
  },
9962
10590
  annotations: {
9963
10591
  readOnlyHint: false,
@@ -10183,6 +10811,23 @@ function buildPermissionHints(securityRule, resourceId) {
10183
10811
  buildTemplateLiteralRuleHint(securityRule, resourceId),
10184
10812
  ].filter(Boolean);
10185
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
+ }
10186
10831
  function registerPermissionTools(server) {
10187
10832
  const cloudBaseOptions = server.cloudBaseOptions;
10188
10833
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
@@ -10225,20 +10870,24 @@ function registerPermissionTools(server) {
10225
10870
  if (!resourceType || !resourceId) {
10226
10871
  throw new Error("action=getResourcePermission 时必须提供 resourceType 和 resourceId");
10227
10872
  }
10873
+ if (resourceType === "storage") {
10874
+ await ensureStorageBucketsExist(cloudbase, [resourceId]);
10875
+ }
10228
10876
  const result = await cloudbase.permission.describeResourcePermission({
10229
10877
  resourceType: mapResourceType(resourceType),
10230
10878
  resources: [resourceId],
10231
10879
  });
10232
10880
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
10233
10881
  const permissions = result.Data.PermissionList ?? [];
10234
- const securityRule = permissions.find((item) => item.Resource === resourceId)?.SecurityRule ??
10235
- permissions[0]?.SecurityRule;
10882
+ const matchedPermission = permissions.find((item) => item.Resource === resourceId) ?? permissions[0];
10883
+ const securityRule = matchedPermission?.SecurityRule;
10236
10884
  const hints = buildPermissionHints(securityRule, resourceId);
10237
10885
  return buildEnvelope({
10238
10886
  action,
10239
10887
  envId,
10240
10888
  resourceType,
10241
10889
  resourceId,
10890
+ aclTag: matchedPermission?.Permission,
10242
10891
  permissions,
10243
10892
  hints,
10244
10893
  raw: result,
@@ -10248,6 +10897,9 @@ function registerPermissionTools(server) {
10248
10897
  if (!resourceType) {
10249
10898
  throw new Error("action=listResourcePermissions 时必须提供 resourceType");
10250
10899
  }
10900
+ if (resourceType === "storage" && resourceIds?.length) {
10901
+ await ensureStorageBucketsExist(cloudbase, resourceIds);
10902
+ }
10251
10903
  const result = await cloudbase.permission.describeResourcePermission({
10252
10904
  resourceType: mapResourceType(resourceType),
10253
10905
  resources: resourceIds,
@@ -10600,7 +11252,9 @@ const http = __importStar(__webpack_require__(3782));
10600
11252
  const https = __importStar(__webpack_require__(3871));
10601
11253
  const os = __importStar(__webpack_require__(8116));
10602
11254
  const path = __importStar(__webpack_require__(2521));
11255
+ const url_1 = __webpack_require__(6887);
10603
11256
  const zod_1 = __webpack_require__(2971);
11257
+ const remote_url_safety_js_1 = __webpack_require__(9192);
10604
11258
  // CloudBase 模板配置
10605
11259
  const TEMPLATES = {
10606
11260
  react: {
@@ -10793,13 +11447,18 @@ function resolveDownloadTemplateIDE(ide, integrationIDE) {
10793
11447
  supportedIDEs: exports.IDE_TYPES.filter((t) => t !== "all"),
10794
11448
  };
10795
11449
  }
11450
+ const MAX_REDIRECT_HOPS = 5;
10796
11451
  // 根据 INTEGRATION_IDE 环境变量获取默认 IDE 类型
10797
11452
  // 下载文件到临时目录
10798
- 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;
10799
11459
  return new Promise((resolve, reject) => {
10800
- const client = url.startsWith("https:") ? https : http;
10801
11460
  client
10802
- .get(url, (res) => {
11461
+ .get(requestUrl, requestOptions ?? {}, (res) => {
10803
11462
  if (res.statusCode === 200) {
10804
11463
  const file = fs.createWriteStream(filePath);
10805
11464
  res.pipe(file);
@@ -10809,10 +11468,11 @@ async function downloadFile(url, filePath) {
10809
11468
  });
10810
11469
  file.on("error", reject);
10811
11470
  }
10812
- else if (res.statusCode === 302 || res.statusCode === 301) {
11471
+ else if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400) {
10813
11472
  // 处理重定向
10814
11473
  if (res.headers.location) {
10815
- downloadFile(res.headers.location, filePath)
11474
+ const redirectUrl = new url_1.URL(res.headers.location, requestUrl).toString();
11475
+ downloadFile(redirectUrl, filePath, redirectCount + 1)
10816
11476
  .then(resolve)
10817
11477
  .catch(reject);
10818
11478
  }
@@ -11008,9 +11668,8 @@ function registerSetupTools(server) {
11008
11668
  // downloadTemplate - 下载项目模板 (cloud-incompatible)
11009
11669
  server.registerTool("downloadTemplate", {
11010
11670
  title: "下载项目模板",
11011
- description: `自动下载并部署CloudBase项目模板。⚠️ **MANDATORY FOR NEW PROJECTS** ⚠️
11012
-
11013
- **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.17.1" : 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.4" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
11014
11673
  inputSchema: {
11015
11674
  template: zod_1.z
11016
11675
  .enum(["react", "vue", "miniprogram", "uniapp", "rules"])
@@ -11207,6 +11866,7 @@ const cloudbase_manager_js_1 = __webpack_require__(3431);
11207
11866
  const json_content_js_1 = __webpack_require__(4365);
11208
11867
  const QUERY_APP_ACTIONS = ["listApps", "getApp", "listAppVersions", "getAppVersion"];
11209
11868
  const MANAGE_APP_ACTIONS = ["deployApp", "deleteApp", "deleteAppVersion"];
11869
+ const APP_FRAMEWORKS = ["vue", "react", "next", "nuxt", "vite", "angular", "static"];
11210
11870
  function buildEnvelope(data, message) {
11211
11871
  return {
11212
11872
  success: true,
@@ -11229,15 +11889,24 @@ function registerAppTools(server) {
11229
11889
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
11230
11890
  server.registerTool?.("queryApps", {
11231
11891
  title: "查询 CloudApp",
11232
- description: "CloudApp 域统一只读入口。支持应用、版本列表与版本详情查询。",
11892
+ description: "CloudApp 域统一只读入口。可先查应用/版本,再在重新部署后用 listAppVersions 或 getAppVersion 按 serviceName 验证是否生成了新版本与最新构建状态。",
11233
11893
  inputSchema: {
11234
11894
  action: zod_1.z.enum(QUERY_APP_ACTIONS),
11235
- serviceName: zod_1.z.string().optional(),
11236
- searchKey: zod_1.z.string().optional(),
11237
- pageNo: zod_1.z.number().optional(),
11238
- pageSize: zod_1.z.number().optional(),
11239
- versionName: zod_1.z.string().optional(),
11240
- 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 后可直接用它轮询状态。"),
11241
11910
  },
11242
11911
  annotations: {
11243
11912
  readOnlyHint: true,
@@ -11316,20 +11985,49 @@ function registerAppTools(server) {
11316
11985
  });
11317
11986
  server.registerTool?.("manageApps", {
11318
11987
  title: "管理 CloudApp",
11319
- description: "CloudApp 域统一写入口。首轮仅支持静态部署、删除应用和删除版本。",
11988
+ description: "CloudApp 域统一写入口。action=deployApp 会先 uploadCode 再 createApp;首次调用创建应用,后续复用同一个 serviceName 会直接触发重新部署并生成新版本,无需先删除旧应用。",
11320
11989
  inputSchema: {
11321
11990
  action: zod_1.z.enum(MANAGE_APP_ACTIONS),
11322
- serviceName: zod_1.z.string(),
11323
- localPath: zod_1.z.string().optional(),
11324
- appPath: zod_1.z.string().optional(),
11325
- buildPath: zod_1.z.string().optional(),
11326
- framework: zod_1.z.string().optional(),
11327
- nodeJsVersion: zod_1.z.string().optional(),
11328
- installCmd: zod_1.z.string().optional(),
11329
- buildCmd: zod_1.z.string().optional(),
11330
- deployCmd: zod_1.z.string().optional(),
11331
- ignore: zod_1.z.array(zod_1.z.string()).optional(),
11332
- 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 时必填。"),
11333
12031
  },
11334
12032
  annotations: {
11335
12033
  readOnlyHint: false,
@@ -11338,7 +12036,7 @@ function registerAppTools(server) {
11338
12036
  openWorldHint: true,
11339
12037
  category: "apps",
11340
12038
  },
11341
- }, 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, }) => {
11342
12040
  try {
11343
12041
  const cloudbase = await getManager();
11344
12042
  const appService = getCloudAppService(cloudbase);
@@ -11346,13 +12044,13 @@ function registerAppTools(server) {
11346
12044
  throw new Error("当前 manager 未提供 cloudAppService");
11347
12045
  }
11348
12046
  if (action === "deployApp") {
11349
- if (!localPath) {
11350
- throw new Error("action=deployApp 时必须提供 localPath");
12047
+ if (!filePath) {
12048
+ throw new Error("action=deployApp 时必须提供 filePath");
11351
12049
  }
11352
12050
  const uploadResult = await appService.uploadCode({
11353
12051
  deployType: "static-hosting",
11354
12052
  serviceName,
11355
- localPath,
12053
+ localPath: filePath,
11356
12054
  ignore,
11357
12055
  });
11358
12056
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, uploadResult);
@@ -11911,7 +12609,65 @@ const fs_1 = __importDefault(__webpack_require__(4421));
11911
12609
  const path_1 = __importDefault(__webpack_require__(2521));
11912
12610
  const zod_1 = __webpack_require__(2971);
11913
12611
  const cloudbase_manager_js_1 = __webpack_require__(3431);
12612
+ const cloud_mode_js_1 = __webpack_require__(9684);
11914
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
+ };
11915
12671
  function isDirectoryUploadTarget(localPath, cloudPath) {
11916
12672
  if (localPath) {
11917
12673
  try {
@@ -11941,277 +12697,661 @@ function buildHostingAccessUrl(staticDomain, cloudPath, localPath) {
11941
12697
  const pathname = isDirectory ? `${normalizedCloudPath}/` : normalizedCloudPath;
11942
12698
  return `https://${staticDomain}/${pathname}`;
11943
12699
  }
11944
- function buildUploadFilesErrorMessage(error, localPath) {
12700
+ function buildUploadErrorMessage(error, localPath) {
11945
12701
  const baseMessage = error instanceof Error ? error.message : String(error);
11946
12702
  const suggestions = [];
11947
12703
  if (/路径不存在|无读写权限/i.test(baseMessage)) {
11948
12704
  if (localPath) {
11949
12705
  suggestions.push(`请先确认本地路径 \`${localPath}\` 存在且当前进程有读取权限。`);
11950
12706
  }
11951
- suggestions.push("如果报错的是构建产物中的某个静态资源文件,请检查构建后的资源引用路径是否正确。");
11952
- suggestions.push("若站点部署到子路径,请确认 `publicPath`、`base`、`assetPrefix` 等配置没有把资源指向不存在的位置。");
12707
+ suggestions.push('如果报错的是构建产物中的某个静态资源文件,请检查构建后的资源引用路径是否正确。');
12708
+ suggestions.push('若站点部署到子路径,请确认 publicPath、base、assetPrefix 等配置没有把资源指向不存在的位置。');
11953
12709
  }
11954
12710
  if (suggestions.length === 0) {
11955
- suggestions.push("请检查上传目录、文件权限和构建产物完整性后重试。");
12711
+ suggestions.push('请检查上传目录、文件权限和构建产物完整性后重试。');
11956
12712
  }
11957
- return `[uploadFiles] ${baseMessage}\n建议:${suggestions.join(" ")}`;
12713
+ return `[manageHosting(upload)] ${baseMessage}\n建议:${suggestions.join(' ')}`;
11958
12714
  }
11959
- function registerHostingTools(server) {
11960
- // 获取 cloudBaseOptions,如果没有则为 undefined
11961
- const cloudBaseOptions = server.cloudBaseOptions;
11962
- // 创建闭包函数来获取 CloudBase Manager
11963
- const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
11964
- // uploadFiles - 上传文件到静态网站托管 (cloud-incompatible)
11965
- server.registerTool("uploadFiles", {
11966
- title: "上传静态文件",
11967
- description: "上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建;如果站点会部署到子路径,请检查构建配置中的 publicPath、base、assetPrefix 等是否使用相对路径,避免静态资源加载失败。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。",
11968
- inputSchema: {
11969
- localPath: zod_1.z.string().optional().describe("本地文件或文件夹路径,需要是绝对路径,例如 /tmp/files/data.txt。"),
11970
- cloudPath: zod_1.z.string().optional().describe("静态托管云端文件或文件夹路径,例如 files/data.txt。若部署到子路径,请同时检查构建配置中的 publicPath、base、assetPrefix 等是否为相对路径。云存储对象路径请改用 manageStorage。"),
11971
- files: zod_1.z.array(zod_1.z.object({
11972
- localPath: zod_1.z.string(),
11973
- cloudPath: zod_1.z.string()
11974
- })).default([]).describe("多文件上传配置"),
11975
- ignore: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("忽略文件模式")
11976
- },
11977
- annotations: {
11978
- readOnlyHint: false,
11979
- destructiveHint: false,
11980
- idempotentHint: false,
11981
- openWorldHint: true,
11982
- category: "hosting"
11983
- }
11984
- }, async ({ localPath, cloudPath, files = [], ignore }) => {
11985
- const cloudbase = await getManager();
11986
- let result;
11987
- try {
11988
- result = await cloudbase.hosting.uploadFiles({
11989
- localPath,
11990
- cloudPath,
11991
- files,
11992
- ignore
11993
- });
11994
- }
11995
- catch (error) {
11996
- throw new Error(buildUploadFilesErrorMessage(error, localPath));
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;
11997
12723
  }
11998
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
11999
- const uploadResult = result;
12000
- // 获取环境信息
12001
- const envInfo = await cloudbase.env.getEnvInfo();
12002
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, envInfo);
12003
- const staticDomain = envInfo.EnvInfo?.StaticStorages?.[0]?.StaticDomain;
12004
- const accessUrl = buildHostingAccessUrl(staticDomain, cloudPath, localPath);
12005
- // Send deployment notification to CodeBuddy IDE
12006
- try {
12007
- const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
12008
- // Extract project name from localPath
12009
- let projectName = "unknown";
12010
- if (localPath) {
12011
- try {
12012
- // If localPath is a file, get parent directory name; if it's a directory, get directory name
12013
- const stats = fs_1.default.statSync(localPath);
12014
- if (stats.isFile()) {
12015
- projectName = path_1.default.basename(path_1.default.dirname(localPath));
12016
- }
12017
- else {
12018
- projectName = path_1.default.basename(localPath);
12019
- }
12020
- }
12021
- catch (statErr) {
12022
- // If stat fails, try to extract from path directly
12023
- projectName = path_1.default.basename(localPath);
12024
- }
12025
- }
12026
- // Build console URL
12027
- const consoleUrl = `https://tcb.cloud.tencent.com/dev?envId=${envId}#/static-hosting`;
12028
- // Send notification
12029
- await (0, notification_js_1.sendDeployNotification)(server, {
12030
- deployType: 'hosting',
12031
- url: accessUrl,
12032
- projectId: envId,
12033
- projectName: projectName,
12034
- consoleUrl: consoleUrl
12035
- });
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;
12036
12764
  }
12037
- catch (notifyErr) {
12038
- // Notification failure should not affect deployment flow
12039
- // Error is already logged in sendDeployNotification
12765
+ const value = getRecordString(candidate, ['Status', 'status', 'TaskStatus', 'State']);
12766
+ if (value) {
12767
+ return value;
12040
12768
  }
12041
- return {
12042
- content: [
12043
- {
12044
- type: "text",
12045
- text: JSON.stringify({
12046
- ...uploadResult,
12047
- staticDomain,
12048
- message: "文件上传成功",
12049
- accessUrl: accessUrl
12050
- }, null, 2)
12051
- }
12052
- ]
12053
- };
12054
- });
12055
- // deleteFiles - 删除静态网站托管文件
12056
- server.registerTool?.("deleteFiles", {
12057
- title: "删除静态文件",
12058
- description: "删除静态网站托管的文件或文件夹",
12059
- inputSchema: {
12060
- cloudPath: zod_1.z.string().describe("云端文件或文件夹路径"),
12061
- isDir: zod_1.z.boolean().default(false).describe("是否为文件夹")
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,
12062
12779
  },
12063
- annotations: {
12064
- readOnlyHint: false,
12065
- destructiveHint: true,
12066
- idempotentHint: true,
12067
- openWorldHint: true,
12068
- category: "hosting"
12069
- }
12070
- }, async ({ cloudPath, isDir = false }) => {
12071
- const cloudbase = await getManager();
12072
- const result = await cloudbase.hosting.deleteFiles({
12073
- cloudPath,
12074
- isDir
12075
- });
12076
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12077
- return {
12078
- content: [
12079
- {
12080
- type: "text",
12081
- text: JSON.stringify(result, null, 2)
12082
- }
12083
- ]
12084
- };
12085
- });
12086
- // findFiles - 搜索文件
12087
- server.registerTool?.("findFiles", {
12088
- title: "搜索静态文件",
12089
- description: "搜索静态网站托管的文件",
12090
- inputSchema: {
12091
- prefix: zod_1.z.string().describe("匹配前缀"),
12092
- marker: zod_1.z.string().optional().describe("起始对象键标记"),
12093
- maxKeys: zod_1.z.number().optional().describe("单次返回最大条目数")
12780
+ };
12781
+ }
12782
+ function buildStatusNextStep() {
12783
+ return {
12784
+ tool: 'queryHosting',
12785
+ action: 'status',
12786
+ suggested_args: {
12787
+ action: 'status',
12094
12788
  },
12095
- annotations: {
12096
- readOnlyHint: true,
12097
- openWorldHint: true,
12098
- category: "hosting"
12099
- }
12100
- }, async ({ prefix, marker, maxKeys }) => {
12101
- const cloudbase = await getManager();
12102
- const result = await cloudbase.hosting.findFiles({
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',
12103
12806
  prefix,
12104
- marker,
12105
- maxKeys
12106
- });
12107
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12108
- return {
12109
- content: [
12110
- {
12111
- type: "text",
12112
- text: JSON.stringify(result, null, 2)
12113
- }
12114
- ]
12115
- };
12116
- });
12117
- // domainManagement - 统一的域名管理工具
12118
- server.registerTool?.("domainManagement", {
12119
- title: "静态托管域名管理",
12120
- description: "统一的域名管理工具,支持绑定、解绑、查询和修改域名配置",
12121
- inputSchema: {
12122
- action: zod_1.z.enum(["create", "delete", "check", "modify"]).describe("操作类型: create=绑定域名, delete=解绑域名, check=查询域名配置, modify=修改域名配置"),
12123
- // 绑定域名参数
12124
- domain: zod_1.z.string().optional().describe("域名"),
12125
- certId: zod_1.z.string().optional().describe("证书ID(绑定域名时必需)"),
12126
- domains: zod_1.z.array(zod_1.z.string()).optional().describe("域名列表(查询配置时使用)"),
12127
- // 修改域名参数
12128
- domainId: zod_1.z.number().optional().describe("域名ID(修改配置时必需)"),
12129
- domainConfig: zod_1.z.object({
12130
- Refer: zod_1.z.object({
12131
- Switch: zod_1.z.string(),
12132
- RefererRules: zod_1.z.array(zod_1.z.object({
12133
- RefererType: zod_1.z.string(),
12134
- Referers: zod_1.z.array(zod_1.z.string()),
12135
- AllowEmpty: zod_1.z.boolean()
12136
- })).optional()
12137
- }).optional(),
12138
- Cache: zod_1.z.array(zod_1.z.object({
12139
- RuleType: zod_1.z.string(),
12140
- RuleValue: zod_1.z.string(),
12141
- CacheTtl: zod_1.z.number()
12142
- })).optional(),
12143
- IpFilter: zod_1.z.object({
12144
- Switch: zod_1.z.string(),
12145
- FilterType: zod_1.z.string().optional(),
12146
- Filters: zod_1.z.array(zod_1.z.string()).optional()
12147
- }).optional(),
12148
- IpFreqLimit: zod_1.z.object({
12149
- Switch: zod_1.z.string(),
12150
- Qps: zod_1.z.number().optional()
12151
- }).optional()
12152
- }).optional().describe("域名配置(修改配置时使用)")
12153
12807
  },
12154
- annotations: {
12155
- readOnlyHint: false,
12156
- destructiveHint: false,
12157
- idempotentHint: false,
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。`);
12946
+ }
12947
+ }
12948
+ function registerHostingTools(server) {
12949
+ const cloudBaseOptions = server.cloudBaseOptions;
12950
+ const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
12951
+ server.registerTool('queryHosting', {
12952
+ title: '查询静态托管',
12953
+ description: '查询 CloudBase 静态托管的只读信息。适合 AI 先做发现再决定下一步:action=websiteConfig 查询首页/错误页/路由规则与站点域名信息;action=status 查询托管服务状态;action=findFiles 按前缀查找文件;action=listFiles 列出全部托管文件;action=domainStatus 查询自定义域名的当前状态与配置。该工具不会产生任何副作用。',
12954
+ inputSchema: queryHostingInputSchema,
12955
+ annotations: {
12956
+ readOnlyHint: true,
12158
12957
  openWorldHint: true,
12159
- category: "hosting"
12160
- }
12161
- }, async ({ action, domain, certId, domains, domainId, domainConfig }) => {
12162
- const cloudbase = await getManager();
12163
- let result;
12164
- switch (action) {
12165
- case "create":
12166
- if (!domain || !certId) {
12167
- throw new Error("绑定域名需要提供域名和证书ID");
12958
+ category: 'hosting',
12959
+ },
12960
+ }, async (args) => {
12961
+ try {
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
+ });
12168
12975
  }
12169
- result = await cloudbase.hosting.CreateHostingDomain({
12170
- domain,
12171
- certId
12172
- });
12173
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12174
- break;
12175
- case "delete":
12176
- if (!domain) {
12177
- throw new Error("解绑域名需要提供域名");
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
+ });
12178
12995
  }
12179
- result = await cloudbase.hosting.deleteHostingDomain({
12180
- domain
12181
- });
12182
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12183
- break;
12184
- case "check":
12185
- if (!domains || domains.length === 0) {
12186
- throw new Error("查询域名配置需要提供域名列表");
12996
+ case 'findFiles': {
12997
+ if (!input.prefix) {
12998
+ throw new Error('queryHosting(action="findFiles") 需要提供 prefix,用于按前缀查找托管文件。');
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
+ });
12187
13019
  }
12188
- result = await cloudbase.hosting.tcbCheckResource({
12189
- domains
12190
- });
12191
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12192
- break;
12193
- case "modify":
12194
- if (!domain || domainId === undefined || !domainConfig) {
12195
- throw new Error("修改域名配置需要提供域名、域名ID和配置信息");
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
+ });
12196
13041
  }
12197
- result = await cloudbase.hosting.tcbModifyAttribute({
12198
- domain,
12199
- domainId,
12200
- domainConfig
12201
- });
12202
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
12203
- break;
12204
- default:
12205
- throw new Error(`不支持的操作类型: ${action}`);
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
+ });
13079
+ }
13080
+ }
12206
13081
  }
12207
- return {
12208
- content: [
12209
- {
12210
- type: "text",
12211
- text: JSON.stringify(result, null, 2)
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);
13088
+ }
13089
+ });
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,
13094
+ annotations: {
13095
+ readOnlyHint: false,
13096
+ destructiveHint: true,
13097
+ idempotentHint: false,
13098
+ openWorldHint: true,
13099
+ category: 'hosting',
13100
+ },
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
+ });
12212
13167
  }
12213
- ]
12214
- };
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
+ });
13211
+ }
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
+ });
13234
+ }
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
+ });
13248
+ }
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
+ }));
13266
+ }
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
+ }));
13285
+ }
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
+ }
12215
13355
  });
12216
13356
  }
12217
13357
 
@@ -12223,6 +13363,8 @@ function registerHostingTools(server) {
12223
13363
 
12224
13364
 
12225
13365
  Object.defineProperty(exports, "__esModule", ({ value: true }));
13366
+ exports.buildVerificationUriComplete = buildVerificationUriComplete;
13367
+ exports.buildDeviceAuthChallengePayload = buildDeviceAuthChallengePayload;
12226
13368
  exports.getAuthProgressStateSync = getAuthProgressStateSync;
12227
13369
  exports.getAuthProgressState = getAuthProgressState;
12228
13370
  exports.resolveAuthOptions = resolveAuthOptions;
@@ -12251,6 +13393,54 @@ function normalizeOptionalString(value) {
12251
13393
  const trimmed = value.trim();
12252
13394
  return trimmed.length > 0 ? trimmed : undefined;
12253
13395
  }
13396
+ function buildVerificationUriComplete(deviceAuthInfo) {
13397
+ const explicitComplete = normalizeOptionalString(deviceAuthInfo?.verification_uri_complete);
13398
+ if (explicitComplete) {
13399
+ return explicitComplete;
13400
+ }
13401
+ const verificationUri = normalizeOptionalString(deviceAuthInfo?.verification_uri);
13402
+ const userCode = normalizeOptionalString(deviceAuthInfo?.user_code);
13403
+ if (!verificationUri || !userCode) {
13404
+ return undefined;
13405
+ }
13406
+ try {
13407
+ const url = new URL(verificationUri);
13408
+ const hash = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash;
13409
+ if (hash) {
13410
+ const [hashPath, hashQuery = ""] = hash.split("?");
13411
+ const hashParams = new URLSearchParams(hashQuery);
13412
+ if (!hashParams.has("user_code")) {
13413
+ hashParams.set("user_code", userCode);
13414
+ }
13415
+ url.hash = hashParams.toString()
13416
+ ? `#${hashPath}?${hashParams.toString()}`
13417
+ : `#${hashPath}`;
13418
+ return url.toString();
13419
+ }
13420
+ if (!url.searchParams.has("user_code")) {
13421
+ url.searchParams.set("user_code", userCode);
13422
+ }
13423
+ return url.toString();
13424
+ }
13425
+ catch {
13426
+ if (verificationUri.includes("user_code=")) {
13427
+ return verificationUri;
13428
+ }
13429
+ const separator = verificationUri.includes("?") ? "&" : "?";
13430
+ return `${verificationUri}${separator}user_code=${encodeURIComponent(userCode)}`;
13431
+ }
13432
+ }
13433
+ function buildDeviceAuthChallengePayload(deviceAuthInfo) {
13434
+ if (!deviceAuthInfo) {
13435
+ return undefined;
13436
+ }
13437
+ return {
13438
+ user_code: deviceAuthInfo.user_code,
13439
+ verification_uri: deviceAuthInfo.verification_uri,
13440
+ verification_uri_complete: buildVerificationUriComplete(deviceAuthInfo),
13441
+ expires_in: deviceAuthInfo.expires_in,
13442
+ };
13443
+ }
12254
13444
  function normalizeAuthMode(value) {
12255
13445
  return value === "web" || value === "device" ? value : undefined;
12256
13446
  }
@@ -12583,15 +13773,14 @@ const fs = __importStar(__webpack_require__(4421));
12583
13773
  const fsPromises = __importStar(__webpack_require__(7932));
12584
13774
  const http = __importStar(__webpack_require__(3782));
12585
13775
  const https = __importStar(__webpack_require__(3871));
12586
- const net = __importStar(__webpack_require__(4357));
12587
13776
  const os = __importStar(__webpack_require__(8116));
12588
13777
  const path = __importStar(__webpack_require__(2521));
12589
13778
  const url_1 = __webpack_require__(6887);
12590
13779
  const zod_1 = __webpack_require__(2971);
12591
- const dns = __importStar(__webpack_require__(7793));
13780
+ const remote_url_safety_js_1 = __webpack_require__(9192);
12592
13781
  // 常量定义
12593
13782
  const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
12594
- const ALLOWED_PROTOCOLS = ["http:", "https:"];
13783
+ const MAX_REDIRECT_HOPS = 5;
12595
13784
  const ALLOWED_CONTENT_TYPES = [
12596
13785
  "text/",
12597
13786
  "image/",
@@ -12640,65 +13829,6 @@ function calculateDownloadPath(relativePath) {
12640
13829
  }
12641
13830
  return finalPath;
12642
13831
  }
12643
- // 检查是否为内网 IP
12644
- function isPrivateIP(ip) {
12645
- // 如果不是有效的 IP 地址,返回 true(保守处理)
12646
- if (!net.isIP(ip)) {
12647
- return true;
12648
- }
12649
- // 检查特殊地址
12650
- if (ip === '127.0.0.1' ||
12651
- ip === 'localhost' ||
12652
- ip === '::1' || // IPv6 本地回环
12653
- ip.startsWith('169.254.') || // 链路本地地址
12654
- ip.startsWith('0.')) { // 特殊用途地址
12655
- return true;
12656
- }
12657
- // 转换 IP 地址为长整数进行范围检查
12658
- const ipv4Parts = ip.split('.').map(part => parseInt(part, 10));
12659
- if (ipv4Parts.length === 4) {
12660
- const ipNum = ((ipv4Parts[0] << 24) >>> 0) + (ipv4Parts[1] << 16) + (ipv4Parts[2] << 8) + ipv4Parts[3];
12661
- // 检查私有 IP 范围
12662
- // 10.0.0.0 - 10.255.255.255
12663
- if (ipNum >= 167772160 && ipNum <= 184549375)
12664
- return true;
12665
- // 172.16.0.0 - 172.31.255.255
12666
- if (ipNum >= 2886729728 && ipNum <= 2887778303)
12667
- return true;
12668
- // 192.168.0.0 - 192.168.255.255
12669
- if (ipNum >= 3232235520 && ipNum <= 3232301055)
12670
- return true;
12671
- }
12672
- // 检查 IPv6 私有地址
12673
- if (net.isIPv6(ip)) {
12674
- const normalizedIP = ip.toLowerCase();
12675
- if (normalizedIP.startsWith('fc00:') || // 唯一本地地址
12676
- normalizedIP.startsWith('fe80:') || // 链路本地地址
12677
- normalizedIP.startsWith('fec0:') || // 站点本地地址
12678
- normalizedIP.startsWith('::1')) { // 本地回环
12679
- return true;
12680
- }
12681
- }
12682
- return false;
12683
- }
12684
- // 检查域名是否解析到内网 IP
12685
- async function doesDomainResolveToPrivateIP(hostname) {
12686
- try {
12687
- const addresses = await new Promise((resolve, reject) => {
12688
- dns.resolve(hostname, (err, addresses) => {
12689
- if (err)
12690
- reject(err);
12691
- else
12692
- resolve(addresses);
12693
- });
12694
- });
12695
- return addresses.some(ip => isPrivateIP(ip));
12696
- }
12697
- catch (error) {
12698
- // 如果解析失败,为安全起见返回 true
12699
- return true;
12700
- }
12701
- }
12702
13832
  // 生成随机文件名
12703
13833
  function generateRandomFileName(extension = '') {
12704
13834
  const randomBytes = crypto.randomBytes(16);
@@ -12749,45 +13879,52 @@ function getFileExtension(url, contentType, contentDisposition) {
12749
13879
  }
12750
13880
  return extension;
12751
13881
  }
12752
- // 验证 URL 和内容类型是否安全
12753
- async function isUrlAndContentTypeSafe(url, contentType) {
12754
- try {
12755
- const parsedUrl = new url_1.URL(url);
12756
- // 检查协议
12757
- if (!ALLOWED_PROTOCOLS.includes(parsedUrl.protocol)) {
12758
- return false;
12759
- }
12760
- // 检查主机名是否为 IP 地址
12761
- const hostname = parsedUrl.hostname;
12762
- if (net.isIP(hostname) && isPrivateIP(hostname)) {
12763
- return false;
12764
- }
12765
- // 如果是域名,检查它是否解析到内网 IP
12766
- if (!net.isIP(hostname) && await doesDomainResolveToPrivateIP(hostname)) {
12767
- return false;
12768
- }
12769
- // 检查内容类型
12770
- return ALLOWED_CONTENT_TYPES.some(allowedType => contentType.startsWith(allowedType));
12771
- }
12772
- catch {
12773
- return false;
12774
- }
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 || '');
12775
13898
  }
12776
- // 下载文件到指定路径
12777
- 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;
12778
13908
  return new Promise((resolve, reject) => {
12779
- const client = url.startsWith('https:') ? https : http;
12780
- 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
+ }
12781
13920
  if (res.statusCode !== 200) {
12782
13921
  reject(new Error(`HTTP Error: ${res.statusCode}`));
12783
13922
  return;
12784
13923
  }
12785
- const contentType = res.headers['content-type'] || '';
12786
- const contentLength = parseInt(res.headers['content-length'] || '0', 10);
12787
- const contentDisposition = res.headers['content-disposition'];
12788
- // 安全检查
12789
- if (!await isUrlAndContentTypeSafe(url, contentType)) {
12790
- 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"}`));
12791
13928
  return;
12792
13929
  }
12793
13930
  // 文件大小检查
@@ -12801,13 +13938,13 @@ function downloadFileToPath(url, targetPath) {
12801
13938
  await fsPromises.mkdir(targetDir, { recursive: true });
12802
13939
  }
12803
13940
  catch (error) {
12804
- reject(new Error(`无法创建目标目录: ${error instanceof Error ? error.message : '未知错误'}`));
13941
+ reject(new Error(`无法创建目标目录: ${error instanceof Error ? error.message : "未知错误"}`));
12805
13942
  return;
12806
13943
  }
12807
13944
  // 创建写入流
12808
13945
  const fileStream = fs.createWriteStream(targetPath);
12809
13946
  let downloadedSize = 0;
12810
- res.on('data', (chunk) => {
13947
+ res.on("data", (chunk) => {
12811
13948
  downloadedSize += chunk.length;
12812
13949
  if (downloadedSize > MAX_FILE_SIZE) {
12813
13950
  fileStream.destroy();
@@ -12816,37 +13953,65 @@ function downloadFileToPath(url, targetPath) {
12816
13953
  }
12817
13954
  });
12818
13955
  res.pipe(fileStream);
12819
- fileStream.on('finish', () => {
13956
+ fileStream.on("finish", () => {
12820
13957
  resolve({
12821
13958
  filePath: targetPath,
12822
13959
  contentType,
12823
- fileSize: downloadedSize
13960
+ fileSize: downloadedSize,
12824
13961
  });
12825
13962
  });
12826
- fileStream.on('error', (error) => {
13963
+ fileStream.on("error", (error) => {
12827
13964
  fsPromises.unlink(targetPath).catch(() => { });
12828
13965
  reject(error);
12829
13966
  });
12830
- }).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
+ }
12831
13981
  reject(error);
12832
13982
  });
12833
13983
  });
12834
13984
  }
12835
- // 下载文件到临时目录(保持向后兼容)
12836
- 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;
12837
13994
  return new Promise((resolve, reject) => {
12838
- const client = url.startsWith('https:') ? https : http;
12839
- 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
+ }
12840
14006
  if (res.statusCode !== 200) {
12841
14007
  reject(new Error(`HTTP Error: ${res.statusCode}`));
12842
14008
  return;
12843
14009
  }
12844
- const contentType = res.headers['content-type'] || '';
12845
- const contentLength = parseInt(res.headers['content-length'] || '0', 10);
12846
- const contentDisposition = res.headers['content-disposition'];
12847
- // 安全检查
12848
- if (!await isUrlAndContentTypeSafe(url, contentType)) {
12849
- 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"}`));
12850
14015
  return;
12851
14016
  }
12852
14017
  // 文件大小检查
@@ -12855,13 +14020,13 @@ function downloadFile(url) {
12855
14020
  return;
12856
14021
  }
12857
14022
  // 生成临时文件路径
12858
- const extension = getFileExtension(url, contentType, contentDisposition);
14023
+ const extension = getFileExtension(requestUrl.toString(), contentType, contentDisposition);
12859
14024
  const fileName = generateRandomFileName(extension);
12860
14025
  const filePath = getSafeTempFilePath(fileName);
12861
14026
  // 创建写入流
12862
14027
  const fileStream = fs.createWriteStream(filePath);
12863
14028
  let downloadedSize = 0;
12864
- res.on('data', (chunk) => {
14029
+ res.on("data", (chunk) => {
12865
14030
  downloadedSize += chunk.length;
12866
14031
  if (downloadedSize > MAX_FILE_SIZE) {
12867
14032
  fileStream.destroy();
@@ -12870,18 +14035,31 @@ function downloadFile(url) {
12870
14035
  }
12871
14036
  });
12872
14037
  res.pipe(fileStream);
12873
- fileStream.on('finish', () => {
14038
+ fileStream.on("finish", () => {
12874
14039
  resolve({
12875
14040
  filePath,
12876
14041
  contentType,
12877
- fileSize: downloadedSize
14042
+ fileSize: downloadedSize,
12878
14043
  });
12879
14044
  });
12880
- fileStream.on('error', (error) => {
14045
+ fileStream.on("error", (error) => {
12881
14046
  fsPromises.unlink(filePath).catch(() => { });
12882
14047
  reject(error);
12883
14048
  });
12884
- }).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
+ }
12885
14063
  reject(error);
12886
14064
  });
12887
14065
  });
@@ -12894,7 +14072,8 @@ function buildDownloadRemoteFileErrorMessage(url, error) {
12894
14072
  suggestions.push("请先在浏览器中确认该链接可访问,再重新执行下载。");
12895
14073
  }
12896
14074
  if (/ENOTFOUND|ECONNRESET|ETIMEDOUT|socket hang up/i.test(baseMessage)) {
12897
- suggestions.push("下载过程中出现网络异常,建议检查本地网络或代理后重试。");
14075
+ suggestions.push("下载过程中出现网络异常,系统已自动重试 3 次但均失败。");
14076
+ suggestions.push("建议:1) 检查本地网络或代理设置;2) 该网站可能限频,请尝试更换其他图片源(如 Unsplash、Pexels);3) 稍后重试。");
12898
14077
  }
12899
14078
  if (suggestions.length === 0) {
12900
14079
  suggestions.push("请检查 URL 是否可访问,以及目标资源是否允许直接下载。");
@@ -12965,6 +14144,191 @@ function registerDownloadTools(server) {
12965
14144
  }
12966
14145
 
12967
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
+
12968
14332
  /***/ }),
12969
14333
 
12970
14334
  /***/ 9322:
@@ -13230,8 +14594,7 @@ function shouldRegisterTool(toolName) {
13230
14594
  'auth',
13231
14595
  // Storage tools - local file uploads
13232
14596
  'uploadFile',
13233
- // Hosting tools - local file uploads
13234
- 'uploadFiles',
14597
+ // Hosting tools - action-level cloud gating is handled inside manageHosting
13235
14598
  // Function tools - local code uploads
13236
14599
  'updateFunctionCode',
13237
14600
  'createFunction', // also involves local files