@cloudbase/cloudbase-mcp 2.24.1 → 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 +1312 -371
- package/dist/index.js +330 -278
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -684,8 +684,8 @@ async function enrichEnvInfoWithRuntimeMode(result, manager) {
|
|
|
684
684
|
: "No legacy NoSQL Database/Storage observed in this env.",
|
|
685
685
|
MysqlNotAvailable: hasMysql
|
|
686
686
|
? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
|
|
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-
|
|
688
|
-
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.",
|
|
689
689
|
}
|
|
690
690
|
: {
|
|
691
691
|
PrimaryBackend: "PostgreSQL is NOT provisioned in this env — this is a legacy NoSQL CloudBase backend.",
|
|
@@ -694,8 +694,8 @@ async function enrichEnvInfoWithRuntimeMode(result, manager) {
|
|
|
694
694
|
Storage: "Browser uploads use `app.uploadFile()` against the bucket exposed in `EnvInfo.Storages[].Bucket`.",
|
|
695
695
|
MysqlNotAvailable: hasMysql
|
|
696
696
|
? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
|
|
697
|
-
: "No MySQL instance in this env. `manageMysqlDatabase` / `queryMysqlDatabase` and the `relational-database-
|
|
698
|
-
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.",
|
|
699
699
|
};
|
|
700
700
|
return {
|
|
701
701
|
...result,
|
|
@@ -1616,7 +1616,7 @@ function registerEnvTools(server) {
|
|
|
1616
1616
|
// Register primary tool name (queryEnv)
|
|
1617
1617
|
const queryEnvToolSchema = {
|
|
1618
1618
|
title: "CloudBase 环境查询",
|
|
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-
|
|
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 都不该使用。",
|
|
1620
1620
|
inputSchema: {
|
|
1621
1621
|
action: zod_1.z
|
|
1622
1622
|
.enum(["list", "info", "domains"])
|
|
@@ -2233,7 +2233,7 @@ ${envIdSection}
|
|
|
2233
2233
|
## 环境信息
|
|
2234
2234
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
2235
2235
|
- Node.js版本: ${process.version}
|
|
2236
|
-
- MCP 版本:${process.env.npm_package_version || "2.
|
|
2236
|
+
- MCP 版本:${process.env.npm_package_version || "2.25.0" || 0}
|
|
2237
2237
|
- 系统架构: ${os_1.default.arch()}
|
|
2238
2238
|
- 时间: ${new Date().toISOString()}
|
|
2239
2239
|
- 请求ID: ${requestId}
|
|
@@ -2314,7 +2314,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
2314
2314
|
requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
|
|
2315
2315
|
ide: server.ide || process.env.INTEGRATION_IDE || ''
|
|
2316
2316
|
});
|
|
2317
|
-
const mcpVersion = true ? "2.
|
|
2317
|
+
const mcpVersion = true ? "2.25.0" : 0;
|
|
2318
2318
|
const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
|
|
2319
2319
|
const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
|
|
2320
2320
|
// 创建新的错误对象,保持原有的错误类型但更新消息
|
|
@@ -7153,21 +7153,23 @@ const zod_1 = __webpack_require__(2971);
|
|
|
7153
7153
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
7154
7154
|
const json_content_js_1 = __webpack_require__(4365);
|
|
7155
7155
|
const QUERY_GATEWAY_ACTIONS = [
|
|
7156
|
-
"getAccess",
|
|
7157
|
-
"listDomains",
|
|
7158
7156
|
"listRoutes",
|
|
7159
7157
|
"getRoute",
|
|
7160
7158
|
"listCustomDomains",
|
|
7161
7159
|
];
|
|
7162
7160
|
const MANAGE_GATEWAY_ACTIONS = [
|
|
7163
|
-
"createAccess",
|
|
7164
7161
|
"createRoute",
|
|
7165
7162
|
"updateRoute",
|
|
7166
7163
|
"deleteRoute",
|
|
7167
7164
|
"bindCustomDomain",
|
|
7168
7165
|
"deleteCustomDomain",
|
|
7169
|
-
|
|
7170
|
-
|
|
7166
|
+
];
|
|
7167
|
+
const UPSTREAM_RESOURCE_TYPES = [
|
|
7168
|
+
"SCF",
|
|
7169
|
+
"WEB_SCF",
|
|
7170
|
+
"CBR",
|
|
7171
|
+
"STATIC_STORE",
|
|
7172
|
+
"LH",
|
|
7171
7173
|
];
|
|
7172
7174
|
function normalizeAccessPath(path) {
|
|
7173
7175
|
if (!path) {
|
|
@@ -7198,24 +7200,11 @@ function registerGatewayTools(server) {
|
|
|
7198
7200
|
return (0, json_content_js_1.jsonContent)(buildErrorEnvelope(error));
|
|
7199
7201
|
}
|
|
7200
7202
|
};
|
|
7201
|
-
const listGatewayDomains = async () => {
|
|
7202
|
-
const cloudbase = await getManager();
|
|
7203
|
-
const result = await cloudbase.access.getDomainList();
|
|
7204
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7205
|
-
const domains = [
|
|
7206
|
-
result.DefaultDomain,
|
|
7207
|
-
...(result.ServiceSet || []).map((item) => item.Domain),
|
|
7208
|
-
].filter(Boolean);
|
|
7209
|
-
return {
|
|
7210
|
-
domains,
|
|
7211
|
-
enableService: result.EnableService,
|
|
7212
|
-
raw: result,
|
|
7213
|
-
};
|
|
7214
|
-
};
|
|
7215
7203
|
const listHttpServiceRoutes = async (domain) => {
|
|
7216
7204
|
const cloudbase = await getManager();
|
|
7217
7205
|
const result = await cloudbase.env.describeHttpServiceRoute({
|
|
7218
7206
|
EnvId: await resolveEnvId(),
|
|
7207
|
+
Limit: 1000,
|
|
7219
7208
|
...(domain
|
|
7220
7209
|
? {
|
|
7221
7210
|
Filters: [
|
|
@@ -7231,123 +7220,180 @@ function registerGatewayTools(server) {
|
|
|
7231
7220
|
return result;
|
|
7232
7221
|
};
|
|
7233
7222
|
const flattenRoutes = (result) => (result.Domains ?? []).flatMap((domainItem) => (domainItem.Routes ?? []).map((route) => ({
|
|
7234
|
-
Domain: domainItem.Domain,
|
|
7223
|
+
Domain: domainItem.Domain ?? "",
|
|
7235
7224
|
DomainType: domainItem.DomainType,
|
|
7236
7225
|
AccessType: domainItem.AccessType,
|
|
7226
|
+
IsDefault: domainItem.IsDefault,
|
|
7237
7227
|
...route,
|
|
7238
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
|
+
};
|
|
7239
7244
|
const resolveRouteDomain = async (preferredDomain) => {
|
|
7240
7245
|
if (preferredDomain) {
|
|
7241
7246
|
return preferredDomain;
|
|
7242
7247
|
}
|
|
7243
|
-
|
|
7244
|
-
return routeInfo.OriginDomain;
|
|
7248
|
+
return resolveDefaultHttpDomain();
|
|
7245
7249
|
};
|
|
7246
|
-
const
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
if (
|
|
7251
|
-
|
|
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";
|
|
7252
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;
|
|
7253
7288
|
return {
|
|
7254
7289
|
EnvId: await resolveEnvId(),
|
|
7255
7290
|
Domain: {
|
|
7256
|
-
Domain:
|
|
7291
|
+
Domain: domain,
|
|
7257
7292
|
Routes: [
|
|
7258
7293
|
{
|
|
7259
7294
|
Path: path,
|
|
7260
|
-
UpstreamResourceType:
|
|
7261
|
-
UpstreamResourceName:
|
|
7262
|
-
EnableAuth:
|
|
7263
|
-
? route.auth
|
|
7264
|
-
: fallback.auth !== undefined
|
|
7265
|
-
? fallback.auth
|
|
7266
|
-
: undefined,
|
|
7295
|
+
UpstreamResourceType: upstreamResourceType,
|
|
7296
|
+
UpstreamResourceName: upstreamResourceName,
|
|
7297
|
+
EnableAuth: enableAuth,
|
|
7267
7298
|
},
|
|
7268
7299
|
],
|
|
7269
7300
|
},
|
|
7301
|
+
resolved: {
|
|
7302
|
+
domain,
|
|
7303
|
+
path,
|
|
7304
|
+
upstreamResourceType,
|
|
7305
|
+
upstreamResourceName,
|
|
7306
|
+
enableAuth,
|
|
7307
|
+
},
|
|
7270
7308
|
};
|
|
7271
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
|
+
];
|
|
7272
7327
|
const handleQueryGateway = async (input) => {
|
|
7273
7328
|
switch (input.action) {
|
|
7274
|
-
case "listDomains": {
|
|
7275
|
-
const result = await listGatewayDomains();
|
|
7276
|
-
return buildEnvelope({
|
|
7277
|
-
action: input.action,
|
|
7278
|
-
domains: result.domains,
|
|
7279
|
-
enableService: result.enableService,
|
|
7280
|
-
raw: result.raw,
|
|
7281
|
-
}, `已获取 ${result.domains.length} 个网关域名`, [
|
|
7282
|
-
{
|
|
7283
|
-
tool: "queryGateway",
|
|
7284
|
-
action: "getAccess",
|
|
7285
|
-
reason: "查看某个目标当前的访问入口",
|
|
7286
|
-
},
|
|
7287
|
-
]);
|
|
7288
|
-
}
|
|
7289
7329
|
case "listCustomDomains": {
|
|
7290
|
-
const result = await
|
|
7330
|
+
const result = await listHttpServiceRoutes();
|
|
7331
|
+
const customDomains = (result.Domains ?? []).filter((item) => item.IsDefault !== true);
|
|
7291
7332
|
return buildEnvelope({
|
|
7292
7333
|
action: input.action,
|
|
7293
|
-
domains:
|
|
7294
|
-
total:
|
|
7295
|
-
raw: result
|
|
7296
|
-
}, `已获取 ${
|
|
7334
|
+
domains: customDomains,
|
|
7335
|
+
total: customDomains.length,
|
|
7336
|
+
raw: result,
|
|
7337
|
+
}, `已获取 ${customDomains.length} 个自定义域名`);
|
|
7297
7338
|
}
|
|
7298
7339
|
case "listRoutes": {
|
|
7299
|
-
const result = await listHttpServiceRoutes();
|
|
7340
|
+
const result = await listHttpServiceRoutes(input.domain);
|
|
7300
7341
|
const routes = flattenRoutes(result);
|
|
7301
7342
|
return buildEnvelope({
|
|
7302
7343
|
action: input.action,
|
|
7303
7344
|
routes,
|
|
7345
|
+
urls: buildRouteUrls(routes),
|
|
7304
7346
|
total: result.TotalCount ?? routes.length,
|
|
7305
7347
|
raw: result,
|
|
7306
7348
|
}, `已获取 ${result.TotalCount ?? routes.length} 条 HTTP 路由`);
|
|
7307
7349
|
}
|
|
7308
7350
|
case "getRoute": {
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
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);
|
|
7312
7377
|
return buildEnvelope({
|
|
7313
7378
|
action: input.action,
|
|
7314
7379
|
routeId: input.routeId ?? null,
|
|
7380
|
+
targetName: input.targetName ?? null,
|
|
7381
|
+
path: normalizedPath ?? null,
|
|
7382
|
+
domain: input.domain ?? null,
|
|
7315
7383
|
route,
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
}
|
|
7319
|
-
case "getAccess": {
|
|
7320
|
-
if (!input.targetName) {
|
|
7321
|
-
throw new Error("getAccess 操作时,targetName 参数是必需的");
|
|
7322
|
-
}
|
|
7323
|
-
const cloudbase = await getManager();
|
|
7324
|
-
const [accessList, domainInfo] = await Promise.all([
|
|
7325
|
-
cloudbase.access.getAccessList({ name: input.targetName }),
|
|
7326
|
-
listGatewayDomains(),
|
|
7327
|
-
]);
|
|
7328
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, accessList);
|
|
7329
|
-
const urls = Array.from(new Set((accessList.APISet || []).flatMap((api) => domainInfo.domains.map((domain) => {
|
|
7330
|
-
const normalizedPath = normalizeAccessPath(api.Path);
|
|
7331
|
-
return `https://${domain}${normalizedPath}`;
|
|
7332
|
-
}))));
|
|
7333
|
-
return buildEnvelope({
|
|
7334
|
-
action: input.action,
|
|
7335
|
-
targetType: input.targetType ?? "function",
|
|
7336
|
-
targetName: input.targetName,
|
|
7337
|
-
apis: accessList.APISet || [],
|
|
7338
|
-
total: accessList.Total || 0,
|
|
7339
|
-
domains: domainInfo.domains,
|
|
7384
|
+
routes: matches,
|
|
7385
|
+
total: matches.length,
|
|
7340
7386
|
urls,
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7387
|
+
raw: result,
|
|
7388
|
+
}, matches.length === 0
|
|
7389
|
+
? "未找到对应路由"
|
|
7390
|
+
: matches.length === 1
|
|
7391
|
+
? "已获取路由详情"
|
|
7392
|
+
: `匹配到 ${matches.length} 条路由,请补充 path 或 domain 精确定位`, [
|
|
7347
7393
|
{
|
|
7348
7394
|
tool: "manageGateway",
|
|
7349
|
-
action: "
|
|
7350
|
-
reason: "
|
|
7395
|
+
action: "createRoute",
|
|
7396
|
+
reason: "为该目标新增 Domain/Route 访问路径",
|
|
7351
7397
|
},
|
|
7352
7398
|
]);
|
|
7353
7399
|
}
|
|
@@ -7357,78 +7403,60 @@ function registerGatewayTools(server) {
|
|
|
7357
7403
|
};
|
|
7358
7404
|
const handleManageGateway = async (input) => {
|
|
7359
7405
|
switch (input.action) {
|
|
7360
|
-
case "
|
|
7361
|
-
if (!input.targetName || !input.targetType) {
|
|
7362
|
-
throw new Error("action=createAccess 时必须提供 targetType 和 targetName");
|
|
7363
|
-
}
|
|
7364
|
-
if (!input.type) {
|
|
7365
|
-
throw new Error("action=createAccess 且 targetType=function 时必须显式提供 type。HTTP 云函数传 HTTP;Event 函数传 Event。省略会默认按 Event 路由处理,可能让 HTTP 云函数访问后返回 FUNCTION_PARAM_INVALID。");
|
|
7366
|
-
}
|
|
7406
|
+
case "createRoute": {
|
|
7367
7407
|
const cloudbase = await getManager();
|
|
7368
|
-
const
|
|
7408
|
+
const payload = await normalizeRoutePayload(input);
|
|
7369
7409
|
let result;
|
|
7370
7410
|
try {
|
|
7371
|
-
result = await cloudbase.
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
type: (input.type === "HTTP" ? 6 : 1),
|
|
7375
|
-
auth: input.auth,
|
|
7411
|
+
result = await cloudbase.env.createHttpServiceRoute({
|
|
7412
|
+
EnvId: payload.EnvId,
|
|
7413
|
+
Domain: payload.Domain,
|
|
7376
7414
|
});
|
|
7377
7415
|
}
|
|
7378
7416
|
catch (err) {
|
|
7379
|
-
|
|
7380
|
-
|
|
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) 该访问路径未被占用。";
|
|
7381
7420
|
if (input.type === "HTTP") {
|
|
7382
|
-
hint +=
|
|
7421
|
+
hint +=
|
|
7422
|
+
"此外注意:如果目标函数最初是作为 Event 函数创建的,这里 type 仍必须传 Event(UpstreamResourceType=SCF);误传 HTTP/WEB_SCF 会导致此错误。";
|
|
7383
7423
|
}
|
|
7384
|
-
throw new Error(`${hint} 原始错误:${
|
|
7424
|
+
throw new Error(`${hint} 原始错误:${message}`);
|
|
7385
7425
|
}
|
|
7386
7426
|
throw err;
|
|
7387
7427
|
}
|
|
7388
7428
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7389
7429
|
return buildEnvelope({
|
|
7390
7430
|
action: input.action,
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
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,
|
|
7394
7439
|
raw: result,
|
|
7395
|
-
}, `已为目标 ${
|
|
7396
|
-
{
|
|
7397
|
-
tool: "queryGateway",
|
|
7398
|
-
action: "getAccess",
|
|
7399
|
-
reason: "等待 30 秒到 3 分钟后再确认访问入口是否已生效",
|
|
7400
|
-
},
|
|
7401
|
-
{
|
|
7402
|
-
tool: "queryPermissions",
|
|
7403
|
-
action: "getResourcePermission",
|
|
7404
|
-
reason: "确认函数安全规则是否允许预期访问方;网关 auth=false 不等于函数已允许匿名访问",
|
|
7405
|
-
},
|
|
7406
|
-
{
|
|
7407
|
-
tool: "managePermissions",
|
|
7408
|
-
action: "updateResourcePermission",
|
|
7409
|
-
reason: "只有在确认需要匿名或浏览器直连访问时,才按实际安全要求更新函数权限",
|
|
7410
|
-
},
|
|
7411
|
-
]);
|
|
7412
|
-
}
|
|
7413
|
-
case "createRoute": {
|
|
7414
|
-
const cloudbase = await getManager();
|
|
7415
|
-
const result = await cloudbase.env.createHttpServiceRoute((await normalizeRoutePayload(input.route, input, input.domain)));
|
|
7416
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7417
|
-
return buildEnvelope({
|
|
7418
|
-
action: input.action,
|
|
7419
|
-
route: input.route ?? null,
|
|
7420
|
-
raw: result,
|
|
7421
|
-
}, "HTTP 路由创建成功");
|
|
7440
|
+
}, `已为目标 ${payload.resolved.upstreamResourceName} 在域名 ${payload.resolved.domain} 创建路由 ${payload.resolved.path}(${payload.resolved.upstreamResourceType})。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。该操作只创建网关入口,不会自动放开函数安全规则;若需要匿名或浏览器直接访问,请继续检查函数资源权限。`, routeMutationNextActions(payload.resolved.upstreamResourceName));
|
|
7422
7441
|
}
|
|
7423
7442
|
case "updateRoute": {
|
|
7424
7443
|
const cloudbase = await getManager();
|
|
7425
|
-
const
|
|
7444
|
+
const payload = await normalizeRoutePayload(input);
|
|
7445
|
+
const result = await cloudbase.env.modifyHttpServiceRoute({
|
|
7446
|
+
EnvId: payload.EnvId,
|
|
7447
|
+
Domain: payload.Domain,
|
|
7448
|
+
});
|
|
7426
7449
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7427
7450
|
return buildEnvelope({
|
|
7428
7451
|
action: input.action,
|
|
7429
|
-
|
|
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,
|
|
7430
7458
|
raw: result,
|
|
7431
|
-
},
|
|
7459
|
+
}, `HTTP 路由更新成功(${payload.resolved.domain}${payload.resolved.path})`, routeMutationNextActions(payload.resolved.upstreamResourceName));
|
|
7432
7460
|
}
|
|
7433
7461
|
case "deleteRoute": {
|
|
7434
7462
|
const routePath = input.route?.path ?? input.path;
|
|
@@ -7437,16 +7465,18 @@ function registerGatewayTools(server) {
|
|
|
7437
7465
|
}
|
|
7438
7466
|
const cloudbase = await getManager();
|
|
7439
7467
|
const domain = await resolveRouteDomain(input.domain);
|
|
7468
|
+
const normalizedPath = normalizeAccessPath(routePath);
|
|
7440
7469
|
const result = await cloudbase.env.deleteHttpServiceRoute({
|
|
7441
7470
|
EnvId: await resolveEnvId(),
|
|
7442
7471
|
Domain: domain,
|
|
7443
|
-
Paths: [
|
|
7472
|
+
Paths: [normalizedPath],
|
|
7444
7473
|
});
|
|
7445
7474
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7446
7475
|
return buildEnvelope({
|
|
7447
7476
|
action: input.action,
|
|
7477
|
+
model: "httpServiceRoute",
|
|
7448
7478
|
domain,
|
|
7449
|
-
path:
|
|
7479
|
+
path: normalizedPath,
|
|
7450
7480
|
raw: result,
|
|
7451
7481
|
}, "HTTP 路由删除成功");
|
|
7452
7482
|
}
|
|
@@ -7486,114 +7516,34 @@ function registerGatewayTools(server) {
|
|
|
7486
7516
|
raw: result,
|
|
7487
7517
|
}, "自定义域名删除成功");
|
|
7488
7518
|
}
|
|
7489
|
-
case "deleteAccess": {
|
|
7490
|
-
const cloudbase = await getManager();
|
|
7491
|
-
const accessPath = input.path ? normalizeAccessPath(input.path) : undefined;
|
|
7492
|
-
let resolvedAccessId = input.accessId;
|
|
7493
|
-
let resolvedName;
|
|
7494
|
-
if (!resolvedAccessId) {
|
|
7495
|
-
if (!input.targetName && !accessPath) {
|
|
7496
|
-
throw new Error("action=deleteAccess 时至少需要提供 accessId,或提供 targetName/path 用于定位入口");
|
|
7497
|
-
}
|
|
7498
|
-
const accessList = await cloudbase.access.getAccessList({
|
|
7499
|
-
...(input.targetName ? { name: input.targetName } : {}),
|
|
7500
|
-
...(accessPath ? { path: accessPath } : {}),
|
|
7501
|
-
});
|
|
7502
|
-
const candidates = (accessList.APISet ?? []).filter((item) => {
|
|
7503
|
-
if (!accessPath) {
|
|
7504
|
-
return true;
|
|
7505
|
-
}
|
|
7506
|
-
return normalizeAccessPath(item.Path) === accessPath;
|
|
7507
|
-
});
|
|
7508
|
-
if (candidates.length === 0) {
|
|
7509
|
-
throw new Error("未找到可删除的访问入口,请确认 targetName/path 或直接传 accessId");
|
|
7510
|
-
}
|
|
7511
|
-
if (candidates.length > 1 && !accessPath) {
|
|
7512
|
-
throw new Error("匹配到多个访问入口,请补充 path 或直接传 accessId");
|
|
7513
|
-
}
|
|
7514
|
-
resolvedAccessId = candidates[0]?.APIId;
|
|
7515
|
-
resolvedName = candidates[0]?.Name;
|
|
7516
|
-
}
|
|
7517
|
-
else {
|
|
7518
|
-
// 仅传入 accessId 时,先查询获取 name 等信息,确保删除参数完整
|
|
7519
|
-
try {
|
|
7520
|
-
const accessList = await cloudbase.access.getAccessList({});
|
|
7521
|
-
const match = (accessList.APISet ?? []).find((item) => item.APIId === resolvedAccessId);
|
|
7522
|
-
if (match) {
|
|
7523
|
-
resolvedName = match.Name;
|
|
7524
|
-
}
|
|
7525
|
-
}
|
|
7526
|
-
catch {
|
|
7527
|
-
// 查询失败不影响主流程,继续用 accessId 删除
|
|
7528
|
-
}
|
|
7529
|
-
}
|
|
7530
|
-
if (!resolvedAccessId) {
|
|
7531
|
-
throw new Error("无法解析访问入口 accessId,请改为显式传入 accessId");
|
|
7532
|
-
}
|
|
7533
|
-
const result = await cloudbase.access.deleteAccess({
|
|
7534
|
-
...(resolvedName ? { name: resolvedName } : {}),
|
|
7535
|
-
apiId: resolvedAccessId,
|
|
7536
|
-
});
|
|
7537
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7538
|
-
return buildEnvelope({
|
|
7539
|
-
action: input.action,
|
|
7540
|
-
targetName: resolvedName ?? input.targetName ?? null,
|
|
7541
|
-
path: accessPath ?? null,
|
|
7542
|
-
accessId: resolvedAccessId,
|
|
7543
|
-
raw: result,
|
|
7544
|
-
}, "网关访问入口删除成功");
|
|
7545
|
-
}
|
|
7546
|
-
case "updatePathAuth": {
|
|
7547
|
-
if (!input.targetName && !input.path) {
|
|
7548
|
-
throw new Error("action=updatePathAuth 时至少需要提供 targetName 或 path");
|
|
7549
|
-
}
|
|
7550
|
-
if (input.auth === undefined) {
|
|
7551
|
-
throw new Error("action=updatePathAuth 时必须提供 auth");
|
|
7552
|
-
}
|
|
7553
|
-
const cloudbase = await getManager();
|
|
7554
|
-
const accessPath = input.path ? normalizeAccessPath(input.path) : undefined;
|
|
7555
|
-
const accessList = await cloudbase.access.getAccessList({
|
|
7556
|
-
...(input.targetName ? { name: input.targetName } : {}),
|
|
7557
|
-
...(accessPath ? { path: accessPath } : {}),
|
|
7558
|
-
});
|
|
7559
|
-
const apiIds = (accessList.APISet ?? []).map((item) => item.APIId).filter(Boolean);
|
|
7560
|
-
if (apiIds.length === 0) {
|
|
7561
|
-
throw new Error("未找到可更新鉴权状态的访问入口");
|
|
7562
|
-
}
|
|
7563
|
-
const result = await cloudbase.access.switchPathAuth({
|
|
7564
|
-
apiIds,
|
|
7565
|
-
auth: input.auth,
|
|
7566
|
-
});
|
|
7567
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7568
|
-
return buildEnvelope({
|
|
7569
|
-
action: input.action,
|
|
7570
|
-
targetName: input.targetName ?? null,
|
|
7571
|
-
path: accessPath ?? null,
|
|
7572
|
-
auth: input.auth,
|
|
7573
|
-
apiIds,
|
|
7574
|
-
raw: result,
|
|
7575
|
-
}, "网关路径鉴权更新成功");
|
|
7576
|
-
}
|
|
7577
7519
|
default:
|
|
7578
7520
|
throw new Error(`不支持的操作类型: ${input.action}`);
|
|
7579
7521
|
}
|
|
7580
7522
|
};
|
|
7581
7523
|
server.registerTool?.("queryGateway", {
|
|
7582
7524
|
title: "查询 CloudBase 网关",
|
|
7583
|
-
description: "CloudBase
|
|
7525
|
+
description: "CloudBase 网关统一只读入口(Domain/Route 模型)。通过 listRoutes / getRoute / listCustomDomains 查询域名与路由;主键为 Domain + Path,上游类型为 SCF / WEB_SCF / CBR / STATIC_STORE / LH。",
|
|
7584
7526
|
inputSchema: {
|
|
7585
7527
|
action: zod_1.z
|
|
7586
7528
|
.enum(QUERY_GATEWAY_ACTIONS)
|
|
7587
|
-
.describe("
|
|
7529
|
+
.describe("只读操作类型:listRoutes、getRoute、listCustomDomains"),
|
|
7588
7530
|
targetType: zod_1.z
|
|
7589
7531
|
.enum(["function"])
|
|
7590
7532
|
.optional()
|
|
7591
|
-
.describe("目标资源类型。当前支持 function
|
|
7533
|
+
.describe("目标资源类型。当前支持 function"),
|
|
7592
7534
|
targetName: zod_1.z
|
|
7593
7535
|
.string()
|
|
7594
7536
|
.optional()
|
|
7595
|
-
.describe("
|
|
7537
|
+
.describe("上游资源名称。getRoute 时可按云函数名过滤"),
|
|
7596
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 过滤时可选"),
|
|
7597
7547
|
},
|
|
7598
7548
|
annotations: {
|
|
7599
7549
|
readOnlyHint: true,
|
|
@@ -7603,19 +7553,19 @@ function registerGatewayTools(server) {
|
|
|
7603
7553
|
}, async (input) => withEnvelope(() => handleQueryGateway(input)));
|
|
7604
7554
|
server.registerTool?.("manageGateway", {
|
|
7605
7555
|
title: "管理 CloudBase 网关",
|
|
7606
|
-
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。",
|
|
7607
7557
|
inputSchema: {
|
|
7608
7558
|
action: zod_1.z
|
|
7609
7559
|
.enum(MANAGE_GATEWAY_ACTIONS)
|
|
7610
|
-
.describe(
|
|
7560
|
+
.describe("写操作类型。为已有函数补默认域名访问入口时使用 createRoute;函数场景必须显式提供 type(HTTP→WEB_SCF,Event→SCF)或 route.upstreamResourceType。"),
|
|
7611
7561
|
targetType: zod_1.z
|
|
7612
7562
|
.enum(["function"])
|
|
7613
7563
|
.optional()
|
|
7614
|
-
.describe("目标资源类型。当前支持 function
|
|
7564
|
+
.describe("目标资源类型。当前支持 function"),
|
|
7615
7565
|
targetName: zod_1.z
|
|
7616
7566
|
.string()
|
|
7617
7567
|
.optional()
|
|
7618
|
-
.describe("目标资源名称。
|
|
7568
|
+
.describe("目标资源名称。createRoute 到云函数时填写函数名"),
|
|
7619
7569
|
path: zod_1.z
|
|
7620
7570
|
.string()
|
|
7621
7571
|
.optional()
|
|
@@ -7623,25 +7573,28 @@ function registerGatewayTools(server) {
|
|
|
7623
7573
|
type: zod_1.z
|
|
7624
7574
|
.enum(["Event", "HTTP"])
|
|
7625
7575
|
.optional()
|
|
7626
|
-
.describe("
|
|
7576
|
+
.describe("目标函数运行时类型,不是接入协议。HTTP 云函数传 HTTP(UpstreamResourceType=WEB_SCF);Event 函数传 Event(SCF)。误标会导致 FUNCTION_PARAM_INVALID 或网关错误。函数路由场景必须显式提供 type 或 route.upstreamResourceType。"),
|
|
7627
7577
|
auth: zod_1.z
|
|
7628
7578
|
.boolean()
|
|
7629
7579
|
.optional()
|
|
7630
|
-
.describe("
|
|
7580
|
+
.describe("是否开启网关路径鉴权(EnableAuth)。若要走默认域名做匿名或浏览器访问,通常设为 false;该开关仅控制网关入口本身,不会修改函数资源权限。"),
|
|
7631
7581
|
route: zod_1.z
|
|
7632
7582
|
.object({
|
|
7633
|
-
routeId: zod_1.z.string().optional(),
|
|
7634
7583
|
path: zod_1.z.string().optional(),
|
|
7635
|
-
serviceType: zod_1.z.string().optional(),
|
|
7636
7584
|
serviceName: zod_1.z.string().optional(),
|
|
7585
|
+
upstreamResourceType: zod_1.z.enum(UPSTREAM_RESOURCE_TYPES).optional(),
|
|
7637
7586
|
auth: zod_1.z.boolean().optional(),
|
|
7638
7587
|
})
|
|
7639
7588
|
.optional()
|
|
7640
|
-
.describe("HTTP
|
|
7641
|
-
domain: zod_1.z
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
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 时必填"),
|
|
7645
7598
|
},
|
|
7646
7599
|
annotations: {
|
|
7647
7600
|
readOnlyHint: false,
|
|
@@ -8288,6 +8241,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8288
8241
|
};
|
|
8289
8242
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
8290
8243
|
exports.CLOUDRUN_ACCESS_TYPES = exports.CLOUDRUN_SERVICE_TYPES = void 0;
|
|
8244
|
+
exports.detectCloudRunDbNetworkRisk = detectCloudRunDbNetworkRisk;
|
|
8291
8245
|
exports.registerCloudRunTools = registerCloudRunTools;
|
|
8292
8246
|
const child_process_1 = __webpack_require__(5814);
|
|
8293
8247
|
const fs_1 = __importDefault(__webpack_require__(4421));
|
|
@@ -8330,7 +8284,7 @@ const ManageCloudRunInputSchema = {
|
|
|
8330
8284
|
})).optional().describe('扩缩容配置数组,用于配置服务的自动扩缩容策略。可配置多个扩缩容策略'),
|
|
8331
8285
|
CustomLogs: zod_1.z.string().optional().describe('自定义日志配置,用于配置服务的日志收集和存储策略'),
|
|
8332
8286
|
Port: zod_1.z.number().min(1).max(65535).optional().describe('服务监听端口配置。函数型服务固定为3000,容器型服务可自定义。服务代码必须监听此端口才能正常接收请求'),
|
|
8333
|
-
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 内数据库'),
|
|
8334
8288
|
Dockerfile: zod_1.z.string().optional().describe('Dockerfile文件名配置,仅容器型服务需要。指定用于构建容器镜像的Dockerfile文件路径,默认为项目根目录下的Dockerfile'),
|
|
8335
8289
|
BuildDir: zod_1.z.string().optional().describe('构建目录配置,指定代码构建的目录路径。当代码结构与标准不同时使用,默认为项目根目录'),
|
|
8336
8290
|
InternalAccess: zod_1.z.string().optional().describe('内网访问开关配置,控制是否启用内网访问。true=启用内网访问(可通过云开发SDK直接调用),false=关闭内网访问(仅公网访问)'),
|
|
@@ -8354,9 +8308,9 @@ const ManageCloudRunInputSchema = {
|
|
|
8354
8308
|
ReplicaNum: zod_1.z.number().min(0).describe('定时扩缩容的目标副本数,最小值0(缩容到0)')
|
|
8355
8309
|
})).optional().describe('定时扩缩容配置数组,用于配置服务的定时自动扩缩容策略。可配置多个时间段的扩缩容计划,支持每日/每周/每月循环'),
|
|
8356
8310
|
VpcConf: zod_1.z.object({
|
|
8357
|
-
VpcId: zod_1.z.string().describe('VPC网络ID
|
|
8358
|
-
SubnetId: zod_1.z.string().describe('子网ID
|
|
8359
|
-
}).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,未生效再走控制台网络设置或删建兜底'),
|
|
8360
8314
|
VolumesConf: zod_1.z.array(zod_1.z.object({
|
|
8361
8315
|
VolumeName: zod_1.z.string().describe('存储卷名称'),
|
|
8362
8316
|
VolumeType: zod_1.z.string().describe('存储卷类型,如CFS表示云文件存储'),
|
|
@@ -8460,6 +8414,74 @@ function buildManageCloudRunErrorMessage(action, serverName, error) {
|
|
|
8460
8414
|
}
|
|
8461
8415
|
return `[manageCloudRun/${action}] ${baseMessage}\n建议:${suggestions.join(" ")}`;
|
|
8462
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
|
+
}
|
|
8463
8485
|
function getCloudRunQueryServerName(input) {
|
|
8464
8486
|
return input.detailServerName || input.serverName;
|
|
8465
8487
|
}
|
|
@@ -8957,6 +8979,17 @@ for await (let x of res.textStream) {
|
|
|
8957
8979
|
if (input.serverConfig) {
|
|
8958
8980
|
deployParams.serverConfig = input.serverConfig;
|
|
8959
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
|
+
}
|
|
8960
8993
|
let result;
|
|
8961
8994
|
try {
|
|
8962
8995
|
result = await cloudrunService.deploy(deployParams);
|
|
@@ -9030,6 +9063,14 @@ for await (let x of res.textStream) {
|
|
|
9030
9063
|
// Notification failure should not affect deployment flow
|
|
9031
9064
|
// Error is already logged in sendDeployNotification
|
|
9032
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
|
+
: "";
|
|
9033
9074
|
return {
|
|
9034
9075
|
content: [
|
|
9035
9076
|
{
|
|
@@ -9042,9 +9083,10 @@ for await (let x of res.textStream) {
|
|
|
9042
9083
|
deployPath: targetPath,
|
|
9043
9084
|
serverType: serverType,
|
|
9044
9085
|
cloudbasercGenerated: true,
|
|
9045
|
-
consoleUrl
|
|
9086
|
+
consoleUrl,
|
|
9087
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
9046
9088
|
},
|
|
9047
|
-
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}`
|
|
9048
9090
|
}, null, 2)
|
|
9049
9091
|
}
|
|
9050
9092
|
]
|
|
@@ -9973,7 +10015,7 @@ class TelemetryReporter {
|
|
|
9973
10015
|
const nodeVersion = process.version; // Node.js版本
|
|
9974
10016
|
const arch = os_1.default.arch(); // 系统架构
|
|
9975
10017
|
// 从构建时注入的版本号获取MCP版本信息
|
|
9976
|
-
const mcpVersion = process.env.npm_package_version || "2.
|
|
10018
|
+
const mcpVersion = process.env.npm_package_version || "2.25.0" || 0;
|
|
9977
10019
|
return {
|
|
9978
10020
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
9979
10021
|
deviceId: this.deviceId,
|
|
@@ -10412,8 +10454,12 @@ exports.MANAGE_FUNCTION_ACTIONS = [
|
|
|
10412
10454
|
"incrementalDeployFunction", // 增量部署,需通过 pluginOptions.functions 注入实现
|
|
10413
10455
|
];
|
|
10414
10456
|
const VPC_SCHEMA = zod_1.z.object({
|
|
10415
|
-
vpcId: zod_1.z
|
|
10416
|
-
|
|
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."),
|
|
10417
10463
|
});
|
|
10418
10464
|
// 镜像部署值,对应 SCF Runtime=CustomImage
|
|
10419
10465
|
exports.CUSTOM_IMAGE_RUNTIME = "CustomImage";
|
|
@@ -10497,8 +10543,11 @@ const CREATE_FUNCTION_SCHEMA = zod_1.z.object({
|
|
|
10497
10543
|
})
|
|
10498
10544
|
.optional(),
|
|
10499
10545
|
timeout: zod_1.z.number().optional().describe("函数超时时间"),
|
|
10500
|
-
envVariables: zod_1.z
|
|
10501
|
-
|
|
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 一致;未知时先查控制台或询问用户,禁止填占位符。"),
|
|
10502
10551
|
runtime: zod_1.z
|
|
10503
10552
|
.string()
|
|
10504
10553
|
.optional()
|
|
@@ -10752,7 +10801,7 @@ function registerFunctionTools(server) {
|
|
|
10752
10801
|
},
|
|
10753
10802
|
{
|
|
10754
10803
|
tool: "queryGateway",
|
|
10755
|
-
action: "
|
|
10804
|
+
action: "getRoute",
|
|
10756
10805
|
reason: "查看该函数是否已暴露网关访问入口",
|
|
10757
10806
|
},
|
|
10758
10807
|
]);
|
|
@@ -11062,7 +11111,7 @@ function registerFunctionTools(server) {
|
|
|
11062
11111
|
raw: imageResult,
|
|
11063
11112
|
}, `已基于镜像 ${createImageConfig.imageUri} 创建 HTTP 函数 ${functionName}。` +
|
|
11064
11113
|
`请确认 TCR、SCF 与构建管道处于同一地域;如需通过 URL 访问,请显式调用 ` +
|
|
11065
|
-
`manageGateway(action="
|
|
11114
|
+
`manageGateway(action="createRoute", type="HTTP") 并按需调整函数安全规则。` +
|
|
11066
11115
|
`部署后可用 queryFunctions(action="getFunctionDetail") 确认函数已就绪。`, [
|
|
11067
11116
|
{
|
|
11068
11117
|
tool: "queryFunctions",
|
|
@@ -11076,8 +11125,8 @@ function registerFunctionTools(server) {
|
|
|
11076
11125
|
},
|
|
11077
11126
|
{
|
|
11078
11127
|
tool: "manageGateway",
|
|
11079
|
-
action: "
|
|
11080
|
-
reason: "如需通过 URL 访问镜像 HTTP
|
|
11128
|
+
action: "createRoute",
|
|
11129
|
+
reason: "如需通过 URL 访问镜像 HTTP 函数,显式创建 Domain/Route 访问入口并传 type=\"HTTP\"(映射 WEB_SCF)",
|
|
11081
11130
|
},
|
|
11082
11131
|
]);
|
|
11083
11132
|
}
|
|
@@ -11132,12 +11181,12 @@ function registerFunctionTools(server) {
|
|
|
11132
11181
|
if (func.type === "HTTP") {
|
|
11133
11182
|
nextActions.push({
|
|
11134
11183
|
tool: "manageGateway",
|
|
11135
|
-
action: "
|
|
11136
|
-
reason: "如果需要通过 URL 访问 HTTP 函数,请调用 manageGateway(action=\"
|
|
11184
|
+
action: "createRoute",
|
|
11185
|
+
reason: "如果需要通过 URL 访问 HTTP 函数,请调用 manageGateway(action=\"createRoute\") 并显式传 type=\"HTTP\"(映射 WEB_SCF),再按实际路径和鉴权需求创建访问入口,不要默认假设 /函数名 已存在",
|
|
11137
11186
|
});
|
|
11138
11187
|
nextActions.push({
|
|
11139
11188
|
tool: "queryGateway",
|
|
11140
|
-
action: "
|
|
11189
|
+
action: "getRoute",
|
|
11141
11190
|
reason: "交付前确认 HTTP 访问路径是否已存在并已生效",
|
|
11142
11191
|
});
|
|
11143
11192
|
nextActions.push({
|
|
@@ -11152,7 +11201,7 @@ function registerFunctionTools(server) {
|
|
|
11152
11201
|
});
|
|
11153
11202
|
}
|
|
11154
11203
|
const message = func.type === "HTTP"
|
|
11155
|
-
? `已创建 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") 调整权限。`
|
|
11156
11205
|
: `已创建函数 ${functionName}`;
|
|
11157
11206
|
return buildEnvelope({
|
|
11158
11207
|
action: input.action,
|
|
@@ -11640,8 +11689,11 @@ function registerFunctionTools(server) {
|
|
|
11640
11689
|
zipFile: zod_1.z.string().optional().describe("仅兼容特殊场景:预先准备好的代码包 base64 编码。普通 createFunction/updateFunctionCode 默认不要先压缩 zip,优先使用 functionRootPath。"),
|
|
11641
11690
|
handler: zod_1.z.string().optional().describe("函数入口"),
|
|
11642
11691
|
timeout: zod_1.z.number().optional().describe("配置更新时的超时时间"),
|
|
11643
|
-
envVariables: zod_1.z
|
|
11644
|
-
|
|
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;不要用占位符。"),
|
|
11645
11697
|
params: zod_1.z.record(zod_1.z.any()).optional().describe("invokeFunction 的调用参数"),
|
|
11646
11698
|
triggers: zod_1.z
|
|
11647
11699
|
.array(TRIGGER_SCHEMA)
|
|
@@ -14275,7 +14327,7 @@ function registerSetupTools(server) {
|
|
|
14275
14327
|
server.registerTool("downloadTemplate", {
|
|
14276
14328
|
title: "下载项目模板",
|
|
14277
14329
|
description: `自动下载并部署CloudBase项目模板。
|
|
14278
|
-
**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)`,
|
|
14279
14331
|
inputSchema: {
|
|
14280
14332
|
template: zod_1.z
|
|
14281
14333
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|