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