@cloudbase/cloudbase-mcp 2.24.0 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -515
- package/README.zh-CN.md +175 -0
- package/dist/cli.cjs +3 -3
- package/dist/index.cjs +1545 -501
- package/dist/index.d.ts +9 -0
- package/dist/index.js +431 -298
- package/package.json +13 -6
package/dist/index.js
CHANGED
|
@@ -30,6 +30,8 @@ import * as __WEBPACK_EXTERNAL_MODULE_winston__ from "winston";
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
33
|
+
exports.DEFAULT_CREATE_ENV_RESOURCES = exports.CREATE_ENV_RESOURCE_VALUES = void 0;
|
|
34
|
+
exports.resolveCreateEnvResources = resolveCreateEnvResources;
|
|
33
35
|
exports.simplifyEnvList = simplifyEnvList;
|
|
34
36
|
exports.registerEnvTools = registerEnvTools;
|
|
35
37
|
const toolbox_1 = __webpack_require__(2090);
|
|
@@ -39,6 +41,26 @@ const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
|
39
41
|
const logger_js_1 = __webpack_require__(3039);
|
|
40
42
|
const tool_result_js_1 = __webpack_require__(9835);
|
|
41
43
|
const env_setup_js_1 = __webpack_require__(6978);
|
|
44
|
+
/**
|
|
45
|
+
* Resources accepted by CreateEnv. Matches Cloud API / Manager SDK contract.
|
|
46
|
+
* `postgresql` enables CloudBase PostgreSQL (PG mode) when supported by the package.
|
|
47
|
+
*/
|
|
48
|
+
exports.CREATE_ENV_RESOURCE_VALUES = [
|
|
49
|
+
"flexdb",
|
|
50
|
+
"storage",
|
|
51
|
+
"function",
|
|
52
|
+
"postgresql",
|
|
53
|
+
];
|
|
54
|
+
/** Default Resources when callers omit the field (schema promises "all four"). */
|
|
55
|
+
exports.DEFAULT_CREATE_ENV_RESOURCES = [
|
|
56
|
+
...exports.CREATE_ENV_RESOURCE_VALUES,
|
|
57
|
+
];
|
|
58
|
+
function resolveCreateEnvResources(resources) {
|
|
59
|
+
if (Array.isArray(resources) && resources.length > 0) {
|
|
60
|
+
return [...resources];
|
|
61
|
+
}
|
|
62
|
+
return [...exports.DEFAULT_CREATE_ENV_RESOURCES];
|
|
63
|
+
}
|
|
42
64
|
/**
|
|
43
65
|
* Simplify environment list data by keeping only essential fields for AI assistant
|
|
44
66
|
* This reduces token consumption when returning environment lists via MCP tools
|
|
@@ -662,8 +684,8 @@ async function enrichEnvInfoWithRuntimeMode(result, manager) {
|
|
|
662
684
|
: "No legacy NoSQL Database/Storage observed in this env.",
|
|
663
685
|
MysqlNotAvailable: hasMysql
|
|
664
686
|
? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
|
|
665
|
-
: "No MySQL instance is provisioned for this env. Do NOT use `manageMysqlDatabase` / `queryMysqlDatabase` (those are MySQL-specific) and do NOT read the `relational-database-
|
|
666
|
-
RecommendedSkills: "Read `postgresql-development` first for new PG code. `
|
|
687
|
+
: "No MySQL instance is provisioned for this env. Do NOT use `manageMysqlDatabase` / `queryMysqlDatabase` (those are MySQL-specific) and do NOT read the `relational-database-mcp-cloudbase` skill — that family targets MySQL, not CloudBase PG.",
|
|
688
|
+
RecommendedSkills: "Read `postgresql-development-cloudbase` first for new PG code. `cloudbase-document-database-web-sdk` is still applicable for existing NoSQL collections in this env. Skip `relational-database-mcp-cloudbase` (MySQL-only) entirely.",
|
|
667
689
|
}
|
|
668
690
|
: {
|
|
669
691
|
PrimaryBackend: "PostgreSQL is NOT provisioned in this env — this is a legacy NoSQL CloudBase backend.",
|
|
@@ -672,8 +694,8 @@ async function enrichEnvInfoWithRuntimeMode(result, manager) {
|
|
|
672
694
|
Storage: "Browser uploads use `app.uploadFile()` against the bucket exposed in `EnvInfo.Storages[].Bucket`.",
|
|
673
695
|
MysqlNotAvailable: hasMysql
|
|
674
696
|
? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
|
|
675
|
-
: "No MySQL instance in this env. `manageMysqlDatabase` / `queryMysqlDatabase` and the `relational-database-
|
|
676
|
-
RecommendedSkills: "Read `
|
|
697
|
+
: "No MySQL instance in this env. `manageMysqlDatabase` / `queryMysqlDatabase` and the `relational-database-mcp-cloudbase` skill are not applicable.",
|
|
698
|
+
RecommendedSkills: "Read `cloudbase-document-database-web-sdk` (and `cloud-storage-web` for uploads). `postgresql-development-cloudbase` and `relational-database-mcp-cloudbase` are not applicable to this env.",
|
|
677
699
|
};
|
|
678
700
|
return {
|
|
679
701
|
...result,
|
|
@@ -804,7 +826,13 @@ function resolveToolAuthOptions(server, overrides) {
|
|
|
804
826
|
function registerEnvTools(server) {
|
|
805
827
|
// 获取 cloudBaseOptions,如果没有则为 undefined
|
|
806
828
|
const cloudBaseOptions = server.cloudBaseOptions;
|
|
807
|
-
|
|
829
|
+
// Default: env-scoped tools need a bound envId (domains / domain management use SDK this.envId).
|
|
830
|
+
// manageEnv create/listPackages are account-level — use requireEnvId: false there only.
|
|
831
|
+
const getManager = (options) => (0, cloudbase_manager_js_1.getCloudBaseManager)({
|
|
832
|
+
cloudBaseOptions,
|
|
833
|
+
requireEnvId: options?.requireEnvId ?? true,
|
|
834
|
+
mcpServer: server,
|
|
835
|
+
});
|
|
808
836
|
const getManagerForEnvQuery = (targetEnvId, requireEnvId = true) => (0, cloudbase_manager_js_1.getCloudBaseManager)({
|
|
809
837
|
cloudBaseOptions: targetEnvId && targetEnvId !== cloudBaseOptions?.envId
|
|
810
838
|
? {
|
|
@@ -1588,7 +1616,7 @@ function registerEnvTools(server) {
|
|
|
1588
1616
|
// Register primary tool name (queryEnv)
|
|
1589
1617
|
const queryEnvToolSchema = {
|
|
1590
1618
|
title: "CloudBase 环境查询",
|
|
1591
|
-
description: "查询 CloudBase 环境相关信息,支持查询环境列表、指定环境详情和安全域名。(曾用名:envQuery、listEnvs、getEnvInfo、getEnvAuthDomains)当 action=list 时,会按 DescribeEnvs 语义做列表/筛选,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知 EnvId 对应环境的详细信息(包括资源字段和计费信息),必须使用 action=info 并传入目标环境的 envId 参数。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。\n\n🔍 action=info 还会派生三个用于后端选型的字段:\n- `EnvInfo.RuntimeMode`:'postgresql' 或 'nosql',表示新业务建议默认使用的后端(PG 已开通时为 postgresql,否则为 nosql)。\n- `EnvInfo.RuntimeBackends`:`{postgresql, nosql, mysql}` 三个布尔值,描述当前环境实际并存的后端。\n- `EnvInfo.RuntimeModeHints`:每个后端对应的 API/工具/skill 提示。\n\nAI 在写业务/权限/存储代码前必须先看这三项:PG 模式下新业务推荐 `app.rdb()` + RLS(`managePgDatabase action=execute` 跑 `CREATE POLICY`)+ pgstore;已存在的 NoSQL 集合 / 旧 storage / `managePermissions(resourceType=\"noSqlDatabase\")` 在 PG 环境下仍然有效。真正不适用的是 MySQL:当 `RuntimeBackends.mysql === false` 时,`manageMysqlDatabase` / `queryMysqlDatabase` / `relational-database-
|
|
1619
|
+
description: "查询 CloudBase 环境相关信息,支持查询环境列表、指定环境详情和安全域名。(曾用名:envQuery、listEnvs、getEnvInfo、getEnvAuthDomains)当 action=list 时,会按 DescribeEnvs 语义做列表/筛选,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知 EnvId 对应环境的详细信息(包括资源字段和计费信息),必须使用 action=info 并传入目标环境的 envId 参数。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。\n\n🔍 action=info 还会派生三个用于后端选型的字段:\n- `EnvInfo.RuntimeMode`:'postgresql' 或 'nosql',表示新业务建议默认使用的后端(PG 已开通时为 postgresql,否则为 nosql)。\n- `EnvInfo.RuntimeBackends`:`{postgresql, nosql, mysql}` 三个布尔值,描述当前环境实际并存的后端。\n- `EnvInfo.RuntimeModeHints`:每个后端对应的 API/工具/skill 提示。\n\nAI 在写业务/权限/存储代码前必须先看这三项:PG 模式下新业务推荐 `app.rdb()` + RLS(`managePgDatabase action=execute` 跑 `CREATE POLICY`)+ pgstore;已存在的 NoSQL 集合 / 旧 storage / `managePermissions(resourceType=\"noSqlDatabase\")` 在 PG 环境下仍然有效。真正不适用的是 MySQL:当 `RuntimeBackends.mysql === false` 时,`manageMysqlDatabase` / `queryMysqlDatabase` / `relational-database-mcp-cloudbase` skill 都不该使用。",
|
|
1592
1620
|
inputSchema: {
|
|
1593
1621
|
action: zod_1.z
|
|
1594
1622
|
.enum(["list", "info", "domains"])
|
|
@@ -1685,14 +1713,10 @@ function registerEnvTools(server) {
|
|
|
1685
1713
|
.string()
|
|
1686
1714
|
.optional()
|
|
1687
1715
|
.describe("套餐 ID(action=create/modifyPlan 时必填)。可选值如 baas_personal(个人版)、baas_pf_standard(标准版)、baas_pf_enterprise(企业版)"),
|
|
1688
|
-
region: zod_1.z
|
|
1689
|
-
.string()
|
|
1690
|
-
.optional()
|
|
1691
|
-
.describe("环境地域(action=create 时可选)。默认 ap-shanghai,可选 ap-guangzhou、ap-beijing 等"),
|
|
1692
1716
|
resources: zod_1.z
|
|
1693
|
-
.array(zod_1.z.enum(
|
|
1717
|
+
.array(zod_1.z.enum(exports.CREATE_ENV_RESOURCE_VALUES))
|
|
1694
1718
|
.optional()
|
|
1695
|
-
.describe("启用的资源类型(action=create
|
|
1719
|
+
.describe("启用的资源类型(action=create 时可选)。省略时默认全部四项:flexdb(文档数据库)、storage(存储)、function(云函数)、postgresql(PostgreSQL)。CreateEnv 要求 Resources 非空,MCP 会始终下发该字段。"),
|
|
1696
1720
|
duration: zod_1.z
|
|
1697
1721
|
.number()
|
|
1698
1722
|
.int()
|
|
@@ -1720,17 +1744,19 @@ function registerEnvTools(server) {
|
|
|
1720
1744
|
const action = rawArgs.action ?? "";
|
|
1721
1745
|
const alias = normalizeOptionalToolString(rawArgs.alias);
|
|
1722
1746
|
const packageId = normalizeOptionalToolString(rawArgs.packageId);
|
|
1723
|
-
const
|
|
1724
|
-
const resources = rawArgs.resources;
|
|
1747
|
+
const resolvedResources = resolveCreateEnvResources(rawArgs.resources);
|
|
1725
1748
|
const duration = rawArgs.duration ?? 1;
|
|
1726
1749
|
const envId = normalizeOptionalToolString(rawArgs.envId);
|
|
1727
1750
|
const confirmed = rawArgs.confirm === "yes";
|
|
1728
1751
|
try {
|
|
1729
|
-
const cloudbase = await getManager();
|
|
1752
|
+
const cloudbase = await getManager({ requireEnvId: false });
|
|
1730
1753
|
switch (action) {
|
|
1731
1754
|
case "listPackages": {
|
|
1732
|
-
//
|
|
1733
|
-
const result = await cloudbase.env.describeBaasPackageList({
|
|
1755
|
+
// Query packages for new purchase. Source aligns with billing-side Baas package APIs.
|
|
1756
|
+
const result = await cloudbase.env.describeBaasPackageList({
|
|
1757
|
+
TargetAction: "new",
|
|
1758
|
+
Source: "qcloud",
|
|
1759
|
+
});
|
|
1734
1760
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
1735
1761
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1736
1762
|
ok: true,
|
|
@@ -1740,12 +1766,13 @@ function registerEnvTools(server) {
|
|
|
1740
1766
|
});
|
|
1741
1767
|
}
|
|
1742
1768
|
case "create": {
|
|
1743
|
-
//
|
|
1769
|
+
// CreateEnv (Manager SDK / Cloud API) accepts Alias, PackageId, Resources, Period, etc.
|
|
1770
|
+
// It does NOT accept Region — region is determined by account/package, not this call.
|
|
1744
1771
|
if (!confirmed) {
|
|
1745
1772
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1746
1773
|
ok: false,
|
|
1747
1774
|
code: "CONFIRM_REQUIRED",
|
|
1748
|
-
message: `创建环境需要您确认。请确认以下配置信息后传入 confirm="yes":\n别名: ${alias}\n套餐: ${packageId}\n
|
|
1775
|
+
message: `创建环境需要您确认。请确认以下配置信息后传入 confirm="yes":\n别名: ${alias}\n套餐: ${packageId}\n资源类型: ${resolvedResources.join(", ")}\n时长: ${duration} 个月\n说明: CreateEnv 不接受 Region;环境地域由账号与套餐侧决定。`,
|
|
1749
1776
|
next_step: {
|
|
1750
1777
|
tool: "manageEnv",
|
|
1751
1778
|
action: "create",
|
|
@@ -1762,12 +1789,9 @@ function registerEnvTools(server) {
|
|
|
1762
1789
|
const createParams = {
|
|
1763
1790
|
Alias: alias,
|
|
1764
1791
|
PackageId: packageId,
|
|
1765
|
-
|
|
1792
|
+
Resources: resolvedResources,
|
|
1766
1793
|
Period: duration,
|
|
1767
1794
|
};
|
|
1768
|
-
if (resources && resources.length > 0) {
|
|
1769
|
-
createParams.Resources = resources;
|
|
1770
|
-
}
|
|
1771
1795
|
const result = await cloudbase.env.createEnv(createParams);
|
|
1772
1796
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
1773
1797
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
@@ -1775,6 +1799,7 @@ function registerEnvTools(server) {
|
|
|
1775
1799
|
code: "ENV_CREATED",
|
|
1776
1800
|
message: `环境创建成功!新环境 ID: ${result.EnvId}。环境初始化可能需要几分钟,请通过 queryEnv(action="info", envId="${result.EnvId}") 轮询直到 Status 为正常。`,
|
|
1777
1801
|
envId: result.EnvId,
|
|
1802
|
+
resources: resolvedResources,
|
|
1778
1803
|
});
|
|
1779
1804
|
}
|
|
1780
1805
|
case "modifyPlan": {
|
|
@@ -1852,11 +1877,19 @@ function registerEnvTools(server) {
|
|
|
1852
1877
|
if (toolPayloadResult) {
|
|
1853
1878
|
return toolPayloadResult;
|
|
1854
1879
|
}
|
|
1880
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1881
|
+
if (action === "listPackages" && /billTags/i.test(errorMessage)) {
|
|
1882
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1883
|
+
ok: false,
|
|
1884
|
+
code: "PACKAGE_LIST_FAILED",
|
|
1885
|
+
message: `查询套餐列表失败(计费标签 BillTags):${errorMessage}。可改用控制台查看套餐,或直接使用已知 packageId(如 baas_personal)调用 manageEnv(action="create")。`,
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1855
1888
|
return {
|
|
1856
1889
|
content: [
|
|
1857
1890
|
{
|
|
1858
1891
|
type: "text",
|
|
1859
|
-
text: `环境管理操作失败: ${
|
|
1892
|
+
text: `环境管理操作失败: ${errorMessage}`,
|
|
1860
1893
|
},
|
|
1861
1894
|
],
|
|
1862
1895
|
};
|
|
@@ -2200,7 +2233,7 @@ ${envIdSection}
|
|
|
2200
2233
|
## 环境信息
|
|
2201
2234
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
2202
2235
|
- Node.js版本: ${process.version}
|
|
2203
|
-
- MCP 版本:${process.env.npm_package_version || "2.
|
|
2236
|
+
- MCP 版本:${process.env.npm_package_version || "2.25.0" || 0}
|
|
2204
2237
|
- 系统架构: ${os_1.default.arch()}
|
|
2205
2238
|
- 时间: ${new Date().toISOString()}
|
|
2206
2239
|
- 请求ID: ${requestId}
|
|
@@ -2281,7 +2314,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
2281
2314
|
requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
|
|
2282
2315
|
ide: server.ide || process.env.INTEGRATION_IDE || ''
|
|
2283
2316
|
});
|
|
2284
|
-
const mcpVersion = true ? "2.
|
|
2317
|
+
const mcpVersion = true ? "2.25.0" : 0;
|
|
2285
2318
|
const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
|
|
2286
2319
|
const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
|
|
2287
2320
|
// 创建新的错误对象,保持原有的错误类型但更新消息
|
|
@@ -2318,7 +2351,8 @@ function createWrappedHandler(name, handler, server) {
|
|
|
2318
2351
|
error: errorMessage,
|
|
2319
2352
|
inputParams: sanitizeArgs(args), // 添加入参上报
|
|
2320
2353
|
cloudBaseOptions: cloudBaseOptions, // 传递 CloudBase 配置(可能已更新)
|
|
2321
|
-
ide: server.ide || process.env.INTEGRATION_IDE // 传递集成IDE信息
|
|
2354
|
+
ide: server.ide || process.env.INTEGRATION_IDE, // 传递集成IDE信息
|
|
2355
|
+
mcpClientInfo: (0, telemetry_js_1.readMcpClientInfoFromServer)(server),
|
|
2322
2356
|
}).catch(err => {
|
|
2323
2357
|
// 静默处理上报错误,不影响主要功能
|
|
2324
2358
|
(0, logger_js_1.debug)('遥测上报失败', { toolName: name, error: err instanceof Error ? err.message : String(err) });
|
|
@@ -7119,21 +7153,23 @@ const zod_1 = __webpack_require__(2971);
|
|
|
7119
7153
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
7120
7154
|
const json_content_js_1 = __webpack_require__(4365);
|
|
7121
7155
|
const QUERY_GATEWAY_ACTIONS = [
|
|
7122
|
-
"getAccess",
|
|
7123
|
-
"listDomains",
|
|
7124
7156
|
"listRoutes",
|
|
7125
7157
|
"getRoute",
|
|
7126
7158
|
"listCustomDomains",
|
|
7127
7159
|
];
|
|
7128
7160
|
const MANAGE_GATEWAY_ACTIONS = [
|
|
7129
|
-
"createAccess",
|
|
7130
7161
|
"createRoute",
|
|
7131
7162
|
"updateRoute",
|
|
7132
7163
|
"deleteRoute",
|
|
7133
7164
|
"bindCustomDomain",
|
|
7134
7165
|
"deleteCustomDomain",
|
|
7135
|
-
|
|
7136
|
-
|
|
7166
|
+
];
|
|
7167
|
+
const UPSTREAM_RESOURCE_TYPES = [
|
|
7168
|
+
"SCF",
|
|
7169
|
+
"WEB_SCF",
|
|
7170
|
+
"CBR",
|
|
7171
|
+
"STATIC_STORE",
|
|
7172
|
+
"LH",
|
|
7137
7173
|
];
|
|
7138
7174
|
function normalizeAccessPath(path) {
|
|
7139
7175
|
if (!path) {
|
|
@@ -7164,24 +7200,11 @@ function registerGatewayTools(server) {
|
|
|
7164
7200
|
return (0, json_content_js_1.jsonContent)(buildErrorEnvelope(error));
|
|
7165
7201
|
}
|
|
7166
7202
|
};
|
|
7167
|
-
const listGatewayDomains = async () => {
|
|
7168
|
-
const cloudbase = await getManager();
|
|
7169
|
-
const result = await cloudbase.access.getDomainList();
|
|
7170
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7171
|
-
const domains = [
|
|
7172
|
-
result.DefaultDomain,
|
|
7173
|
-
...(result.ServiceSet || []).map((item) => item.Domain),
|
|
7174
|
-
].filter(Boolean);
|
|
7175
|
-
return {
|
|
7176
|
-
domains,
|
|
7177
|
-
enableService: result.EnableService,
|
|
7178
|
-
raw: result,
|
|
7179
|
-
};
|
|
7180
|
-
};
|
|
7181
7203
|
const listHttpServiceRoutes = async (domain) => {
|
|
7182
7204
|
const cloudbase = await getManager();
|
|
7183
7205
|
const result = await cloudbase.env.describeHttpServiceRoute({
|
|
7184
7206
|
EnvId: await resolveEnvId(),
|
|
7207
|
+
Limit: 1000,
|
|
7185
7208
|
...(domain
|
|
7186
7209
|
? {
|
|
7187
7210
|
Filters: [
|
|
@@ -7197,123 +7220,180 @@ function registerGatewayTools(server) {
|
|
|
7197
7220
|
return result;
|
|
7198
7221
|
};
|
|
7199
7222
|
const flattenRoutes = (result) => (result.Domains ?? []).flatMap((domainItem) => (domainItem.Routes ?? []).map((route) => ({
|
|
7200
|
-
Domain: domainItem.Domain,
|
|
7223
|
+
Domain: domainItem.Domain ?? "",
|
|
7201
7224
|
DomainType: domainItem.DomainType,
|
|
7202
7225
|
AccessType: domainItem.AccessType,
|
|
7226
|
+
IsDefault: domainItem.IsDefault,
|
|
7203
7227
|
...route,
|
|
7204
7228
|
})));
|
|
7229
|
+
const buildRouteUrls = (routes) => Array.from(new Set(routes
|
|
7230
|
+
.filter((route) => route.Domain && route.Path)
|
|
7231
|
+
.map((route) => `https://${route.Domain}${normalizeAccessPath(String(route.Path))}`)));
|
|
7232
|
+
const resolveDefaultHttpDomain = async () => {
|
|
7233
|
+
const routeInfo = await listHttpServiceRoutes();
|
|
7234
|
+
const domains = routeInfo.Domains ?? [];
|
|
7235
|
+
const defaultCandidates = domains.filter((item) => item.IsDefault === true && item.Domain);
|
|
7236
|
+
const preferred = defaultCandidates.find((item) => item.Enable !== false && item.Status === "SUCCESS") ??
|
|
7237
|
+
defaultCandidates.find((item) => item.Enable !== false) ??
|
|
7238
|
+
defaultCandidates[0];
|
|
7239
|
+
if (!preferred?.Domain) {
|
|
7240
|
+
throw new Error("环境默认 HTTP 访问域名未就绪或未开通。请先在控制台开通 HTTP 访问服务,或用 queryGateway(action=\"listRoutes\") 确认 Domains 中是否存在 IsDefault=true 的域名;也可以显式传入 domain 后重试 createRoute/updateRoute/deleteRoute。");
|
|
7241
|
+
}
|
|
7242
|
+
return preferred.Domain;
|
|
7243
|
+
};
|
|
7205
7244
|
const resolveRouteDomain = async (preferredDomain) => {
|
|
7206
7245
|
if (preferredDomain) {
|
|
7207
7246
|
return preferredDomain;
|
|
7208
7247
|
}
|
|
7209
|
-
|
|
7210
|
-
return routeInfo.OriginDomain;
|
|
7248
|
+
return resolveDefaultHttpDomain();
|
|
7211
7249
|
};
|
|
7212
|
-
const
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
if (
|
|
7217
|
-
|
|
7250
|
+
const mapUpstreamResourceType = (input) => {
|
|
7251
|
+
if (input.upstreamResourceType) {
|
|
7252
|
+
return input.upstreamResourceType;
|
|
7253
|
+
}
|
|
7254
|
+
if (input.type === "HTTP") {
|
|
7255
|
+
return "WEB_SCF";
|
|
7256
|
+
}
|
|
7257
|
+
if (input.type === "Event") {
|
|
7258
|
+
return "SCF";
|
|
7218
7259
|
}
|
|
7260
|
+
if (input.requiresFunctionType || input.targetType === "function") {
|
|
7261
|
+
throw new Error("为云函数创建/更新路由时必须显式提供 type(HTTP→WEB_SCF,Event→SCF)或 route.upstreamResourceType。省略会导致 HTTP 与 Event 函数互相误标,访问时可能返回 FUNCTION_PARAM_INVALID 或网关内部错误。");
|
|
7262
|
+
}
|
|
7263
|
+
throw new Error("必须提供 route.upstreamResourceType(SCF / WEB_SCF / CBR / STATIC_STORE / LH),或在函数场景提供 type=\"HTTP\"|\"Event\"。");
|
|
7264
|
+
};
|
|
7265
|
+
const normalizeRoutePayload = async (input) => {
|
|
7266
|
+
const upstreamResourceName = input.route?.serviceName ?? input.targetName;
|
|
7267
|
+
if (!upstreamResourceName) {
|
|
7268
|
+
throw new Error("必须提供 targetName 或 route.serviceName 作为上游资源名称(例如云函数名)");
|
|
7269
|
+
}
|
|
7270
|
+
const isFunctionRoute = input.targetType === "function" ||
|
|
7271
|
+
input.type !== undefined ||
|
|
7272
|
+
input.route?.upstreamResourceType === "SCF" ||
|
|
7273
|
+
input.route?.upstreamResourceType === "WEB_SCF" ||
|
|
7274
|
+
(!input.route?.upstreamResourceType && Boolean(input.targetName));
|
|
7275
|
+
const upstreamResourceType = mapUpstreamResourceType({
|
|
7276
|
+
type: input.type,
|
|
7277
|
+
targetType: input.targetType,
|
|
7278
|
+
upstreamResourceType: input.route?.upstreamResourceType,
|
|
7279
|
+
requiresFunctionType: isFunctionRoute,
|
|
7280
|
+
});
|
|
7281
|
+
const path = normalizeAccessPath(input.route?.path ?? input.path ?? `/${upstreamResourceName}`);
|
|
7282
|
+
const domain = await resolveRouteDomain(input.domain);
|
|
7283
|
+
const enableAuth = input.route?.auth !== undefined
|
|
7284
|
+
? input.route.auth
|
|
7285
|
+
: input.auth !== undefined
|
|
7286
|
+
? input.auth
|
|
7287
|
+
: undefined;
|
|
7219
7288
|
return {
|
|
7220
7289
|
EnvId: await resolveEnvId(),
|
|
7221
7290
|
Domain: {
|
|
7222
|
-
Domain:
|
|
7291
|
+
Domain: domain,
|
|
7223
7292
|
Routes: [
|
|
7224
7293
|
{
|
|
7225
7294
|
Path: path,
|
|
7226
|
-
UpstreamResourceType:
|
|
7227
|
-
UpstreamResourceName:
|
|
7228
|
-
EnableAuth:
|
|
7229
|
-
? route.auth
|
|
7230
|
-
: fallback.auth !== undefined
|
|
7231
|
-
? fallback.auth
|
|
7232
|
-
: undefined,
|
|
7295
|
+
UpstreamResourceType: upstreamResourceType,
|
|
7296
|
+
UpstreamResourceName: upstreamResourceName,
|
|
7297
|
+
EnableAuth: enableAuth,
|
|
7233
7298
|
},
|
|
7234
7299
|
],
|
|
7235
7300
|
},
|
|
7301
|
+
resolved: {
|
|
7302
|
+
domain,
|
|
7303
|
+
path,
|
|
7304
|
+
upstreamResourceType,
|
|
7305
|
+
upstreamResourceName,
|
|
7306
|
+
enableAuth,
|
|
7307
|
+
},
|
|
7236
7308
|
};
|
|
7237
7309
|
};
|
|
7310
|
+
const routeMutationNextActions = (targetName) => [
|
|
7311
|
+
{
|
|
7312
|
+
tool: "queryGateway",
|
|
7313
|
+
action: "getRoute",
|
|
7314
|
+
reason: "等待 30 秒到 3 分钟后再确认访问入口是否已生效",
|
|
7315
|
+
},
|
|
7316
|
+
{
|
|
7317
|
+
tool: "queryPermissions",
|
|
7318
|
+
action: "getResourcePermission",
|
|
7319
|
+
reason: "确认函数安全规则是否允许预期访问方;网关 EnableAuth/auth=false 不等于函数已允许匿名访问",
|
|
7320
|
+
},
|
|
7321
|
+
{
|
|
7322
|
+
tool: "managePermissions",
|
|
7323
|
+
action: "updateResourcePermission",
|
|
7324
|
+
reason: "只有在确认需要匿名或浏览器直连访问时,才按实际安全要求更新函数权限",
|
|
7325
|
+
},
|
|
7326
|
+
];
|
|
7238
7327
|
const handleQueryGateway = async (input) => {
|
|
7239
7328
|
switch (input.action) {
|
|
7240
|
-
case "listDomains": {
|
|
7241
|
-
const result = await listGatewayDomains();
|
|
7242
|
-
return buildEnvelope({
|
|
7243
|
-
action: input.action,
|
|
7244
|
-
domains: result.domains,
|
|
7245
|
-
enableService: result.enableService,
|
|
7246
|
-
raw: result.raw,
|
|
7247
|
-
}, `已获取 ${result.domains.length} 个网关域名`, [
|
|
7248
|
-
{
|
|
7249
|
-
tool: "queryGateway",
|
|
7250
|
-
action: "getAccess",
|
|
7251
|
-
reason: "查看某个目标当前的访问入口",
|
|
7252
|
-
},
|
|
7253
|
-
]);
|
|
7254
|
-
}
|
|
7255
7329
|
case "listCustomDomains": {
|
|
7256
|
-
const result = await
|
|
7330
|
+
const result = await listHttpServiceRoutes();
|
|
7331
|
+
const customDomains = (result.Domains ?? []).filter((item) => item.IsDefault !== true);
|
|
7257
7332
|
return buildEnvelope({
|
|
7258
7333
|
action: input.action,
|
|
7259
|
-
domains:
|
|
7260
|
-
total:
|
|
7261
|
-
raw: result
|
|
7262
|
-
}, `已获取 ${
|
|
7334
|
+
domains: customDomains,
|
|
7335
|
+
total: customDomains.length,
|
|
7336
|
+
raw: result,
|
|
7337
|
+
}, `已获取 ${customDomains.length} 个自定义域名`);
|
|
7263
7338
|
}
|
|
7264
7339
|
case "listRoutes": {
|
|
7265
|
-
const result = await listHttpServiceRoutes();
|
|
7340
|
+
const result = await listHttpServiceRoutes(input.domain);
|
|
7266
7341
|
const routes = flattenRoutes(result);
|
|
7267
7342
|
return buildEnvelope({
|
|
7268
7343
|
action: input.action,
|
|
7269
7344
|
routes,
|
|
7345
|
+
urls: buildRouteUrls(routes),
|
|
7270
7346
|
total: result.TotalCount ?? routes.length,
|
|
7271
7347
|
raw: result,
|
|
7272
7348
|
}, `已获取 ${result.TotalCount ?? routes.length} 条 HTTP 路由`);
|
|
7273
7349
|
}
|
|
7274
7350
|
case "getRoute": {
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7351
|
+
if (!input.routeId && !input.targetName && !input.path) {
|
|
7352
|
+
throw new Error("action=getRoute 时至少需要提供 routeId、targetName 或 path");
|
|
7353
|
+
}
|
|
7354
|
+
const result = await listHttpServiceRoutes(input.domain);
|
|
7355
|
+
const normalizedPath = input.path
|
|
7356
|
+
? normalizeAccessPath(input.path)
|
|
7357
|
+
: undefined;
|
|
7358
|
+
const matches = flattenRoutes(result).filter((item) => {
|
|
7359
|
+
if (input.routeId && item.RouteId !== input.routeId) {
|
|
7360
|
+
return false;
|
|
7361
|
+
}
|
|
7362
|
+
if (input.domain && item.Domain !== input.domain) {
|
|
7363
|
+
return false;
|
|
7364
|
+
}
|
|
7365
|
+
if (normalizedPath &&
|
|
7366
|
+
normalizeAccessPath(String(item.Path ?? "")) !== normalizedPath) {
|
|
7367
|
+
return false;
|
|
7368
|
+
}
|
|
7369
|
+
if (input.targetName &&
|
|
7370
|
+
item.UpstreamResourceName !== input.targetName) {
|
|
7371
|
+
return false;
|
|
7372
|
+
}
|
|
7373
|
+
return Boolean(input.routeId || input.targetName || normalizedPath);
|
|
7374
|
+
});
|
|
7375
|
+
const route = matches.length === 1 ? matches[0] : null;
|
|
7376
|
+
const urls = buildRouteUrls(matches);
|
|
7278
7377
|
return buildEnvelope({
|
|
7279
7378
|
action: input.action,
|
|
7280
7379
|
routeId: input.routeId ?? null,
|
|
7380
|
+
targetName: input.targetName ?? null,
|
|
7381
|
+
path: normalizedPath ?? null,
|
|
7382
|
+
domain: input.domain ?? null,
|
|
7281
7383
|
route,
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
}
|
|
7285
|
-
case "getAccess": {
|
|
7286
|
-
if (!input.targetName) {
|
|
7287
|
-
throw new Error("getAccess 操作时,targetName 参数是必需的");
|
|
7288
|
-
}
|
|
7289
|
-
const cloudbase = await getManager();
|
|
7290
|
-
const [accessList, domainInfo] = await Promise.all([
|
|
7291
|
-
cloudbase.access.getAccessList({ name: input.targetName }),
|
|
7292
|
-
listGatewayDomains(),
|
|
7293
|
-
]);
|
|
7294
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, accessList);
|
|
7295
|
-
const urls = Array.from(new Set((accessList.APISet || []).flatMap((api) => domainInfo.domains.map((domain) => {
|
|
7296
|
-
const normalizedPath = normalizeAccessPath(api.Path);
|
|
7297
|
-
return `https://${domain}${normalizedPath}`;
|
|
7298
|
-
}))));
|
|
7299
|
-
return buildEnvelope({
|
|
7300
|
-
action: input.action,
|
|
7301
|
-
targetType: input.targetType ?? "function",
|
|
7302
|
-
targetName: input.targetName,
|
|
7303
|
-
apis: accessList.APISet || [],
|
|
7304
|
-
total: accessList.Total || 0,
|
|
7305
|
-
domains: domainInfo.domains,
|
|
7384
|
+
routes: matches,
|
|
7385
|
+
total: matches.length,
|
|
7306
7386
|
urls,
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7387
|
+
raw: result,
|
|
7388
|
+
}, matches.length === 0
|
|
7389
|
+
? "未找到对应路由"
|
|
7390
|
+
: matches.length === 1
|
|
7391
|
+
? "已获取路由详情"
|
|
7392
|
+
: `匹配到 ${matches.length} 条路由,请补充 path 或 domain 精确定位`, [
|
|
7313
7393
|
{
|
|
7314
7394
|
tool: "manageGateway",
|
|
7315
|
-
action: "
|
|
7316
|
-
reason: "
|
|
7395
|
+
action: "createRoute",
|
|
7396
|
+
reason: "为该目标新增 Domain/Route 访问路径",
|
|
7317
7397
|
},
|
|
7318
7398
|
]);
|
|
7319
7399
|
}
|
|
@@ -7323,78 +7403,60 @@ function registerGatewayTools(server) {
|
|
|
7323
7403
|
};
|
|
7324
7404
|
const handleManageGateway = async (input) => {
|
|
7325
7405
|
switch (input.action) {
|
|
7326
|
-
case "
|
|
7327
|
-
if (!input.targetName || !input.targetType) {
|
|
7328
|
-
throw new Error("action=createAccess 时必须提供 targetType 和 targetName");
|
|
7329
|
-
}
|
|
7330
|
-
if (!input.type) {
|
|
7331
|
-
throw new Error("action=createAccess 且 targetType=function 时必须显式提供 type。HTTP 云函数传 HTTP;Event 函数传 Event。省略会默认按 Event 路由处理,可能让 HTTP 云函数访问后返回 FUNCTION_PARAM_INVALID。");
|
|
7332
|
-
}
|
|
7406
|
+
case "createRoute": {
|
|
7333
7407
|
const cloudbase = await getManager();
|
|
7334
|
-
const
|
|
7408
|
+
const payload = await normalizeRoutePayload(input);
|
|
7335
7409
|
let result;
|
|
7336
7410
|
try {
|
|
7337
|
-
result = await cloudbase.
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
type: (input.type === "HTTP" ? 6 : 1),
|
|
7341
|
-
auth: input.auth,
|
|
7411
|
+
result = await cloudbase.env.createHttpServiceRoute({
|
|
7412
|
+
EnvId: payload.EnvId,
|
|
7413
|
+
Domain: payload.Domain,
|
|
7342
7414
|
});
|
|
7343
7415
|
}
|
|
7344
7416
|
catch (err) {
|
|
7345
|
-
|
|
7346
|
-
|
|
7417
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7418
|
+
if (message.includes("An error has occurred")) {
|
|
7419
|
+
let hint = "为目标资源配置访问路由失败(后端内部错误)。请确保:1) 目标云函数已成功创建并处于 Active 状态;2) 环境默认 HTTP 域名已完成初始化(IsDefault);3) 该访问路径未被占用。";
|
|
7347
7420
|
if (input.type === "HTTP") {
|
|
7348
|
-
hint +=
|
|
7421
|
+
hint +=
|
|
7422
|
+
"此外注意:如果目标函数最初是作为 Event 函数创建的,这里 type 仍必须传 Event(UpstreamResourceType=SCF);误传 HTTP/WEB_SCF 会导致此错误。";
|
|
7349
7423
|
}
|
|
7350
|
-
throw new Error(`${hint} 原始错误:${
|
|
7424
|
+
throw new Error(`${hint} 原始错误:${message}`);
|
|
7351
7425
|
}
|
|
7352
7426
|
throw err;
|
|
7353
7427
|
}
|
|
7354
7428
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7355
7429
|
return buildEnvelope({
|
|
7356
7430
|
action: input.action,
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
reason: "等待 30 秒到 3 分钟后再确认访问入口是否已生效",
|
|
7366
|
-
},
|
|
7367
|
-
{
|
|
7368
|
-
tool: "queryPermissions",
|
|
7369
|
-
action: "getResourcePermission",
|
|
7370
|
-
reason: "确认函数安全规则是否允许预期访问方;网关 auth=false 不等于函数已允许匿名访问",
|
|
7371
|
-
},
|
|
7372
|
-
{
|
|
7373
|
-
tool: "managePermissions",
|
|
7374
|
-
action: "updateResourcePermission",
|
|
7375
|
-
reason: "只有在确认需要匿名或浏览器直连访问时,才按实际安全要求更新函数权限",
|
|
7376
|
-
},
|
|
7377
|
-
]);
|
|
7378
|
-
}
|
|
7379
|
-
case "createRoute": {
|
|
7380
|
-
const cloudbase = await getManager();
|
|
7381
|
-
const result = await cloudbase.env.createHttpServiceRoute((await normalizeRoutePayload(input.route, input, input.domain)));
|
|
7382
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7383
|
-
return buildEnvelope({
|
|
7384
|
-
action: input.action,
|
|
7385
|
-
route: input.route ?? null,
|
|
7431
|
+
model: "httpServiceRoute",
|
|
7432
|
+
targetType: input.targetType ?? null,
|
|
7433
|
+
targetName: payload.resolved.upstreamResourceName,
|
|
7434
|
+
domain: payload.resolved.domain,
|
|
7435
|
+
path: payload.resolved.path,
|
|
7436
|
+
upstreamResourceType: payload.resolved.upstreamResourceType,
|
|
7437
|
+
upstreamResourceName: payload.resolved.upstreamResourceName,
|
|
7438
|
+
auth: payload.resolved.enableAuth ?? null,
|
|
7386
7439
|
raw: result,
|
|
7387
|
-
},
|
|
7440
|
+
}, `已为目标 ${payload.resolved.upstreamResourceName} 在域名 ${payload.resolved.domain} 创建路由 ${payload.resolved.path}(${payload.resolved.upstreamResourceType})。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。该操作只创建网关入口,不会自动放开函数安全规则;若需要匿名或浏览器直接访问,请继续检查函数资源权限。`, routeMutationNextActions(payload.resolved.upstreamResourceName));
|
|
7388
7441
|
}
|
|
7389
7442
|
case "updateRoute": {
|
|
7390
7443
|
const cloudbase = await getManager();
|
|
7391
|
-
const
|
|
7444
|
+
const payload = await normalizeRoutePayload(input);
|
|
7445
|
+
const result = await cloudbase.env.modifyHttpServiceRoute({
|
|
7446
|
+
EnvId: payload.EnvId,
|
|
7447
|
+
Domain: payload.Domain,
|
|
7448
|
+
});
|
|
7392
7449
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7393
7450
|
return buildEnvelope({
|
|
7394
7451
|
action: input.action,
|
|
7395
|
-
|
|
7452
|
+
model: "httpServiceRoute",
|
|
7453
|
+
domain: payload.resolved.domain,
|
|
7454
|
+
path: payload.resolved.path,
|
|
7455
|
+
upstreamResourceType: payload.resolved.upstreamResourceType,
|
|
7456
|
+
upstreamResourceName: payload.resolved.upstreamResourceName,
|
|
7457
|
+
auth: payload.resolved.enableAuth ?? null,
|
|
7396
7458
|
raw: result,
|
|
7397
|
-
},
|
|
7459
|
+
}, `HTTP 路由更新成功(${payload.resolved.domain}${payload.resolved.path})`, routeMutationNextActions(payload.resolved.upstreamResourceName));
|
|
7398
7460
|
}
|
|
7399
7461
|
case "deleteRoute": {
|
|
7400
7462
|
const routePath = input.route?.path ?? input.path;
|
|
@@ -7403,16 +7465,18 @@ function registerGatewayTools(server) {
|
|
|
7403
7465
|
}
|
|
7404
7466
|
const cloudbase = await getManager();
|
|
7405
7467
|
const domain = await resolveRouteDomain(input.domain);
|
|
7468
|
+
const normalizedPath = normalizeAccessPath(routePath);
|
|
7406
7469
|
const result = await cloudbase.env.deleteHttpServiceRoute({
|
|
7407
7470
|
EnvId: await resolveEnvId(),
|
|
7408
7471
|
Domain: domain,
|
|
7409
|
-
Paths: [
|
|
7472
|
+
Paths: [normalizedPath],
|
|
7410
7473
|
});
|
|
7411
7474
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7412
7475
|
return buildEnvelope({
|
|
7413
7476
|
action: input.action,
|
|
7477
|
+
model: "httpServiceRoute",
|
|
7414
7478
|
domain,
|
|
7415
|
-
path:
|
|
7479
|
+
path: normalizedPath,
|
|
7416
7480
|
raw: result,
|
|
7417
7481
|
}, "HTTP 路由删除成功");
|
|
7418
7482
|
}
|
|
@@ -7452,114 +7516,34 @@ function registerGatewayTools(server) {
|
|
|
7452
7516
|
raw: result,
|
|
7453
7517
|
}, "自定义域名删除成功");
|
|
7454
7518
|
}
|
|
7455
|
-
case "deleteAccess": {
|
|
7456
|
-
const cloudbase = await getManager();
|
|
7457
|
-
const accessPath = input.path ? normalizeAccessPath(input.path) : undefined;
|
|
7458
|
-
let resolvedAccessId = input.accessId;
|
|
7459
|
-
let resolvedName;
|
|
7460
|
-
if (!resolvedAccessId) {
|
|
7461
|
-
if (!input.targetName && !accessPath) {
|
|
7462
|
-
throw new Error("action=deleteAccess 时至少需要提供 accessId,或提供 targetName/path 用于定位入口");
|
|
7463
|
-
}
|
|
7464
|
-
const accessList = await cloudbase.access.getAccessList({
|
|
7465
|
-
...(input.targetName ? { name: input.targetName } : {}),
|
|
7466
|
-
...(accessPath ? { path: accessPath } : {}),
|
|
7467
|
-
});
|
|
7468
|
-
const candidates = (accessList.APISet ?? []).filter((item) => {
|
|
7469
|
-
if (!accessPath) {
|
|
7470
|
-
return true;
|
|
7471
|
-
}
|
|
7472
|
-
return normalizeAccessPath(item.Path) === accessPath;
|
|
7473
|
-
});
|
|
7474
|
-
if (candidates.length === 0) {
|
|
7475
|
-
throw new Error("未找到可删除的访问入口,请确认 targetName/path 或直接传 accessId");
|
|
7476
|
-
}
|
|
7477
|
-
if (candidates.length > 1 && !accessPath) {
|
|
7478
|
-
throw new Error("匹配到多个访问入口,请补充 path 或直接传 accessId");
|
|
7479
|
-
}
|
|
7480
|
-
resolvedAccessId = candidates[0]?.APIId;
|
|
7481
|
-
resolvedName = candidates[0]?.Name;
|
|
7482
|
-
}
|
|
7483
|
-
else {
|
|
7484
|
-
// 仅传入 accessId 时,先查询获取 name 等信息,确保删除参数完整
|
|
7485
|
-
try {
|
|
7486
|
-
const accessList = await cloudbase.access.getAccessList({});
|
|
7487
|
-
const match = (accessList.APISet ?? []).find((item) => item.APIId === resolvedAccessId);
|
|
7488
|
-
if (match) {
|
|
7489
|
-
resolvedName = match.Name;
|
|
7490
|
-
}
|
|
7491
|
-
}
|
|
7492
|
-
catch {
|
|
7493
|
-
// 查询失败不影响主流程,继续用 accessId 删除
|
|
7494
|
-
}
|
|
7495
|
-
}
|
|
7496
|
-
if (!resolvedAccessId) {
|
|
7497
|
-
throw new Error("无法解析访问入口 accessId,请改为显式传入 accessId");
|
|
7498
|
-
}
|
|
7499
|
-
const result = await cloudbase.access.deleteAccess({
|
|
7500
|
-
...(resolvedName ? { name: resolvedName } : {}),
|
|
7501
|
-
apiId: resolvedAccessId,
|
|
7502
|
-
});
|
|
7503
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7504
|
-
return buildEnvelope({
|
|
7505
|
-
action: input.action,
|
|
7506
|
-
targetName: resolvedName ?? input.targetName ?? null,
|
|
7507
|
-
path: accessPath ?? null,
|
|
7508
|
-
accessId: resolvedAccessId,
|
|
7509
|
-
raw: result,
|
|
7510
|
-
}, "网关访问入口删除成功");
|
|
7511
|
-
}
|
|
7512
|
-
case "updatePathAuth": {
|
|
7513
|
-
if (!input.targetName && !input.path) {
|
|
7514
|
-
throw new Error("action=updatePathAuth 时至少需要提供 targetName 或 path");
|
|
7515
|
-
}
|
|
7516
|
-
if (input.auth === undefined) {
|
|
7517
|
-
throw new Error("action=updatePathAuth 时必须提供 auth");
|
|
7518
|
-
}
|
|
7519
|
-
const cloudbase = await getManager();
|
|
7520
|
-
const accessPath = input.path ? normalizeAccessPath(input.path) : undefined;
|
|
7521
|
-
const accessList = await cloudbase.access.getAccessList({
|
|
7522
|
-
...(input.targetName ? { name: input.targetName } : {}),
|
|
7523
|
-
...(accessPath ? { path: accessPath } : {}),
|
|
7524
|
-
});
|
|
7525
|
-
const apiIds = (accessList.APISet ?? []).map((item) => item.APIId).filter(Boolean);
|
|
7526
|
-
if (apiIds.length === 0) {
|
|
7527
|
-
throw new Error("未找到可更新鉴权状态的访问入口");
|
|
7528
|
-
}
|
|
7529
|
-
const result = await cloudbase.access.switchPathAuth({
|
|
7530
|
-
apiIds,
|
|
7531
|
-
auth: input.auth,
|
|
7532
|
-
});
|
|
7533
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7534
|
-
return buildEnvelope({
|
|
7535
|
-
action: input.action,
|
|
7536
|
-
targetName: input.targetName ?? null,
|
|
7537
|
-
path: accessPath ?? null,
|
|
7538
|
-
auth: input.auth,
|
|
7539
|
-
apiIds,
|
|
7540
|
-
raw: result,
|
|
7541
|
-
}, "网关路径鉴权更新成功");
|
|
7542
|
-
}
|
|
7543
7519
|
default:
|
|
7544
7520
|
throw new Error(`不支持的操作类型: ${input.action}`);
|
|
7545
7521
|
}
|
|
7546
7522
|
};
|
|
7547
7523
|
server.registerTool?.("queryGateway", {
|
|
7548
7524
|
title: "查询 CloudBase 网关",
|
|
7549
|
-
description: "CloudBase
|
|
7525
|
+
description: "CloudBase 网关统一只读入口(Domain/Route 模型)。通过 listRoutes / getRoute / listCustomDomains 查询域名与路由;主键为 Domain + Path,上游类型为 SCF / WEB_SCF / CBR / STATIC_STORE / LH。",
|
|
7550
7526
|
inputSchema: {
|
|
7551
7527
|
action: zod_1.z
|
|
7552
7528
|
.enum(QUERY_GATEWAY_ACTIONS)
|
|
7553
|
-
.describe("
|
|
7529
|
+
.describe("只读操作类型:listRoutes、getRoute、listCustomDomains"),
|
|
7554
7530
|
targetType: zod_1.z
|
|
7555
7531
|
.enum(["function"])
|
|
7556
7532
|
.optional()
|
|
7557
|
-
.describe("目标资源类型。当前支持 function
|
|
7533
|
+
.describe("目标资源类型。当前支持 function"),
|
|
7558
7534
|
targetName: zod_1.z
|
|
7559
7535
|
.string()
|
|
7560
7536
|
.optional()
|
|
7561
|
-
.describe("
|
|
7537
|
+
.describe("上游资源名称。getRoute 时可按云函数名过滤"),
|
|
7562
7538
|
routeId: zod_1.z.string().optional().describe("路由 ID。getRoute 时可选"),
|
|
7539
|
+
path: zod_1.z
|
|
7540
|
+
.string()
|
|
7541
|
+
.optional()
|
|
7542
|
+
.describe("路由路径。getRoute / listRoutes 过滤时可选"),
|
|
7543
|
+
domain: zod_1.z
|
|
7544
|
+
.string()
|
|
7545
|
+
.optional()
|
|
7546
|
+
.describe("域名。getRoute / listRoutes 过滤时可选"),
|
|
7563
7547
|
},
|
|
7564
7548
|
annotations: {
|
|
7565
7549
|
readOnlyHint: true,
|
|
@@ -7569,19 +7553,19 @@ function registerGatewayTools(server) {
|
|
|
7569
7553
|
}, async (input) => withEnvelope(() => handleQueryGateway(input)));
|
|
7570
7554
|
server.registerTool?.("manageGateway", {
|
|
7571
7555
|
title: "管理 CloudBase 网关",
|
|
7572
|
-
description: "CloudBase
|
|
7556
|
+
description: "CloudBase 网关统一写入口(Domain/Route 模型)。为已存在的 HTTP 云函数补默认域名访问时,使用 createRoute,并提供 targetType=\"function\"、targetName、type=\"HTTP\"(映射 WEB_SCF)与期望 path;Event 函数传 type=\"Event\"(映射 SCF)。未传 domain 时自动解析 IsDefault 默认域名。注意 createRoute 只创建网关入口,不会自动修改函数资源权限。更新鉴权用 updateRoute;删除用 deleteRoute(Domain+Path)。⚠️ 绑定带 SSL 证书的自定义域名用 bindCustomDomain;CORS/安全域名请使用 envDomainManagement。",
|
|
7573
7557
|
inputSchema: {
|
|
7574
7558
|
action: zod_1.z
|
|
7575
7559
|
.enum(MANAGE_GATEWAY_ACTIONS)
|
|
7576
|
-
.describe(
|
|
7560
|
+
.describe("写操作类型。为已有函数补默认域名访问入口时使用 createRoute;函数场景必须显式提供 type(HTTP→WEB_SCF,Event→SCF)或 route.upstreamResourceType。"),
|
|
7577
7561
|
targetType: zod_1.z
|
|
7578
7562
|
.enum(["function"])
|
|
7579
7563
|
.optional()
|
|
7580
|
-
.describe("目标资源类型。当前支持 function
|
|
7564
|
+
.describe("目标资源类型。当前支持 function"),
|
|
7581
7565
|
targetName: zod_1.z
|
|
7582
7566
|
.string()
|
|
7583
7567
|
.optional()
|
|
7584
|
-
.describe("目标资源名称。
|
|
7568
|
+
.describe("目标资源名称。createRoute 到云函数时填写函数名"),
|
|
7585
7569
|
path: zod_1.z
|
|
7586
7570
|
.string()
|
|
7587
7571
|
.optional()
|
|
@@ -7589,25 +7573,28 @@ function registerGatewayTools(server) {
|
|
|
7589
7573
|
type: zod_1.z
|
|
7590
7574
|
.enum(["Event", "HTTP"])
|
|
7591
7575
|
.optional()
|
|
7592
|
-
.describe("
|
|
7576
|
+
.describe("目标函数运行时类型,不是接入协议。HTTP 云函数传 HTTP(UpstreamResourceType=WEB_SCF);Event 函数传 Event(SCF)。误标会导致 FUNCTION_PARAM_INVALID 或网关错误。函数路由场景必须显式提供 type 或 route.upstreamResourceType。"),
|
|
7593
7577
|
auth: zod_1.z
|
|
7594
7578
|
.boolean()
|
|
7595
7579
|
.optional()
|
|
7596
|
-
.describe("
|
|
7580
|
+
.describe("是否开启网关路径鉴权(EnableAuth)。若要走默认域名做匿名或浏览器访问,通常设为 false;该开关仅控制网关入口本身,不会修改函数资源权限。"),
|
|
7597
7581
|
route: zod_1.z
|
|
7598
7582
|
.object({
|
|
7599
|
-
routeId: zod_1.z.string().optional(),
|
|
7600
7583
|
path: zod_1.z.string().optional(),
|
|
7601
|
-
serviceType: zod_1.z.string().optional(),
|
|
7602
7584
|
serviceName: zod_1.z.string().optional(),
|
|
7585
|
+
upstreamResourceType: zod_1.z.enum(UPSTREAM_RESOURCE_TYPES).optional(),
|
|
7603
7586
|
auth: zod_1.z.boolean().optional(),
|
|
7604
7587
|
})
|
|
7605
7588
|
.optional()
|
|
7606
|
-
.describe("HTTP
|
|
7607
|
-
domain: zod_1.z
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7589
|
+
.describe("HTTP 路由配置。upstreamResourceType 可选 SCF / WEB_SCF / CBR / STATIC_STORE / LH"),
|
|
7590
|
+
domain: zod_1.z
|
|
7591
|
+
.string()
|
|
7592
|
+
.optional()
|
|
7593
|
+
.describe("域名。省略时自动使用环境 IsDefault 默认 HTTP 域名;自定义域名场景请显式传入"),
|
|
7594
|
+
certificateId: zod_1.z
|
|
7595
|
+
.string()
|
|
7596
|
+
.optional()
|
|
7597
|
+
.describe("证书 ID。bindCustomDomain 时必填"),
|
|
7611
7598
|
},
|
|
7612
7599
|
annotations: {
|
|
7613
7600
|
readOnlyHint: false,
|
|
@@ -8254,6 +8241,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8254
8241
|
};
|
|
8255
8242
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
8256
8243
|
exports.CLOUDRUN_ACCESS_TYPES = exports.CLOUDRUN_SERVICE_TYPES = void 0;
|
|
8244
|
+
exports.detectCloudRunDbNetworkRisk = detectCloudRunDbNetworkRisk;
|
|
8257
8245
|
exports.registerCloudRunTools = registerCloudRunTools;
|
|
8258
8246
|
const child_process_1 = __webpack_require__(5814);
|
|
8259
8247
|
const fs_1 = __importDefault(__webpack_require__(4421));
|
|
@@ -8296,7 +8284,7 @@ const ManageCloudRunInputSchema = {
|
|
|
8296
8284
|
})).optional().describe('扩缩容配置数组,用于配置服务的自动扩缩容策略。可配置多个扩缩容策略'),
|
|
8297
8285
|
CustomLogs: zod_1.z.string().optional().describe('自定义日志配置,用于配置服务的日志收集和存储策略'),
|
|
8298
8286
|
Port: zod_1.z.number().min(1).max(65535).optional().describe('服务监听端口配置。函数型服务固定为3000,容器型服务可自定义。服务代码必须监听此端口才能正常接收请求'),
|
|
8299
|
-
EnvParams: zod_1.z.string().optional().describe('环境变量配置,JSON字符串格式。用于传递配置信息给服务代码,如\'{"DATABASE_URL":"
|
|
8287
|
+
EnvParams: zod_1.z.string().optional().describe('环境变量配置,JSON字符串格式。用于传递配置信息给服务代码,如\'{"DATABASE_URL":"postgres://user:pass@10.x.x.x:5432/db","NODE_ENV":"production"}\'。SDK v5.6.1+ 会自动对传入的环境变量进行 AES-256-CBC 加密传输。⚠️ 若 EnvParams 含 DATABASE_URL / MYSQL_* / POSTGRES_* / REDIS_* 等传统 TCP 连库变量,必须同时配置 VpcConf,否则实例通常无法访问 VPC 内数据库'),
|
|
8300
8288
|
Dockerfile: zod_1.z.string().optional().describe('Dockerfile文件名配置,仅容器型服务需要。指定用于构建容器镜像的Dockerfile文件路径,默认为项目根目录下的Dockerfile'),
|
|
8301
8289
|
BuildDir: zod_1.z.string().optional().describe('构建目录配置,指定代码构建的目录路径。当代码结构与标准不同时使用,默认为项目根目录'),
|
|
8302
8290
|
InternalAccess: zod_1.z.string().optional().describe('内网访问开关配置,控制是否启用内网访问。true=启用内网访问(可通过云开发SDK直接调用),false=关闭内网访问(仅公网访问)'),
|
|
@@ -8320,9 +8308,9 @@ const ManageCloudRunInputSchema = {
|
|
|
8320
8308
|
ReplicaNum: zod_1.z.number().min(0).describe('定时扩缩容的目标副本数,最小值0(缩容到0)')
|
|
8321
8309
|
})).optional().describe('定时扩缩容配置数组,用于配置服务的定时自动扩缩容策略。可配置多个时间段的扩缩容计划,支持每日/每周/每月循环'),
|
|
8322
8310
|
VpcConf: zod_1.z.object({
|
|
8323
|
-
VpcId: zod_1.z.string().describe('VPC网络ID
|
|
8324
|
-
SubnetId: zod_1.z.string().describe('子网ID
|
|
8325
|
-
}).optional().describe('VPC
|
|
8311
|
+
VpcId: zod_1.z.string().describe('VPC网络ID,格式如 vpc-xxxxxxxx。必须与目标数据库/Redis 处于同一地域,并优先选择同一 VPC。禁止猜测或使用占位符;须来自数据库控制台、已有资源详情、callCloudApi 或用户确认。建议首次创建即配置;已存在服务也可在 deploy 时传入,部署后必须用 queryCloudRun detail 复核是否生效'),
|
|
8312
|
+
SubnetId: zod_1.z.string().describe('子网ID,格式如 subnet-xxxxxxxx。云托管实例将占用该子网 IP,需确保有足够可用 IP'),
|
|
8313
|
+
}).optional().describe('VPC网络配置(实例出网/私有网络)。用于让云托管实例接入指定 VPC,从而内网访问 MySQL/PostgreSQL/Redis/CVM 等资源。与 OpenAccessTypes(外部如何访问本服务)是不同概念。TCP 连库场景必须配置。禁止猜测 VpcId/SubnetId,须来自数据库控制台、已有资源详情、callCloudApi 或用户确认。MCP 在创建时会将 VpcConf 映射为 SDK vpcInfo(CreateType=2);更新后必须用 queryCloudRun detail 复核 ServerConfig.VpcConf,未生效再走控制台网络设置或删建兜底'),
|
|
8326
8314
|
VolumesConf: zod_1.z.array(zod_1.z.object({
|
|
8327
8315
|
VolumeName: zod_1.z.string().describe('存储卷名称'),
|
|
8328
8316
|
VolumeType: zod_1.z.string().describe('存储卷类型,如CFS表示云文件存储'),
|
|
@@ -8426,6 +8414,74 @@ function buildManageCloudRunErrorMessage(action, serverName, error) {
|
|
|
8426
8414
|
}
|
|
8427
8415
|
return `[manageCloudRun/${action}] ${baseMessage}\n建议:${suggestions.join(" ")}`;
|
|
8428
8416
|
}
|
|
8417
|
+
const CLOUDRUN_DB_ENV_KEY_PATTERN = /^(DATABASE_URL|DB_HOST|DB_PORT|DB_USER|DB_PASSWORD|DB_NAME|MYSQL_|POSTGRES_|PGHOST|PGPORT|PGUSER|PGPASSWORD|PGDATABASE|PG_|REDIS_|MONGO_|MONGODB_|SQLALCHEMY_DATABASE_URI|SPRING_DATASOURCE_)/i;
|
|
8418
|
+
const CLOUDRUN_DB_URL_PATTERN = /(mysql|mariadb|postgres|postgresql|mongodb(\+srv)?|redis|rediss):\/\//i;
|
|
8419
|
+
/**
|
|
8420
|
+
* Detect likely TCP database/cache env usage without VpcConf.
|
|
8421
|
+
* Soft signal for AI agents — does not block deploy.
|
|
8422
|
+
*/
|
|
8423
|
+
function detectCloudRunDbNetworkRisk(options) {
|
|
8424
|
+
const vpcId = options.vpcConf?.VpcId?.trim();
|
|
8425
|
+
const subnetId = options.vpcConf?.SubnetId?.trim();
|
|
8426
|
+
if (vpcId && subnetId) {
|
|
8427
|
+
return null;
|
|
8428
|
+
}
|
|
8429
|
+
const rawEnv = options.envParams?.trim();
|
|
8430
|
+
if (!rawEnv) {
|
|
8431
|
+
return null;
|
|
8432
|
+
}
|
|
8433
|
+
let parsed;
|
|
8434
|
+
try {
|
|
8435
|
+
const value = JSON.parse(rawEnv);
|
|
8436
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8437
|
+
return null;
|
|
8438
|
+
}
|
|
8439
|
+
parsed = value;
|
|
8440
|
+
}
|
|
8441
|
+
catch {
|
|
8442
|
+
// Non-JSON EnvParams still may embed connection URLs as plain text.
|
|
8443
|
+
if (!CLOUDRUN_DB_URL_PATTERN.test(rawEnv)) {
|
|
8444
|
+
return null;
|
|
8445
|
+
}
|
|
8446
|
+
return {
|
|
8447
|
+
code: "MISSING_VPC_FOR_DB_ENV",
|
|
8448
|
+
message: "EnvParams appears to include a database/cache connection URL, but serverConfig.VpcConf is missing. CloudRun instances usually cannot reach VPC-private MySQL/PostgreSQL/Redis without VpcConf.",
|
|
8449
|
+
matchedKeys: ["<non-json-envParams>"],
|
|
8450
|
+
remediation: [
|
|
8451
|
+
"Set serverConfig.VpcConf to the same region/VPC (and a subnet with free IPs) as the database.",
|
|
8452
|
+
"Do NOT invent VpcId/SubnetId. Resolve real IDs from the DB console, resource detail, callCloudApi, or the user.",
|
|
8453
|
+
"Use the database private/intranet hostname in EnvParams, not localhost or docker-compose service names.",
|
|
8454
|
+
"Ensure the DB security group allows the CloudRun subnet on the DB port.",
|
|
8455
|
+
"Re-deploy after VpcConf is set. OpenAccessTypes alone does not provide VPC egress to databases.",
|
|
8456
|
+
],
|
|
8457
|
+
};
|
|
8458
|
+
}
|
|
8459
|
+
const matchedKeys = [];
|
|
8460
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
8461
|
+
if (CLOUDRUN_DB_ENV_KEY_PATTERN.test(key)) {
|
|
8462
|
+
matchedKeys.push(key);
|
|
8463
|
+
continue;
|
|
8464
|
+
}
|
|
8465
|
+
if (typeof value === "string" && CLOUDRUN_DB_URL_PATTERN.test(value)) {
|
|
8466
|
+
matchedKeys.push(key);
|
|
8467
|
+
}
|
|
8468
|
+
}
|
|
8469
|
+
if (matchedKeys.length === 0) {
|
|
8470
|
+
return null;
|
|
8471
|
+
}
|
|
8472
|
+
return {
|
|
8473
|
+
code: "MISSING_VPC_FOR_DB_ENV",
|
|
8474
|
+
message: "EnvParams includes database/cache connection settings, but serverConfig.VpcConf is missing. Deploy may succeed while runtime DB connections fail.",
|
|
8475
|
+
matchedKeys,
|
|
8476
|
+
remediation: [
|
|
8477
|
+
"Set serverConfig.VpcConf.VpcId and SubnetId to the database VPC/subnet (same region).",
|
|
8478
|
+
"Do NOT invent VpcId/SubnetId. Resolve real IDs from the DB console, resource detail, callCloudApi, or the user.",
|
|
8479
|
+
"Use the private DB endpoint in EnvParams.",
|
|
8480
|
+
"Confirm security group / allowlist permits CloudRun subnet access to the DB port.",
|
|
8481
|
+
"Do not confuse OpenAccessTypes (ingress) with VpcConf (egress to VPC resources).",
|
|
8482
|
+
],
|
|
8483
|
+
};
|
|
8484
|
+
}
|
|
8429
8485
|
function getCloudRunQueryServerName(input) {
|
|
8430
8486
|
return input.detailServerName || input.serverName;
|
|
8431
8487
|
}
|
|
@@ -8923,6 +8979,17 @@ for await (let x of res.textStream) {
|
|
|
8923
8979
|
if (input.serverConfig) {
|
|
8924
8980
|
deployParams.serverConfig = input.serverConfig;
|
|
8925
8981
|
}
|
|
8982
|
+
// Manager SDK create path prefers top-level vpcInfo (CreateCloudRunServer.VpcInfo).
|
|
8983
|
+
// Map serverConfig.VpcConf → vpcInfo so first-time create actually binds VPC.
|
|
8984
|
+
// Update path still sends Items.VpcConf; platform may ignore VPC changes on existing services.
|
|
8985
|
+
const vpcConf = input.serverConfig?.VpcConf;
|
|
8986
|
+
if (vpcConf?.VpcId?.trim() && vpcConf?.SubnetId?.trim()) {
|
|
8987
|
+
deployParams.vpcInfo = {
|
|
8988
|
+
VpcId: vpcConf.VpcId.trim(),
|
|
8989
|
+
CreateType: 2,
|
|
8990
|
+
SubnetIds: [vpcConf.SubnetId.trim()],
|
|
8991
|
+
};
|
|
8992
|
+
}
|
|
8926
8993
|
let result;
|
|
8927
8994
|
try {
|
|
8928
8995
|
result = await cloudrunService.deploy(deployParams);
|
|
@@ -8996,6 +9063,14 @@ for await (let x of res.textStream) {
|
|
|
8996
9063
|
// Notification failure should not affect deployment flow
|
|
8997
9064
|
// Error is already logged in sendDeployNotification
|
|
8998
9065
|
}
|
|
9066
|
+
const dbNetworkRisk = detectCloudRunDbNetworkRisk({
|
|
9067
|
+
envParams: input.serverConfig?.EnvParams,
|
|
9068
|
+
vpcConf: input.serverConfig?.VpcConf,
|
|
9069
|
+
});
|
|
9070
|
+
const warnings = dbNetworkRisk ? [dbNetworkRisk] : [];
|
|
9071
|
+
const warningSuffix = dbNetworkRisk
|
|
9072
|
+
? ` Warning: ${dbNetworkRisk.message} Set serverConfig.VpcConf before relying on DB connectivity.`
|
|
9073
|
+
: "";
|
|
8999
9074
|
return {
|
|
9000
9075
|
content: [
|
|
9001
9076
|
{
|
|
@@ -9008,9 +9083,10 @@ for await (let x of res.textStream) {
|
|
|
9008
9083
|
deployPath: targetPath,
|
|
9009
9084
|
serverType: serverType,
|
|
9010
9085
|
cloudbasercGenerated: true,
|
|
9011
|
-
consoleUrl
|
|
9086
|
+
consoleUrl,
|
|
9087
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
9012
9088
|
},
|
|
9013
|
-
message: `Triggered deployment for ${serverType} service '${input.serverName}' from ${targetPath}. You can follow the progress in ${consoleUrl}`
|
|
9089
|
+
message: `Triggered deployment for ${serverType} service '${input.serverName}' from ${targetPath}. You can follow the progress in ${consoleUrl}.${warningSuffix}`
|
|
9014
9090
|
}, null, 2)
|
|
9015
9091
|
}
|
|
9016
9092
|
]
|
|
@@ -9886,6 +9962,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
9886
9962
|
};
|
|
9887
9963
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
9888
9964
|
exports.reportToolkitLifecycle = exports.reportToolCall = exports.telemetryReporter = void 0;
|
|
9965
|
+
exports.extractMcpClientInfo = extractMcpClientInfo;
|
|
9966
|
+
exports.readMcpClientInfoFromServer = readMcpClientInfoFromServer;
|
|
9889
9967
|
const crypto_1 = __importDefault(__webpack_require__(7823));
|
|
9890
9968
|
const http_1 = __importDefault(__webpack_require__(3782));
|
|
9891
9969
|
const https_1 = __importDefault(__webpack_require__(3871));
|
|
@@ -9937,7 +10015,7 @@ class TelemetryReporter {
|
|
|
9937
10015
|
const nodeVersion = process.version; // Node.js版本
|
|
9938
10016
|
const arch = os_1.default.arch(); // 系统架构
|
|
9939
10017
|
// 从构建时注入的版本号获取MCP版本信息
|
|
9940
|
-
const mcpVersion = process.env.npm_package_version || "2.
|
|
10018
|
+
const mcpVersion = process.env.npm_package_version || "2.25.0" || 0;
|
|
9941
10019
|
return {
|
|
9942
10020
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
9943
10021
|
deviceId: this.deviceId,
|
|
@@ -10119,6 +10197,49 @@ function resolveEnvId(cloudBaseOptions) {
|
|
|
10119
10197
|
}
|
|
10120
10198
|
return { envId, envIdSource };
|
|
10121
10199
|
}
|
|
10200
|
+
/**
|
|
10201
|
+
* Normalize SDK Implementation / clientInfo into telemetry-safe fields.
|
|
10202
|
+
*/
|
|
10203
|
+
function extractMcpClientInfo(clientVersion) {
|
|
10204
|
+
if (!clientVersion || typeof clientVersion !== "object") {
|
|
10205
|
+
return {};
|
|
10206
|
+
}
|
|
10207
|
+
const info = clientVersion;
|
|
10208
|
+
const name = typeof info.name === "string" ? info.name.trim() : "";
|
|
10209
|
+
const version = typeof info.version === "string" ? info.version.trim() : "";
|
|
10210
|
+
const title = typeof info.title === "string" ? info.title.trim() : "";
|
|
10211
|
+
return {
|
|
10212
|
+
...(name ? { name } : {}),
|
|
10213
|
+
...(version ? { version } : {}),
|
|
10214
|
+
...(title ? { title } : {}),
|
|
10215
|
+
};
|
|
10216
|
+
}
|
|
10217
|
+
/**
|
|
10218
|
+
* Read MCP clientInfo from an McpServer after initialize.
|
|
10219
|
+
* Returns empty object when handshake has not completed or the accessor is unavailable.
|
|
10220
|
+
*/
|
|
10221
|
+
function readMcpClientInfoFromServer(server) {
|
|
10222
|
+
try {
|
|
10223
|
+
return extractMcpClientInfo(server?.server?.getClientVersion?.());
|
|
10224
|
+
}
|
|
10225
|
+
catch {
|
|
10226
|
+
return {};
|
|
10227
|
+
}
|
|
10228
|
+
}
|
|
10229
|
+
function appendMcpClientInfoFields(eventData, clientInfo) {
|
|
10230
|
+
if (!clientInfo) {
|
|
10231
|
+
return;
|
|
10232
|
+
}
|
|
10233
|
+
if (clientInfo.name) {
|
|
10234
|
+
eventData.mcpClientName = clientInfo.name;
|
|
10235
|
+
}
|
|
10236
|
+
if (clientInfo.version) {
|
|
10237
|
+
eventData.mcpClientVersion = clientInfo.version;
|
|
10238
|
+
}
|
|
10239
|
+
if (clientInfo.title) {
|
|
10240
|
+
eventData.mcpClientTitle = clientInfo.title;
|
|
10241
|
+
}
|
|
10242
|
+
}
|
|
10122
10243
|
// 便捷方法
|
|
10123
10244
|
const reportToolCall = async (params) => {
|
|
10124
10245
|
const { nodeVersion, osType, osRelease, arch, mcpVersion } = exports.telemetryReporter.getUserAgent();
|
|
@@ -10163,6 +10284,7 @@ const reportToolCall = async (params) => {
|
|
|
10163
10284
|
if (params.ide) {
|
|
10164
10285
|
eventData.ide = params.ide;
|
|
10165
10286
|
}
|
|
10287
|
+
appendMcpClientInfoFields(eventData, params.mcpClientInfo);
|
|
10166
10288
|
// Debug: 打印最终上报参数
|
|
10167
10289
|
(0, logger_js_1.debug)('[telemetry] 工具调用上报参数', {
|
|
10168
10290
|
toolName: params.toolName,
|
|
@@ -10205,6 +10327,7 @@ const reportToolkitLifecycle = async (params) => {
|
|
|
10205
10327
|
if (params.ide) {
|
|
10206
10328
|
eventData.ide = params.ide;
|
|
10207
10329
|
}
|
|
10330
|
+
appendMcpClientInfoFields(eventData, params.mcpClientInfo);
|
|
10208
10331
|
// Debug: 打印最终上报参数
|
|
10209
10332
|
(0, logger_js_1.debug)('[telemetry] 生命周期事件上报参数', {
|
|
10210
10333
|
event: params.event,
|
|
@@ -10331,8 +10454,12 @@ exports.MANAGE_FUNCTION_ACTIONS = [
|
|
|
10331
10454
|
"incrementalDeployFunction", // 增量部署,需通过 pluginOptions.functions 注入实现
|
|
10332
10455
|
];
|
|
10333
10456
|
const VPC_SCHEMA = zod_1.z.object({
|
|
10334
|
-
vpcId: zod_1.z
|
|
10335
|
-
|
|
10457
|
+
vpcId: zod_1.z
|
|
10458
|
+
.string()
|
|
10459
|
+
.describe("VPC ID from the real database/network console (e.g. vpc-xxxxxxxx). Required for non-native TCP DB access. Do NOT invent or use placeholders."),
|
|
10460
|
+
subnetId: zod_1.z
|
|
10461
|
+
.string()
|
|
10462
|
+
.describe("Subnet ID in the same VPC as the private DB endpoint (e.g. subnet-xxxxxxxx). Do NOT invent or use placeholders."),
|
|
10336
10463
|
});
|
|
10337
10464
|
// 镜像部署值,对应 SCF Runtime=CustomImage
|
|
10338
10465
|
exports.CUSTOM_IMAGE_RUNTIME = "CustomImage";
|
|
@@ -10416,8 +10543,11 @@ const CREATE_FUNCTION_SCHEMA = zod_1.z.object({
|
|
|
10416
10543
|
})
|
|
10417
10544
|
.optional(),
|
|
10418
10545
|
timeout: zod_1.z.number().optional().describe("函数超时时间"),
|
|
10419
|
-
envVariables: zod_1.z
|
|
10420
|
-
|
|
10546
|
+
envVariables: zod_1.z
|
|
10547
|
+
.record(zod_1.z.string())
|
|
10548
|
+
.optional()
|
|
10549
|
+
.describe("环境变量。若包含 DATABASE_URL / MYSQL_* / POSTGRES_* / REDIS_* 等传统 TCP 连库变量,必须同时配置 vpc(vpcId+subnetId),且 ID 必须来自真实库/网络信息,禁止猜测。原生 app.rdb()/app.database() 不需要 VPC。"),
|
|
10550
|
+
vpc: VPC_SCHEMA.optional().describe("私有网络配置(出网)。非原生 SDK、用 TCP 访问 VPC 内 MySQL/PostgreSQL/Redis 时必填。vpcId/subnetId 必须与数据库内网 VPC 一致;未知时先查控制台或询问用户,禁止填占位符。"),
|
|
10421
10551
|
runtime: zod_1.z
|
|
10422
10552
|
.string()
|
|
10423
10553
|
.optional()
|
|
@@ -10671,7 +10801,7 @@ function registerFunctionTools(server) {
|
|
|
10671
10801
|
},
|
|
10672
10802
|
{
|
|
10673
10803
|
tool: "queryGateway",
|
|
10674
|
-
action: "
|
|
10804
|
+
action: "getRoute",
|
|
10675
10805
|
reason: "查看该函数是否已暴露网关访问入口",
|
|
10676
10806
|
},
|
|
10677
10807
|
]);
|
|
@@ -10981,7 +11111,7 @@ function registerFunctionTools(server) {
|
|
|
10981
11111
|
raw: imageResult,
|
|
10982
11112
|
}, `已基于镜像 ${createImageConfig.imageUri} 创建 HTTP 函数 ${functionName}。` +
|
|
10983
11113
|
`请确认 TCR、SCF 与构建管道处于同一地域;如需通过 URL 访问,请显式调用 ` +
|
|
10984
|
-
`manageGateway(action="
|
|
11114
|
+
`manageGateway(action="createRoute", type="HTTP") 并按需调整函数安全规则。` +
|
|
10985
11115
|
`部署后可用 queryFunctions(action="getFunctionDetail") 确认函数已就绪。`, [
|
|
10986
11116
|
{
|
|
10987
11117
|
tool: "queryFunctions",
|
|
@@ -10995,8 +11125,8 @@ function registerFunctionTools(server) {
|
|
|
10995
11125
|
},
|
|
10996
11126
|
{
|
|
10997
11127
|
tool: "manageGateway",
|
|
10998
|
-
action: "
|
|
10999
|
-
reason: "如需通过 URL 访问镜像 HTTP
|
|
11128
|
+
action: "createRoute",
|
|
11129
|
+
reason: "如需通过 URL 访问镜像 HTTP 函数,显式创建 Domain/Route 访问入口并传 type=\"HTTP\"(映射 WEB_SCF)",
|
|
11000
11130
|
},
|
|
11001
11131
|
]);
|
|
11002
11132
|
}
|
|
@@ -11051,12 +11181,12 @@ function registerFunctionTools(server) {
|
|
|
11051
11181
|
if (func.type === "HTTP") {
|
|
11052
11182
|
nextActions.push({
|
|
11053
11183
|
tool: "manageGateway",
|
|
11054
|
-
action: "
|
|
11055
|
-
reason: "如果需要通过 URL 访问 HTTP 函数,请调用 manageGateway(action=\"
|
|
11184
|
+
action: "createRoute",
|
|
11185
|
+
reason: "如果需要通过 URL 访问 HTTP 函数,请调用 manageGateway(action=\"createRoute\") 并显式传 type=\"HTTP\"(映射 WEB_SCF),再按实际路径和鉴权需求创建访问入口,不要默认假设 /函数名 已存在",
|
|
11056
11186
|
});
|
|
11057
11187
|
nextActions.push({
|
|
11058
11188
|
tool: "queryGateway",
|
|
11059
|
-
action: "
|
|
11189
|
+
action: "getRoute",
|
|
11060
11190
|
reason: "交付前确认 HTTP 访问路径是否已存在并已生效",
|
|
11061
11191
|
});
|
|
11062
11192
|
nextActions.push({
|
|
@@ -11071,7 +11201,7 @@ function registerFunctionTools(server) {
|
|
|
11071
11201
|
});
|
|
11072
11202
|
}
|
|
11073
11203
|
const message = func.type === "HTTP"
|
|
11074
|
-
? `已创建 HTTP 函数 ${functionName}。如果后续需要通过 URL 访问,请显式调用 manageGateway(action="
|
|
11204
|
+
? `已创建 HTTP 函数 ${functionName}。如果后续需要通过 URL 访问,请显式调用 manageGateway(action="createRoute"),并把 type="HTTP" 一起传入(映射 UpstreamResourceType=WEB_SCF),再按实际路径和鉴权需求创建访问入口。评测或其他外部调用方可能会以匿名身份访问,而且失败后不一定会把 EXCEED_AUTHORITY 再反馈给 AI;交付前请主动确认访问路径和函数安全规则,若已出现 EXCEED_AUTHORITY,请先调用 queryPermissions(action="getResourcePermission", resourceType="function", resourceId="${functionName}") 查看当前规则,再按需要使用 managePermissions(action="updateResourcePermission") 调整权限。`
|
|
11075
11205
|
: `已创建函数 ${functionName}`;
|
|
11076
11206
|
return buildEnvelope({
|
|
11077
11207
|
action: input.action,
|
|
@@ -11559,8 +11689,11 @@ function registerFunctionTools(server) {
|
|
|
11559
11689
|
zipFile: zod_1.z.string().optional().describe("仅兼容特殊场景:预先准备好的代码包 base64 编码。普通 createFunction/updateFunctionCode 默认不要先压缩 zip,优先使用 functionRootPath。"),
|
|
11560
11690
|
handler: zod_1.z.string().optional().describe("函数入口"),
|
|
11561
11691
|
timeout: zod_1.z.number().optional().describe("配置更新时的超时时间"),
|
|
11562
|
-
envVariables: zod_1.z
|
|
11563
|
-
|
|
11692
|
+
envVariables: zod_1.z
|
|
11693
|
+
.record(zod_1.z.string())
|
|
11694
|
+
.optional()
|
|
11695
|
+
.describe("配置更新时要合并的环境变量。若含 DATABASE_URL / MYSQL_* / POSTGRES_* / REDIS_* 等 TCP 连库变量,必须同时提供真实 vpc(或函数已绑定完整 VPC)。禁止猜测 vpcId/subnetId。"),
|
|
11696
|
+
vpc: VPC_SCHEMA.optional().describe("配置更新时的 VPC 信息。非原生 TCP 连库场景必填真实 vpcId+subnetId;不要用占位符。"),
|
|
11564
11697
|
params: zod_1.z.record(zod_1.z.any()).optional().describe("invokeFunction 的调用参数"),
|
|
11565
11698
|
triggers: zod_1.z
|
|
11566
11699
|
.array(TRIGGER_SCHEMA)
|
|
@@ -14194,7 +14327,7 @@ function registerSetupTools(server) {
|
|
|
14194
14327
|
server.registerTool("downloadTemplate", {
|
|
14195
14328
|
title: "下载项目模板",
|
|
14196
14329
|
description: `自动下载并部署CloudBase项目模板。
|
|
14197
|
-
**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.
|
|
14330
|
+
**Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.25.0" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
|
|
14198
14331
|
inputSchema: {
|
|
14199
14332
|
template: zod_1.z
|
|
14200
14333
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|