@cloudbase/cloudbase-mcp 2.25.9 → 2.26.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/dist/508.index.cjs +4 -4
- package/dist/cli.cjs +9 -3
- package/dist/index.cjs +49942 -30467
- package/dist/index.d.ts +34 -1
- package/dist/index.js +616 -102
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
33
33
|
exports.DEFAULT_CREATE_ENV_RESOURCES = exports.CREATE_ENV_RESOURCE_VALUES = void 0;
|
|
34
34
|
exports.resolveCreateEnvResources = resolveCreateEnvResources;
|
|
35
35
|
exports.simplifyEnvList = simplifyEnvList;
|
|
36
|
+
exports.isUsableNoSqlDatabaseEntry = isUsableNoSqlDatabaseEntry;
|
|
36
37
|
exports.registerEnvTools = registerEnvTools;
|
|
37
38
|
const toolbox_1 = __webpack_require__(2090);
|
|
38
39
|
const zod_1 = __webpack_require__(2971);
|
|
@@ -40,6 +41,7 @@ const auth_js_1 = __webpack_require__(7291);
|
|
|
40
41
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
41
42
|
const logger_js_1 = __webpack_require__(3039);
|
|
42
43
|
const tool_result_js_1 = __webpack_require__(9835);
|
|
44
|
+
const gateway_access_urls_js_1 = __webpack_require__(2435);
|
|
43
45
|
const env_setup_js_1 = __webpack_require__(6978);
|
|
44
46
|
/**
|
|
45
47
|
* Resources accepted by CreateEnv. Matches Cloud API / Manager SDK contract.
|
|
@@ -597,19 +599,21 @@ async function enrichEnvInfoWithBilling(params) {
|
|
|
597
599
|
* CloudBase environment can have any combination of:
|
|
598
600
|
* - PostgreSQL (CloudBase PG / pgstore): signaled by `EnvInfo.PostgreSQL[]`
|
|
599
601
|
* non-empty and/or `EnvInfo.Meta` containing `postgresql=enable`.
|
|
600
|
-
* - NoSQL document database
|
|
601
|
-
*
|
|
602
|
-
*
|
|
602
|
+
* - NoSQL document database (flexdb): signaled ONLY by a usable entry in
|
|
603
|
+
* `EnvInfo.Databases[]`. The usable signal is a non-empty `InstanceId`
|
|
604
|
+
* (FlexDB Tag, typically `tnt-...` — same value `getDatabaseInstanceId`
|
|
605
|
+
* / ListTables `Tag` consume) with `Status` missing or `RUNNING`.
|
|
606
|
+
* Cloud storage (`EnvInfo.Storages[]` / CreateEnv `storage`) is unrelated
|
|
607
|
+
* and must never flip `RuntimeBackends.nosql`.
|
|
603
608
|
* - MySQL: signaled by a non-empty `EnvInfo.MysqlInstances[]` (or similar
|
|
604
609
|
* field, name varies). In a pure PG environment this is absent.
|
|
605
610
|
*
|
|
606
|
-
* In particular: a CloudBase PG environment commonly STILL has
|
|
607
|
-
*
|
|
608
|
-
* — its collection APIs
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
* table that the task explicitly puts in PG.
|
|
611
|
+
* In particular: a CloudBase PG environment commonly STILL has flexdb
|
|
612
|
+
* provisioned in parallel (`Databases[].InstanceId = tnt-...`). NoSQL is
|
|
613
|
+
* therefore "co-present" — its collection APIs and NoSQL `securityRule`s
|
|
614
|
+
* remain valid for collections that already live there. The thing that is
|
|
615
|
+
* NOT a substitute for the new PG surface is using NoSQL collections to
|
|
616
|
+
* model a brand-new business table that the task explicitly puts in PG.
|
|
613
617
|
*
|
|
614
618
|
* What this enrichment does:
|
|
615
619
|
* - Adds `EnvInfo.RuntimeMode = "postgresql" | "nosql"` based on whether
|
|
@@ -617,11 +621,29 @@ async function enrichEnvInfoWithBilling(params) {
|
|
|
617
621
|
* business data when set to "postgresql".
|
|
618
622
|
* - Adds `EnvInfo.RuntimeBackends`, a structured snapshot of which
|
|
619
623
|
* backends are actually available (postgresql / nosql / mysql), so the
|
|
620
|
-
* agent does not have to re-read `Databases`/`
|
|
624
|
+
* agent does not have to re-read `Databases`/`PostgreSQL`.
|
|
621
625
|
* - Adds `EnvInfo.RuntimeModeHints` summarizing which API/tool to prefer
|
|
622
626
|
* for new code, including an explicit `MysqlNotAvailable` line when
|
|
623
627
|
* MySQL is absent — that one IS a hard "do not use" signal.
|
|
624
628
|
*/
|
|
629
|
+
/** Exported for unit tests / live-fixture checks against FlexDB InstanceId. */
|
|
630
|
+
function isUsableNoSqlDatabaseEntry(db) {
|
|
631
|
+
if (!db || typeof db !== "object") {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
const entry = db;
|
|
635
|
+
const instanceId = typeof entry.InstanceId === "string" ? entry.InstanceId.trim() : "";
|
|
636
|
+
// Empty InstanceId means no flexdb tenant — same failure mode as
|
|
637
|
+
// getDatabaseInstanceId() throwing "无法获取数据库实例ID".
|
|
638
|
+
if (!instanceId) {
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
// Some API versions omit Status; when present, only RUNNING counts as usable.
|
|
642
|
+
if (entry.Status != null && String(entry.Status).toUpperCase() !== "RUNNING") {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
625
647
|
async function enrichEnvInfoWithRuntimeMode(result, manager) {
|
|
626
648
|
const envInfo = result?.EnvInfo;
|
|
627
649
|
if (!envInfo || typeof envInfo !== "object") {
|
|
@@ -634,14 +656,12 @@ async function enrichEnvInfoWithRuntimeMode(result, manager) {
|
|
|
634
656
|
/^postgre[_]?sql$/i.test(item.Key) &&
|
|
635
657
|
String(item.Value).toLowerCase() === "enable");
|
|
636
658
|
const hasPostgresql = pgList.length > 0 || metaPostgresEnabled;
|
|
637
|
-
// NoSQL
|
|
638
|
-
//
|
|
639
|
-
// non-empty (an old env may keep one without the other).
|
|
659
|
+
// NoSQL = flexdb tenant id on Databases[].InstanceId (typically tnt-...).
|
|
660
|
+
// Aligns with getDatabaseInstanceId() / ListTables Tag. Storage is unrelated.
|
|
640
661
|
const databasesList = Array.isArray(envInfo.Databases)
|
|
641
662
|
? envInfo.Databases
|
|
642
663
|
: [];
|
|
643
|
-
const
|
|
644
|
-
const hasNoSql = databasesList.length > 0 || storagesList.length > 0;
|
|
664
|
+
const hasNoSql = databasesList.some(isUsableNoSqlDatabaseEntry);
|
|
645
665
|
// MySQL field name has been seen as MysqlInstances / MySQLInstances /
|
|
646
666
|
// MySQL across API versions; check any of them.
|
|
647
667
|
const mysqlList = (() => {
|
|
@@ -688,8 +708,8 @@ async function enrichEnvInfoWithRuntimeMode(result, manager) {
|
|
|
688
708
|
Permissions: "PG table permissions use Row-Level Security. Run `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` and `CREATE POLICY` via `managePgDatabase(action=\"execute\", confirm=true)`. `managePermissions(resourceType=\"noSqlDatabase\", securityRule=...)` only governs NoSQL collection rules and has NO effect on PG tables — keep using it for the NoSQL collections that already exist here.",
|
|
689
709
|
Storage: "PG-mode browser uploads should use `app.storage.from().upload(<bucket>/<key>, file)` against an explicitly-created `pgstore` bucket (same model as Supabase Storage; the v3 SDK does not auto-create one). `EnvInfo.Storages[]` here is the legacy NoSQL bucket — it is still usable for the legacy `app.uploadFile()` flow but is NOT a valid pgstore target.",
|
|
690
710
|
CoexistingNoSQL: hasNoSql
|
|
691
|
-
? "This env also has
|
|
692
|
-
: "No
|
|
711
|
+
? "This env also has a usable NoSQL (flexdb) InstanceId in EnvInfo.Databases[] (FlexDB Tag, typically tnt-...). Existing collections and `managePermissions(resourceType=\"noSqlDatabase\")` rules remain valid for that data."
|
|
712
|
+
: "No usable NoSQL (flexdb) InstanceId in EnvInfo.Databases[] — do not assume app.database() / NoSQL MCP tools work. Cloud storage does not imply flexdb.",
|
|
693
713
|
MysqlNotAvailable: hasMysql
|
|
694
714
|
? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
|
|
695
715
|
: "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.",
|
|
@@ -774,6 +794,89 @@ async function enrichEnvInfoWithMissingFields(manager, result, envId) {
|
|
|
774
794
|
return result;
|
|
775
795
|
}
|
|
776
796
|
}
|
|
797
|
+
/**
|
|
798
|
+
* Project gateway Route.Enable onto envQuery(info) without mutating StaticDomain.
|
|
799
|
+
*
|
|
800
|
+
* StaticStorages[].StaticDomain stays the cloud-API nominal hostname.
|
|
801
|
+
* Sibling field staticDomainRouteEnabled (and EnvInfo-level mirrors for the
|
|
802
|
+
* primary store) tell callers whether the default hosting domain route is
|
|
803
|
+
* actually reachable (Enable !== false). Matches queryHosting websiteConfig.
|
|
804
|
+
*/
|
|
805
|
+
async function enrichEnvInfoWithStaticDomainRouteEnabled(manager, result, envId) {
|
|
806
|
+
const envInfo = result?.EnvInfo;
|
|
807
|
+
if (!envInfo || typeof envInfo !== "object") {
|
|
808
|
+
return result;
|
|
809
|
+
}
|
|
810
|
+
const staticStorages = Array.isArray(envInfo.StaticStorages)
|
|
811
|
+
? envInfo.StaticStorages
|
|
812
|
+
: null;
|
|
813
|
+
if (!staticStorages || staticStorages.length === 0) {
|
|
814
|
+
return result;
|
|
815
|
+
}
|
|
816
|
+
if (typeof manager?.env?.describeHttpServiceRoute !== "function") {
|
|
817
|
+
return result;
|
|
818
|
+
}
|
|
819
|
+
const resolvedEnvId = (typeof envId === "string" && envId.trim()) ||
|
|
820
|
+
(typeof envInfo.EnvId === "string" && envInfo.EnvId.trim()) ||
|
|
821
|
+
undefined;
|
|
822
|
+
if (!resolvedEnvId) {
|
|
823
|
+
return result;
|
|
824
|
+
}
|
|
825
|
+
try {
|
|
826
|
+
const routeResult = await manager.env.describeHttpServiceRoute({
|
|
827
|
+
EnvId: resolvedEnvId,
|
|
828
|
+
Limit: 1000,
|
|
829
|
+
});
|
|
830
|
+
const routes = (0, gateway_access_urls_js_1.flattenHttpServiceRoutes)(routeResult);
|
|
831
|
+
let primaryRouteEnabled = null;
|
|
832
|
+
const enrichedStorages = staticStorages.map((store, index) => {
|
|
833
|
+
if (!store || typeof store !== "object" || Array.isArray(store)) {
|
|
834
|
+
return store;
|
|
835
|
+
}
|
|
836
|
+
const record = store;
|
|
837
|
+
const domain = typeof record.StaticDomain === "string" ? record.StaticDomain : "";
|
|
838
|
+
if (!domain) {
|
|
839
|
+
return store;
|
|
840
|
+
}
|
|
841
|
+
const reachable = (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(routes, domain, "/");
|
|
842
|
+
if (index === 0) {
|
|
843
|
+
primaryRouteEnabled = reachable;
|
|
844
|
+
}
|
|
845
|
+
return {
|
|
846
|
+
...record,
|
|
847
|
+
staticDomainRouteEnabled: reachable,
|
|
848
|
+
};
|
|
849
|
+
});
|
|
850
|
+
const enrichment = {
|
|
851
|
+
StaticStorages: enrichedStorages,
|
|
852
|
+
};
|
|
853
|
+
if (primaryRouteEnabled !== null) {
|
|
854
|
+
enrichment.staticDomainRouteEnabled = primaryRouteEnabled;
|
|
855
|
+
enrichment.accessUrlReachable = primaryRouteEnabled !== false;
|
|
856
|
+
if (primaryRouteEnabled === false) {
|
|
857
|
+
const primaryDomain = typeof enrichedStorages[0]
|
|
858
|
+
?.StaticDomain === "string"
|
|
859
|
+
? String(enrichedStorages[0].StaticDomain)
|
|
860
|
+
: "";
|
|
861
|
+
enrichment.routeDisabled = true;
|
|
862
|
+
if (primaryDomain) {
|
|
863
|
+
enrichment.disabledAccessUrls = [`https://${primaryDomain}/`];
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return {
|
|
868
|
+
...result,
|
|
869
|
+
EnvInfo: {
|
|
870
|
+
...envInfo,
|
|
871
|
+
...enrichment,
|
|
872
|
+
},
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
catch {
|
|
876
|
+
// Gateway route lookup is best-effort enrichment only.
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
777
880
|
function normalizeOptionalToolString(value) {
|
|
778
881
|
return typeof value === "string" && value.trim().length > 0
|
|
779
882
|
? value.trim()
|
|
@@ -1255,8 +1358,9 @@ function registerEnvTools(server) {
|
|
|
1255
1358
|
if (action === "status") {
|
|
1256
1359
|
const loginState = await (0, auth_js_1.peekLoginState)();
|
|
1257
1360
|
const authFlowState = await (0, auth_js_1.getAuthProgressState)();
|
|
1258
|
-
//
|
|
1259
|
-
const
|
|
1361
|
+
// Detect API Key mode (CLOUDBASE_API_KEY preferred, CLOUDBASE_APIKEY fallback)
|
|
1362
|
+
const apiKeyFromEnv = (0, auth_js_1.getCloudBaseApiKeyFromEnv)();
|
|
1363
|
+
const isApiKeyMode = !!(apiKeyFromEnv && process.env.CLOUDBASE_ENV_ID);
|
|
1260
1364
|
const authStatus = loginState
|
|
1261
1365
|
? "READY"
|
|
1262
1366
|
: authFlowState.status === "PENDING"
|
|
@@ -1304,8 +1408,9 @@ function registerEnvTools(server) {
|
|
|
1304
1408
|
});
|
|
1305
1409
|
}
|
|
1306
1410
|
if (action === "start_auth") {
|
|
1307
|
-
// API Key
|
|
1308
|
-
|
|
1411
|
+
// API Key mode: exchange for temporary credentials
|
|
1412
|
+
const apiKeyFromEnv = (0, auth_js_1.getCloudBaseApiKeyFromEnv)();
|
|
1413
|
+
if (apiKeyFromEnv && process.env.CLOUDBASE_ENV_ID) {
|
|
1309
1414
|
try {
|
|
1310
1415
|
const existingLoginState = await (0, auth_js_1.peekLoginState)();
|
|
1311
1416
|
if (existingLoginState) {
|
|
@@ -1319,22 +1424,21 @@ function registerEnvTools(server) {
|
|
|
1319
1424
|
}
|
|
1320
1425
|
}
|
|
1321
1426
|
catch (e) {
|
|
1322
|
-
// peekLoginState
|
|
1427
|
+
// peekLoginState threw; log and fall through to diagnostic
|
|
1323
1428
|
(0, logger_js_1.debug)("start_auth: peekLoginState threw", { error: e instanceof Error ? e.message : String(e) });
|
|
1324
1429
|
}
|
|
1325
|
-
// API Key
|
|
1326
|
-
// 尝试获取更详细的错误信息
|
|
1430
|
+
// API Key exchange failed: return diagnostic details
|
|
1327
1431
|
let diagMessage = "当前配置了 API Key 认证模式,但换取临时密钥失败。";
|
|
1328
1432
|
const endpoint = process.env.CLOUDBASE_API_ENDPOINT || `https://${process.env.CLOUDBASE_ENV_ID}.ap-shanghai.tcb-api.tencentcloudapi.com`;
|
|
1329
1433
|
diagMessage += `\n\n诊断信息:`;
|
|
1330
1434
|
diagMessage += `\n- CLOUDBASE_ENV_ID: ${process.env.CLOUDBASE_ENV_ID}`;
|
|
1331
|
-
diagMessage += `\n- CLOUDBASE_API_KEY: ${
|
|
1435
|
+
diagMessage += `\n- CLOUDBASE_API_KEY: ${apiKeyFromEnv.slice(0, 20)}...(已截断)`;
|
|
1332
1436
|
diagMessage += `\n- Endpoint: ${endpoint}`;
|
|
1333
1437
|
diagMessage += `\n\n可能原因:`;
|
|
1334
1438
|
diagMessage += `\n1. API Key 已过期或被删除`;
|
|
1335
1439
|
diagMessage += `\n2. Endpoint 不可达(网络/DNS 问题)`;
|
|
1336
1440
|
diagMessage += `\n3. CLOUDBASE_ENV_ID 与 API Key 所属环境不匹配`;
|
|
1337
|
-
diagMessage += `\n\n建议:检查 MCP 配置中的 CLOUDBASE_API_KEY
|
|
1441
|
+
diagMessage += `\n\n建议:检查 MCP 配置中的 CLOUDBASE_API_KEY(或兼容的 CLOUDBASE_APIKEY)和 CLOUDBASE_ENV_ID 环境变量是否正确。`;
|
|
1338
1442
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1339
1443
|
ok: false,
|
|
1340
1444
|
code: "API_KEY_AUTH_FAILED",
|
|
@@ -1620,12 +1724,12 @@ function registerEnvTools(server) {
|
|
|
1620
1724
|
});
|
|
1621
1725
|
}
|
|
1622
1726
|
if (action === "logout") {
|
|
1623
|
-
// API Key
|
|
1624
|
-
if (
|
|
1727
|
+
// Disallow logout in API Key mode
|
|
1728
|
+
if ((0, auth_js_1.getCloudBaseApiKeyFromEnv)() && process.env.CLOUDBASE_ENV_ID) {
|
|
1625
1729
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1626
1730
|
ok: false,
|
|
1627
1731
|
code: "LOGOUT_NOT_ALLOWED",
|
|
1628
|
-
message: "当前使用 API Key 认证模式,不支持退出登录。如需切换认证方式,请移除 CLOUDBASE_API_KEY
|
|
1732
|
+
message: "当前使用 API Key 认证模式,不支持退出登录。如需切换认证方式,请移除 CLOUDBASE_API_KEY(或兼容的 CLOUDBASE_APIKEY)环境变量后重启。",
|
|
1629
1733
|
auth_mode: "api_key",
|
|
1630
1734
|
});
|
|
1631
1735
|
}
|
|
@@ -1847,6 +1951,8 @@ function registerEnvTools(server) {
|
|
|
1847
1951
|
if (envId) {
|
|
1848
1952
|
result = await enrichEnvInfoWithMissingFields(cloudbaseInfo, result, envId);
|
|
1849
1953
|
}
|
|
1954
|
+
// Project gateway Route.Enable next to StaticDomain (do not rewrite StaticDomain).
|
|
1955
|
+
result = await enrichEnvInfoWithStaticDomainRouteEnabled(cloudbaseInfo, result, envId);
|
|
1850
1956
|
result = await enrichEnvInfoWithRuntimeMode(result, cloudbaseInfo);
|
|
1851
1957
|
break;
|
|
1852
1958
|
case "domains":
|
|
@@ -1915,7 +2021,7 @@ function registerEnvTools(server) {
|
|
|
1915
2021
|
// Register primary tool name (queryEnv)
|
|
1916
2022
|
const queryEnvToolSchema = {
|
|
1917
2023
|
title: "CloudBase 环境查询",
|
|
1918
|
-
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 都不该使用。",
|
|
2024
|
+
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\n🌐 action=info 还会在不改写 `StaticStorages[].StaticDomain`(云 API 名义域名)的前提下,投影网关路由 Enable 状态:`StaticStorages[].staticDomainRouteEnabled` 与 `EnvInfo.staticDomainRouteEnabled`(与 queryHosting websiteConfig 同源)。`false` 表示默认静态域名根路由已禁用(访问会返回 GATEWAY_ROUTE_DISABLED),勿把名义域名当成可达 URL。\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 都不该使用。",
|
|
1919
2025
|
inputSchema: {
|
|
1920
2026
|
action: zod_1.z
|
|
1921
2027
|
.enum(["list", "info", "domains"])
|
|
@@ -2718,6 +2824,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
2718
2824
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
2719
2825
|
};
|
|
2720
2826
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2827
|
+
exports.applyCategoryAnnotationMeta = applyCategoryAnnotationMeta;
|
|
2721
2828
|
exports.wrapServerWithTelemetry = wrapServerWithTelemetry;
|
|
2722
2829
|
const os_1 = __importDefault(__webpack_require__(8116));
|
|
2723
2830
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
@@ -2725,6 +2832,30 @@ const cloud_mode_js_1 = __webpack_require__(9684);
|
|
|
2725
2832
|
const logger_js_1 = __webpack_require__(3039);
|
|
2726
2833
|
const telemetry_js_1 = __webpack_require__(5880);
|
|
2727
2834
|
const tool_result_js_1 = __webpack_require__(9835);
|
|
2835
|
+
/**
|
|
2836
|
+
* Mirror annotations.category into _meta.category.
|
|
2837
|
+
*
|
|
2838
|
+
* MCP SDK Client >=1.26 parses tools/list with a stripping ToolAnnotationsSchema,
|
|
2839
|
+
* which drops unknown annotation keys. `_meta` remains available to clients, while
|
|
2840
|
+
* the server wire payload still includes annotations.category for non-SDK hosts.
|
|
2841
|
+
*/
|
|
2842
|
+
function applyCategoryAnnotationMeta(config) {
|
|
2843
|
+
const category = config?.annotations?.category;
|
|
2844
|
+
if (typeof category !== "string" || category.length === 0) {
|
|
2845
|
+
return config;
|
|
2846
|
+
}
|
|
2847
|
+
const existingMeta = config._meta && typeof config._meta === "object" ? config._meta : undefined;
|
|
2848
|
+
if (existingMeta && existingMeta.category === category) {
|
|
2849
|
+
return config;
|
|
2850
|
+
}
|
|
2851
|
+
return {
|
|
2852
|
+
...config,
|
|
2853
|
+
_meta: {
|
|
2854
|
+
...existingMeta,
|
|
2855
|
+
category,
|
|
2856
|
+
},
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2728
2859
|
/**
|
|
2729
2860
|
* 生成 GitHub Issue 创建链接
|
|
2730
2861
|
* @param toolName 工具名称
|
|
@@ -2772,7 +2903,7 @@ ${envIdSection}
|
|
|
2772
2903
|
## 环境信息
|
|
2773
2904
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
2774
2905
|
- Node.js版本: ${process.version}
|
|
2775
|
-
- MCP 版本:${process.env.npm_package_version || "2.
|
|
2906
|
+
- MCP 版本:${process.env.npm_package_version || ( true ? "2.26.0" : 0)}
|
|
2776
2907
|
- 系统架构: ${os_1.default.arch()}
|
|
2777
2908
|
- 时间: ${new Date().toISOString()}
|
|
2778
2909
|
- 请求ID: ${requestId}
|
|
@@ -2853,7 +2984,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
2853
2984
|
requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
|
|
2854
2985
|
ide: server.ide || process.env.INTEGRATION_IDE || ''
|
|
2855
2986
|
});
|
|
2856
|
-
const mcpVersion = true ? "2.
|
|
2987
|
+
const mcpVersion = true ? "2.26.0" : 0;
|
|
2857
2988
|
const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
|
|
2858
2989
|
const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
|
|
2859
2990
|
// 创建新的错误对象,保持原有的错误类型但更新消息
|
|
@@ -2950,7 +3081,7 @@ function wrapServerWithTelemetry(server) {
|
|
|
2950
3081
|
// Use the wrapped handler, passing the server instance
|
|
2951
3082
|
const wrappedHandler = createWrappedHandler(toolName, handler, server);
|
|
2952
3083
|
// Call the original registerTool method
|
|
2953
|
-
return originalRegisterTool(toolName, toolConfig, wrappedHandler);
|
|
3084
|
+
return originalRegisterTool(toolName, applyCategoryAnnotationMeta(toolConfig ?? {}), wrappedHandler);
|
|
2954
3085
|
};
|
|
2955
3086
|
}
|
|
2956
3087
|
/**
|
|
@@ -3189,14 +3320,15 @@ async function createCloudBaseMcpServer(options) {
|
|
|
3189
3320
|
server.toolDefs = [];
|
|
3190
3321
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
3191
3322
|
server.registerTool = ((name, meta, handler) => {
|
|
3323
|
+
const toolMeta = (0, tool_wrapper_js_1.applyCategoryAnnotationMeta)(meta ?? {});
|
|
3192
3324
|
// 同步记录到 toolDefs
|
|
3193
3325
|
server.toolDefs.push({
|
|
3194
3326
|
name,
|
|
3195
|
-
description:
|
|
3196
|
-
inputSchema:
|
|
3327
|
+
description: toolMeta?.description ?? toolMeta?.title ?? '',
|
|
3328
|
+
inputSchema: toolMeta?.inputSchema ?? {},
|
|
3197
3329
|
handler,
|
|
3198
3330
|
});
|
|
3199
|
-
return originalRegisterTool(name,
|
|
3331
|
+
return originalRegisterTool(name, toolMeta, async (args) => {
|
|
3200
3332
|
try {
|
|
3201
3333
|
return await handler(args);
|
|
3202
3334
|
}
|
|
@@ -3454,12 +3586,19 @@ module.exports = __WEBPACK_EXTERNAL_MODULE__modelcontextprotocol_sdk_types_js_70
|
|
|
3454
3586
|
|
|
3455
3587
|
|
|
3456
3588
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3589
|
+
exports.isGatewayRouteEnabled = isGatewayRouteEnabled;
|
|
3590
|
+
exports.isDomainPathReachableViaGateway = isDomainPathReachableViaGateway;
|
|
3457
3591
|
exports.rankGatewayAccessUrls = rankGatewayAccessUrls;
|
|
3458
3592
|
exports.flattenHttpServiceRoutes = flattenHttpServiceRoutes;
|
|
3459
3593
|
exports.filterRoutesByUpstream = filterRoutesByUpstream;
|
|
3460
3594
|
exports.toAccessUrlEnvelope = toAccessUrlEnvelope;
|
|
3461
3595
|
exports.resolveGatewayAccessUrls = resolveGatewayAccessUrls;
|
|
3596
|
+
exports.resolveAllGatewayRoutes = resolveAllGatewayRoutes;
|
|
3462
3597
|
exports.preferGatewayOrFallback = preferGatewayOrFallback;
|
|
3598
|
+
/** Route is reachable unless Enable is explicitly false. */
|
|
3599
|
+
function isGatewayRouteEnabled(route) {
|
|
3600
|
+
return route.Enable !== false;
|
|
3601
|
+
}
|
|
3463
3602
|
function normalizeAccessPath(path) {
|
|
3464
3603
|
if (!path) {
|
|
3465
3604
|
return "/";
|
|
@@ -3469,13 +3608,63 @@ function normalizeAccessPath(path) {
|
|
|
3469
3608
|
function buildHttpsUrl(domain, path) {
|
|
3470
3609
|
return `https://${domain}${normalizeAccessPath(path)}`;
|
|
3471
3610
|
}
|
|
3611
|
+
function extractHostname(urlOrHost) {
|
|
3612
|
+
const trimmed = urlOrHost.trim();
|
|
3613
|
+
if (!trimmed) {
|
|
3614
|
+
return "";
|
|
3615
|
+
}
|
|
3616
|
+
try {
|
|
3617
|
+
if (trimmed.includes("://")) {
|
|
3618
|
+
return new URL(trimmed).hostname.toLowerCase();
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
catch {
|
|
3622
|
+
// Fall through to treat as bare hostname.
|
|
3623
|
+
}
|
|
3624
|
+
return trimmed.replace(/\/.*$/, "").toLowerCase();
|
|
3625
|
+
}
|
|
3626
|
+
/**
|
|
3627
|
+
* Whether a hostname has an enabled covering gateway route for the given path.
|
|
3628
|
+
* Longest matching Path prefix wins. Missing route info → unknown (null).
|
|
3629
|
+
*/
|
|
3630
|
+
function isDomainPathReachableViaGateway(routes, domainOrUrl, requestPath) {
|
|
3631
|
+
const hostname = extractHostname(domainOrUrl);
|
|
3632
|
+
if (!hostname) {
|
|
3633
|
+
return null;
|
|
3634
|
+
}
|
|
3635
|
+
const normalizedRequest = normalizeAccessPath(requestPath);
|
|
3636
|
+
const domainRoutes = routes.filter((route) => Boolean(route.Domain) &&
|
|
3637
|
+
route.Domain.toLowerCase() === hostname);
|
|
3638
|
+
if (domainRoutes.length === 0) {
|
|
3639
|
+
return null;
|
|
3640
|
+
}
|
|
3641
|
+
const covering = domainRoutes
|
|
3642
|
+
.map((route) => ({
|
|
3643
|
+
route,
|
|
3644
|
+
path: normalizeAccessPath(route.Path),
|
|
3645
|
+
}))
|
|
3646
|
+
.filter(({ path }) => {
|
|
3647
|
+
if (path === "/") {
|
|
3648
|
+
return true;
|
|
3649
|
+
}
|
|
3650
|
+
return (normalizedRequest === path ||
|
|
3651
|
+
normalizedRequest.startsWith(path.endsWith("/") ? path : `${path}/`));
|
|
3652
|
+
})
|
|
3653
|
+
.sort((a, b) => b.path.length - a.path.length);
|
|
3654
|
+
if (covering.length === 0) {
|
|
3655
|
+
return null;
|
|
3656
|
+
}
|
|
3657
|
+
return isGatewayRouteEnabled(covering[0].route);
|
|
3658
|
+
}
|
|
3472
3659
|
/**
|
|
3473
3660
|
* Rank gateway route URLs: custom domains (IsDefault !== true) before default domain.
|
|
3474
|
-
* Stable within each group; dedupe by full URL.
|
|
3661
|
+
* By default skips Enable === false routes. Stable within each group; dedupe by full URL.
|
|
3475
3662
|
*/
|
|
3476
|
-
function rankGatewayAccessUrls(routes) {
|
|
3663
|
+
function rankGatewayAccessUrls(routes, options) {
|
|
3664
|
+
const includeDisabled = options?.includeDisabled === true;
|
|
3477
3665
|
const mapped = routes
|
|
3478
3666
|
.filter((route) => Boolean(route.Domain))
|
|
3667
|
+
.filter((route) => includeDisabled || isGatewayRouteEnabled(route))
|
|
3479
3668
|
.map((route) => {
|
|
3480
3669
|
const path = normalizeAccessPath(route.Path);
|
|
3481
3670
|
const isDefault = route.IsDefault === true;
|
|
@@ -3484,6 +3673,7 @@ function rankGatewayAccessUrls(routes) {
|
|
|
3484
3673
|
domain: route.Domain,
|
|
3485
3674
|
path,
|
|
3486
3675
|
isDefault,
|
|
3676
|
+
enabled: isGatewayRouteEnabled(route),
|
|
3487
3677
|
source: (isDefault
|
|
3488
3678
|
? "gateway.default"
|
|
3489
3679
|
: "gateway.custom"),
|
|
@@ -3511,6 +3701,7 @@ function flattenHttpServiceRoutes(result) {
|
|
|
3511
3701
|
Domain: domainItem.Domain ?? "",
|
|
3512
3702
|
IsDefault: domainItem.IsDefault,
|
|
3513
3703
|
Path: typeof route.Path === "string" ? route.Path : undefined,
|
|
3704
|
+
Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
|
|
3514
3705
|
UpstreamResourceType: typeof route.UpstreamResourceType === "string"
|
|
3515
3706
|
? route.UpstreamResourceType
|
|
3516
3707
|
: undefined,
|
|
@@ -3533,64 +3724,116 @@ function filterRoutesByUpstream(routes, input) {
|
|
|
3533
3724
|
types.includes(String(route.UpstreamResourceType)));
|
|
3534
3725
|
});
|
|
3535
3726
|
}
|
|
3536
|
-
function toAccessUrlEnvelope(ranked) {
|
|
3537
|
-
|
|
3538
|
-
|
|
3727
|
+
function toAccessUrlEnvelope(ranked, disabledAccessUrls = []) {
|
|
3728
|
+
const enabled = ranked.filter((item) => item.enabled);
|
|
3729
|
+
if (enabled.length === 0) {
|
|
3730
|
+
return {
|
|
3731
|
+
accessUrls: [],
|
|
3732
|
+
routes: [],
|
|
3733
|
+
disabledAccessUrls,
|
|
3734
|
+
};
|
|
3539
3735
|
}
|
|
3540
3736
|
return {
|
|
3541
|
-
accessUrl:
|
|
3542
|
-
accessUrls:
|
|
3543
|
-
accessUrlSource:
|
|
3544
|
-
routes:
|
|
3737
|
+
accessUrl: enabled[0].url,
|
|
3738
|
+
accessUrls: enabled.map((item) => item.url),
|
|
3739
|
+
accessUrlSource: enabled[0].source,
|
|
3740
|
+
routes: enabled,
|
|
3741
|
+
disabledAccessUrls,
|
|
3545
3742
|
};
|
|
3546
3743
|
}
|
|
3744
|
+
async function loadHttpServiceRoutes(getManager, envId) {
|
|
3745
|
+
const manager = await getManager();
|
|
3746
|
+
const result = await manager.env.describeHttpServiceRoute({
|
|
3747
|
+
EnvId: envId,
|
|
3748
|
+
Limit: 1000,
|
|
3749
|
+
});
|
|
3750
|
+
return flattenHttpServiceRoutes(result);
|
|
3751
|
+
}
|
|
3547
3752
|
/**
|
|
3548
3753
|
* Best-effort lookup of gateway access URLs for an upstream resource.
|
|
3549
3754
|
* Never throws — returns empty lists on failure.
|
|
3755
|
+
* Disabled routes (Enable === false) are excluded from accessUrl(s).
|
|
3550
3756
|
*/
|
|
3551
3757
|
async function resolveGatewayAccessUrls(input) {
|
|
3552
3758
|
try {
|
|
3553
|
-
const
|
|
3554
|
-
const result = await manager.env.describeHttpServiceRoute({
|
|
3555
|
-
EnvId: input.envId,
|
|
3556
|
-
Limit: 1000,
|
|
3557
|
-
});
|
|
3558
|
-
const flattened = flattenHttpServiceRoutes(result);
|
|
3759
|
+
const flattened = await loadHttpServiceRoutes(input.getManager, input.envId);
|
|
3559
3760
|
const matched = filterRoutesByUpstream(flattened, {
|
|
3560
3761
|
upstreamResourceName: input.upstreamResourceName,
|
|
3561
3762
|
upstreamResourceTypes: input.upstreamResourceTypes,
|
|
3562
3763
|
});
|
|
3563
|
-
|
|
3764
|
+
const disabledAccessUrls = rankGatewayAccessUrls(matched, {
|
|
3765
|
+
includeDisabled: true,
|
|
3766
|
+
})
|
|
3767
|
+
.filter((item) => !item.enabled)
|
|
3768
|
+
.map((item) => item.url);
|
|
3769
|
+
return toAccessUrlEnvelope(rankGatewayAccessUrls(matched), disabledAccessUrls);
|
|
3770
|
+
}
|
|
3771
|
+
catch {
|
|
3772
|
+
return { accessUrls: [], routes: [], disabledAccessUrls: [] };
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
/**
|
|
3776
|
+
* Load all flattened HTTP service routes (best-effort). Used by hosting to
|
|
3777
|
+
* judge whether the static-domain fallback itself is gateway-disabled.
|
|
3778
|
+
*/
|
|
3779
|
+
async function resolveAllGatewayRoutes(input) {
|
|
3780
|
+
try {
|
|
3781
|
+
return await loadHttpServiceRoutes(input.getManager, input.envId);
|
|
3564
3782
|
}
|
|
3565
3783
|
catch {
|
|
3566
|
-
return
|
|
3784
|
+
return [];
|
|
3567
3785
|
}
|
|
3568
3786
|
}
|
|
3569
3787
|
/**
|
|
3570
3788
|
* Merge ranked gateway URLs with a resource-native fallback URL.
|
|
3571
3789
|
* Gateway custom/default always win over the fallback when present.
|
|
3790
|
+
* Unreachable fallbacks are omitted from accessUrl(s) and listed under
|
|
3791
|
+
* disabledAccessUrls instead.
|
|
3572
3792
|
*/
|
|
3573
3793
|
function preferGatewayOrFallback(input) {
|
|
3794
|
+
const disabledAccessUrls = [
|
|
3795
|
+
...(input.gateway.disabledAccessUrls ?? []),
|
|
3796
|
+
];
|
|
3797
|
+
const fallbackReachable = input.fallbackReachable !== false;
|
|
3574
3798
|
if (input.gateway.accessUrl) {
|
|
3575
3799
|
const urls = [...input.gateway.accessUrls];
|
|
3576
3800
|
if (input.fallbackUrl &&
|
|
3801
|
+
fallbackReachable &&
|
|
3577
3802
|
!urls.includes(input.fallbackUrl)) {
|
|
3578
3803
|
urls.push(input.fallbackUrl);
|
|
3579
3804
|
}
|
|
3805
|
+
else if (input.fallbackUrl &&
|
|
3806
|
+
!fallbackReachable &&
|
|
3807
|
+
!disabledAccessUrls.includes(input.fallbackUrl)) {
|
|
3808
|
+
disabledAccessUrls.push(input.fallbackUrl);
|
|
3809
|
+
}
|
|
3580
3810
|
return {
|
|
3581
3811
|
accessUrl: input.gateway.accessUrl,
|
|
3582
3812
|
accessUrls: urls,
|
|
3583
3813
|
accessUrlSource: input.gateway.accessUrlSource,
|
|
3814
|
+
accessUrlReachable: true,
|
|
3815
|
+
disabledAccessUrls,
|
|
3584
3816
|
};
|
|
3585
3817
|
}
|
|
3586
|
-
if (input.fallbackUrl) {
|
|
3818
|
+
if (input.fallbackUrl && fallbackReachable) {
|
|
3587
3819
|
return {
|
|
3588
3820
|
accessUrl: input.fallbackUrl,
|
|
3589
3821
|
accessUrls: [input.fallbackUrl],
|
|
3590
3822
|
accessUrlSource: input.fallbackSource,
|
|
3823
|
+
accessUrlReachable: true,
|
|
3824
|
+
disabledAccessUrls,
|
|
3591
3825
|
};
|
|
3592
3826
|
}
|
|
3593
|
-
|
|
3827
|
+
if (input.fallbackUrl && !fallbackReachable) {
|
|
3828
|
+
if (!disabledAccessUrls.includes(input.fallbackUrl)) {
|
|
3829
|
+
disabledAccessUrls.push(input.fallbackUrl);
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
return {
|
|
3833
|
+
accessUrls: [],
|
|
3834
|
+
accessUrlReachable: false,
|
|
3835
|
+
disabledAccessUrls,
|
|
3836
|
+
};
|
|
3594
3837
|
}
|
|
3595
3838
|
|
|
3596
3839
|
|
|
@@ -7847,6 +8090,8 @@ const MANAGE_GATEWAY_ACTIONS = [
|
|
|
7847
8090
|
"createRoute",
|
|
7848
8091
|
"updateRoute",
|
|
7849
8092
|
"deleteRoute",
|
|
8093
|
+
"enableRoute",
|
|
8094
|
+
"disableRoute",
|
|
7850
8095
|
"bindCustomDomain",
|
|
7851
8096
|
"deleteCustomDomain",
|
|
7852
8097
|
"enableService",
|
|
@@ -7919,6 +8164,7 @@ function registerGatewayTools(server) {
|
|
|
7919
8164
|
Domain: route.Domain,
|
|
7920
8165
|
Path: route.Path,
|
|
7921
8166
|
IsDefault: route.IsDefault,
|
|
8167
|
+
Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
|
|
7922
8168
|
UpstreamResourceType: route.UpstreamResourceType,
|
|
7923
8169
|
UpstreamResourceName: route.UpstreamResourceName,
|
|
7924
8170
|
}));
|
|
@@ -7929,16 +8175,26 @@ function registerGatewayTools(server) {
|
|
|
7929
8175
|
Domain: route.Domain,
|
|
7930
8176
|
Path: route.Path,
|
|
7931
8177
|
IsDefault: route.IsDefault,
|
|
8178
|
+
Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
|
|
7932
8179
|
UpstreamResourceType: route.UpstreamResourceType,
|
|
7933
8180
|
UpstreamResourceName: route.UpstreamResourceName,
|
|
7934
8181
|
}));
|
|
7935
|
-
const
|
|
8182
|
+
const disabledAccessUrls = (0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates, {
|
|
8183
|
+
includeDisabled: true,
|
|
8184
|
+
})
|
|
8185
|
+
.filter((item) => !item.enabled)
|
|
8186
|
+
.map((item) => item.url);
|
|
8187
|
+
const envelope = (0, gateway_access_urls_js_1.toAccessUrlEnvelope)((0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates), disabledAccessUrls);
|
|
7936
8188
|
return {
|
|
7937
8189
|
...(envelope.accessUrl ? { accessUrl: envelope.accessUrl } : {}),
|
|
7938
8190
|
accessUrls: envelope.accessUrls,
|
|
7939
8191
|
...(envelope.accessUrlSource
|
|
7940
8192
|
? { accessUrlSource: envelope.accessUrlSource }
|
|
7941
8193
|
: {}),
|
|
8194
|
+
accessUrlReachable: Boolean(envelope.accessUrl),
|
|
8195
|
+
...(disabledAccessUrls.length > 0
|
|
8196
|
+
? { disabledAccessUrls, routeDisabled: true }
|
|
8197
|
+
: {}),
|
|
7942
8198
|
};
|
|
7943
8199
|
};
|
|
7944
8200
|
const pickUsableDefaultDomain = (domains) => {
|
|
@@ -8028,6 +8284,15 @@ function registerGatewayTools(server) {
|
|
|
8028
8284
|
: input.enablePathTransmission !== undefined
|
|
8029
8285
|
? input.enablePathTransmission
|
|
8030
8286
|
: undefined;
|
|
8287
|
+
// Route enable: prefer route.enable. For updateRoute only, top-level
|
|
8288
|
+
// enable also maps to Route.Enable (enableService/authSwitch use the
|
|
8289
|
+
// same field name but different actions).
|
|
8290
|
+
const routeEnable = input.route?.enable !== undefined
|
|
8291
|
+
? input.route.enable
|
|
8292
|
+
: input.action === "updateRoute" &&
|
|
8293
|
+
typeof input.enable === "boolean"
|
|
8294
|
+
? input.enable
|
|
8295
|
+
: undefined;
|
|
8031
8296
|
const route = {
|
|
8032
8297
|
Path: path,
|
|
8033
8298
|
UpstreamResourceType: upstreamResourceType,
|
|
@@ -8037,6 +8302,9 @@ function registerGatewayTools(server) {
|
|
|
8037
8302
|
if (enablePathTransmission !== undefined) {
|
|
8038
8303
|
route.EnablePathTransmission = enablePathTransmission;
|
|
8039
8304
|
}
|
|
8305
|
+
if (routeEnable !== undefined) {
|
|
8306
|
+
route.Enable = routeEnable;
|
|
8307
|
+
}
|
|
8040
8308
|
return {
|
|
8041
8309
|
EnvId: await resolveEnvId(),
|
|
8042
8310
|
Domain: {
|
|
@@ -8050,9 +8318,46 @@ function registerGatewayTools(server) {
|
|
|
8050
8318
|
upstreamResourceName,
|
|
8051
8319
|
enableAuth,
|
|
8052
8320
|
enablePathTransmission,
|
|
8321
|
+
enable: routeEnable,
|
|
8053
8322
|
},
|
|
8054
8323
|
};
|
|
8055
8324
|
};
|
|
8325
|
+
const resolveExistingRouteForToggle = async (input) => {
|
|
8326
|
+
const routePath = input.routePath ?? input.path;
|
|
8327
|
+
if (!routePath) {
|
|
8328
|
+
throw new Error("action=enableRoute/disableRoute 时必须提供 path 或 route.path(例如 \"/\" 或 \"/api\")");
|
|
8329
|
+
}
|
|
8330
|
+
const normalizedPath = normalizeAccessPath(routePath);
|
|
8331
|
+
const preferredDomain = input.domain;
|
|
8332
|
+
const result = await listHttpServiceRoutes(preferredDomain ? { domain: preferredDomain } : undefined);
|
|
8333
|
+
const upstreamName = input.routeServiceName ?? input.targetName;
|
|
8334
|
+
const matches = flattenRoutes(result).filter((item) => {
|
|
8335
|
+
if (preferredDomain &&
|
|
8336
|
+
item.Domain !== preferredDomain) {
|
|
8337
|
+
return false;
|
|
8338
|
+
}
|
|
8339
|
+
if (normalizeAccessPath(String(item.Path ?? "")) !== normalizedPath) {
|
|
8340
|
+
return false;
|
|
8341
|
+
}
|
|
8342
|
+
if (upstreamName && item.UpstreamResourceName !== upstreamName) {
|
|
8343
|
+
return false;
|
|
8344
|
+
}
|
|
8345
|
+
return true;
|
|
8346
|
+
});
|
|
8347
|
+
if (matches.length === 0) {
|
|
8348
|
+
throw new Error(`未找到路径 ${normalizedPath} 的路由` +
|
|
8349
|
+
(preferredDomain ? `(域名 ${preferredDomain})` : "") +
|
|
8350
|
+
(upstreamName ? `(上游 ${upstreamName})` : "") +
|
|
8351
|
+
"。请先用 queryGateway(action=\"listRoutes\") 确认 Domain / Path,再调用 enableRoute/disableRoute。" +
|
|
8352
|
+
"关闭静态托管默认域名(*.tcloudbaseapp.com)时,请显式传 domain=该 STATIC_STORE IsDefault 域名,并通常 path=\"/\"。");
|
|
8353
|
+
}
|
|
8354
|
+
if (matches.length > 1) {
|
|
8355
|
+
const domains = [...new Set(matches.map((item) => item.Domain))];
|
|
8356
|
+
throw new Error(`路径 ${normalizedPath} 匹配到 ${matches.length} 条路由(域名:${domains.join(", ")})。` +
|
|
8357
|
+
"请补充 domain(必要时再加 targetName/route.serviceName)精确定位后再 enableRoute/disableRoute。");
|
|
8358
|
+
}
|
|
8359
|
+
return matches[0];
|
|
8360
|
+
};
|
|
8056
8361
|
const routeMutationNextActions = (targetName) => [
|
|
8057
8362
|
{
|
|
8058
8363
|
tool: "queryGateway",
|
|
@@ -8256,6 +8561,10 @@ function registerGatewayTools(server) {
|
|
|
8256
8561
|
Domain: payload.Domain,
|
|
8257
8562
|
});
|
|
8258
8563
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
8564
|
+
const routeEnabled = payload.resolved.enable !== false;
|
|
8565
|
+
const accessUrl = routeEnabled
|
|
8566
|
+
? `https://${payload.resolved.domain}${payload.resolved.path}`
|
|
8567
|
+
: undefined;
|
|
8259
8568
|
return buildEnvelope({
|
|
8260
8569
|
action: input.action,
|
|
8261
8570
|
model: "httpServiceRoute",
|
|
@@ -8265,13 +8574,31 @@ function registerGatewayTools(server) {
|
|
|
8265
8574
|
upstreamResourceName: payload.resolved.upstreamResourceName,
|
|
8266
8575
|
auth: payload.resolved.enableAuth ?? null,
|
|
8267
8576
|
enablePathTransmission: payload.resolved.enablePathTransmission ?? null,
|
|
8268
|
-
|
|
8269
|
-
|
|
8577
|
+
enable: payload.resolved.enable ?? null,
|
|
8578
|
+
...(accessUrl
|
|
8579
|
+
? {
|
|
8580
|
+
accessUrl,
|
|
8581
|
+
accessUrls: [accessUrl],
|
|
8582
|
+
accessUrlReachable: true,
|
|
8583
|
+
}
|
|
8584
|
+
: {
|
|
8585
|
+
accessUrls: [],
|
|
8586
|
+
accessUrlReachable: false,
|
|
8587
|
+
routeDisabled: true,
|
|
8588
|
+
disabledAccessUrls: [
|
|
8589
|
+
`https://${payload.resolved.domain}${payload.resolved.path}`,
|
|
8590
|
+
],
|
|
8591
|
+
}),
|
|
8270
8592
|
accessUrlSource: input.domain && input.domain === payload.resolved.domain
|
|
8271
8593
|
? "gateway.custom"
|
|
8272
8594
|
: "gateway.default",
|
|
8273
8595
|
raw: result,
|
|
8274
8596
|
}, `HTTP 路由更新成功(${payload.resolved.domain}${payload.resolved.path}` +
|
|
8597
|
+
(payload.resolved.enable === false
|
|
8598
|
+
? ",路由已禁用 Enable=false"
|
|
8599
|
+
: payload.resolved.enable === true
|
|
8600
|
+
? ",路由已启用 Enable=true"
|
|
8601
|
+
: "") +
|
|
8275
8602
|
(payload.resolved.enablePathTransmission === true
|
|
8276
8603
|
? ",路径透传=开启"
|
|
8277
8604
|
: payload.resolved.enablePathTransmission === false
|
|
@@ -8279,6 +8606,102 @@ function registerGatewayTools(server) {
|
|
|
8279
8606
|
: "") +
|
|
8280
8607
|
`)`, routeMutationNextActions(payload.resolved.upstreamResourceName));
|
|
8281
8608
|
}
|
|
8609
|
+
case "enableRoute":
|
|
8610
|
+
case "disableRoute": {
|
|
8611
|
+
const enable = input.action === "enableRoute";
|
|
8612
|
+
const existing = await resolveExistingRouteForToggle({
|
|
8613
|
+
path: input.path,
|
|
8614
|
+
routePath: input.route?.path,
|
|
8615
|
+
domain: input.domain,
|
|
8616
|
+
targetName: input.targetName,
|
|
8617
|
+
routeServiceName: input.route?.serviceName,
|
|
8618
|
+
});
|
|
8619
|
+
const upstreamResourceType = resolveUpstreamResourceType({
|
|
8620
|
+
upstreamResourceType: input.upstreamResourceType ??
|
|
8621
|
+
existing.UpstreamResourceType,
|
|
8622
|
+
routeUpstreamResourceType: input.route?.upstreamResourceType,
|
|
8623
|
+
});
|
|
8624
|
+
const upstreamResourceName = input.route?.serviceName ??
|
|
8625
|
+
input.targetName ??
|
|
8626
|
+
String(existing.UpstreamResourceName ?? "");
|
|
8627
|
+
if (!upstreamResourceName) {
|
|
8628
|
+
throw new Error(`action=${input.action} 无法解析上游资源名;请用 queryGateway(listRoutes) 确认后传入 targetName 或 route.serviceName。`);
|
|
8629
|
+
}
|
|
8630
|
+
const normalizedPath = normalizeAccessPath(String(existing.Path ?? input.route?.path ?? input.path ?? "/"));
|
|
8631
|
+
const domain = existing.Domain;
|
|
8632
|
+
const enableAuth = input.route?.auth !== undefined
|
|
8633
|
+
? input.route.auth
|
|
8634
|
+
: input.auth !== undefined
|
|
8635
|
+
? input.auth
|
|
8636
|
+
: existing.EnableAuth;
|
|
8637
|
+
const enablePathTransmission = input.route?.enablePathTransmission !== undefined
|
|
8638
|
+
? input.route.enablePathTransmission
|
|
8639
|
+
: input.enablePathTransmission !== undefined
|
|
8640
|
+
? input.enablePathTransmission
|
|
8641
|
+
: existing.EnablePathTransmission;
|
|
8642
|
+
const route = {
|
|
8643
|
+
Path: normalizedPath,
|
|
8644
|
+
UpstreamResourceType: upstreamResourceType,
|
|
8645
|
+
UpstreamResourceName: upstreamResourceName,
|
|
8646
|
+
Enable: enable,
|
|
8647
|
+
};
|
|
8648
|
+
if (enableAuth !== undefined) {
|
|
8649
|
+
route.EnableAuth = enableAuth;
|
|
8650
|
+
}
|
|
8651
|
+
if (enablePathTransmission !== undefined) {
|
|
8652
|
+
route.EnablePathTransmission = enablePathTransmission;
|
|
8653
|
+
}
|
|
8654
|
+
const cloudbase = await getManager();
|
|
8655
|
+
const result = await cloudbase.env.modifyHttpServiceRoute({
|
|
8656
|
+
EnvId: await resolveEnvId(),
|
|
8657
|
+
Domain: {
|
|
8658
|
+
Domain: domain,
|
|
8659
|
+
Routes: [route],
|
|
8660
|
+
},
|
|
8661
|
+
});
|
|
8662
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
8663
|
+
const verb = enable ? "启用" : "禁用";
|
|
8664
|
+
const accessUrl = enable
|
|
8665
|
+
? `https://${domain}${normalizedPath}`
|
|
8666
|
+
: undefined;
|
|
8667
|
+
return buildEnvelope({
|
|
8668
|
+
action: input.action,
|
|
8669
|
+
model: "httpServiceRoute",
|
|
8670
|
+
domain,
|
|
8671
|
+
path: normalizedPath,
|
|
8672
|
+
upstreamResourceType,
|
|
8673
|
+
upstreamResourceName,
|
|
8674
|
+
enable,
|
|
8675
|
+
auth: enableAuth ?? null,
|
|
8676
|
+
enablePathTransmission: enablePathTransmission ?? null,
|
|
8677
|
+
...(accessUrl
|
|
8678
|
+
? {
|
|
8679
|
+
accessUrl,
|
|
8680
|
+
accessUrls: [accessUrl],
|
|
8681
|
+
accessUrlReachable: true,
|
|
8682
|
+
}
|
|
8683
|
+
: {
|
|
8684
|
+
accessUrls: [],
|
|
8685
|
+
accessUrlReachable: false,
|
|
8686
|
+
routeDisabled: true,
|
|
8687
|
+
disabledAccessUrls: [`https://${domain}${normalizedPath}`],
|
|
8688
|
+
}),
|
|
8689
|
+
accessUrlSource: existing.IsDefault
|
|
8690
|
+
? "gateway.default"
|
|
8691
|
+
: "gateway.custom",
|
|
8692
|
+
raw: result,
|
|
8693
|
+
}, `HTTP 路由已${verb}(${domain}${normalizedPath},Enable=${enable})。` +
|
|
8694
|
+
(enable
|
|
8695
|
+
? "启用后通常数秒到约 30 秒内可访问;请用 queryGateway(getRoute) 或探测 accessUrl 确认。"
|
|
8696
|
+
: "禁用后该路径将不可公网访问(GATEWAY_ROUTE_DISABLED);关闭静态托管默认域名(*.tcloudbaseapp.com)时,请确认 DomainType=STATIC_STORE 且 IsDefault=true。") +
|
|
8697
|
+
" 底层对应 tcb ModifyHTTPServiceRoute(不是 ModifyGatewayRoute)。", [
|
|
8698
|
+
{
|
|
8699
|
+
tool: "queryGateway",
|
|
8700
|
+
action: "getRoute",
|
|
8701
|
+
reason: `复核路由 Enable=${enable} 是否已生效`,
|
|
8702
|
+
},
|
|
8703
|
+
]);
|
|
8704
|
+
}
|
|
8282
8705
|
case "deleteRoute": {
|
|
8283
8706
|
const routePath = input.route?.path ?? input.path;
|
|
8284
8707
|
if (!routePath) {
|
|
@@ -8486,8 +8909,11 @@ function registerGatewayTools(server) {
|
|
|
8486
8909
|
}, async (input) => withEnvelope(() => handleQueryGateway(input)));
|
|
8487
8910
|
server.registerTool?.("manageGateway", {
|
|
8488
8911
|
title: "管理 CloudBase 网关",
|
|
8489
|
-
description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path
|
|
8912
|
+
description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;" +
|
|
8913
|
+
"enableRoute/disableRoute 启用或禁用已有路由(底层 ModifyHTTPServiceRoute 的 Routes[].Enable,不是 ModifyGatewayRoute)。" +
|
|
8914
|
+
"未传 domain 时用 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(形如 *.{region}.app.tcloudbase.com),不会使用静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE)。" +
|
|
8490
8915
|
"这是网关默认域上的路径路由,不是 STATIC_STORE 上游绑定;STATIC_STORE 上游必须显式传 upstreamResourceType=STATIC_STORE。" +
|
|
8916
|
+
"关闭静态托管默认域名(*.tcloudbaseapp.com):先 queryGateway(listRoutes) 找到 DomainType=STATIC_STORE 且 IsDefault=true 的 domain,再 manageGateway(action=\"disableRoute\", domain=该域名, path=\"/\");勿用 manageHosting。" +
|
|
8491
8917
|
"创建后可用 queryGateway(action=\"listRoutes\") 核对 Domain / DomainType / Path / UpstreamResourceType。" +
|
|
8492
8918
|
"上游类型只用一个参数 upstreamResourceType(也可写在 route.upstreamResourceType,route 优先):" +
|
|
8493
8919
|
"WEB_SCF=HTTP云函数,SCF=Event云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器;" +
|
|
@@ -8500,9 +8926,12 @@ function registerGatewayTools(server) {
|
|
|
8500
8926
|
inputSchema: {
|
|
8501
8927
|
action: zod_1.z
|
|
8502
8928
|
.enum(MANAGE_GATEWAY_ACTIONS)
|
|
8503
|
-
.describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;
|
|
8929
|
+
.describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;enableRoute/disableRoute 启用或禁用已有路由(需 path,建议显式传 domain);" +
|
|
8930
|
+
"bindCustomDomain/deleteCustomDomain 管理自定义域名;" +
|
|
8504
8931
|
"enableService/authSwitch 开关 HTTP 网关总开关与访问鉴权(需配合 enable 参数)。" +
|
|
8505
|
-
"createRoute/updateRoute 必须提供 upstreamResourceType
|
|
8932
|
+
"createRoute/updateRoute 必须提供 upstreamResourceType;enableRoute/disableRoute 会先 listRoutes 定位已有路由,通常不必重填上游。" +
|
|
8933
|
+
"updateRoute 也可传 enable/route.enable 直接改 Routes[].Enable。" +
|
|
8934
|
+
"关闭 *.tcloudbaseapp.com 默认静态托管域:disableRoute + domain=该 STATIC_STORE IsDefault 域名 + path=\"/\"。" +
|
|
8506
8935
|
"已有自定义域名时优先 createRoute(domain=已有域名) 实现访问,不必再次 bindCustomDomain / 传入 certificateId;" +
|
|
8507
8936
|
"bindCustomDomain 仅用于首次绑定新域名(需 certificateId;可选 accessType=DIRECT|CDN|CUSTOM,CUSTOM 需 customCname;普通场景用默认 DIRECT)。" +
|
|
8508
8937
|
"接入说明:https://docs.cloudbase.net/service/custom-domain"),
|
|
@@ -8551,16 +8980,25 @@ function registerGatewayTools(server) {
|
|
|
8551
8980
|
.boolean()
|
|
8552
8981
|
.optional()
|
|
8553
8982
|
.describe("同顶层 enablePathTransmission。route 内设置时优先于顶层。"),
|
|
8983
|
+
enable: zod_1.z
|
|
8984
|
+
.boolean()
|
|
8985
|
+
.optional()
|
|
8986
|
+
.describe("路由级开关(Routes[].Enable / Route.Enable)。createRoute/updateRoute 可用:" +
|
|
8987
|
+
"enable=false 禁用该 Domain+Path(访问返回 GATEWAY_ROUTE_DISABLED);" +
|
|
8988
|
+
"enable=true 重新启用。updateRoute 也可用顶层 enable 表达同一语义;" +
|
|
8989
|
+
"也可用专用 action enableRoute/disableRoute。route.enable 优先于顶层 enable。"),
|
|
8554
8990
|
})
|
|
8555
8991
|
.optional()
|
|
8556
8992
|
.describe("路由对象(可选写法)。例:云函数 {upstreamResourceType:\"WEB_SCF\",serviceName:\"fn\",path:\"/api\"};" +
|
|
8557
8993
|
"云托管 {upstreamResourceType:\"CBR\",serviceName:\"svc\",path:\"/api\"};" +
|
|
8558
|
-
"静态托管 {upstreamResourceType:\"STATIC_STORE\",serviceName:\"staticstore\",path:\"/\"}
|
|
8994
|
+
"静态托管 {upstreamResourceType:\"STATIC_STORE\",serviceName:\"staticstore\",path:\"/\"};" +
|
|
8995
|
+
"禁用路由 {path:\"/\",enable:false}(配合 updateRoute,或直接用 disableRoute)。"),
|
|
8559
8996
|
domain: zod_1.z
|
|
8560
8997
|
.string()
|
|
8561
8998
|
.optional()
|
|
8562
8999
|
.describe("域名。省略时自动使用环境 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(*.{region}.app.tcloudbase.com),不会回退到静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE);也不是 STATIC_STORE 上游绑定。" +
|
|
8563
9000
|
"可用 queryGateway(action=\"listRoutes\") 核对实际 Domain / DomainType。" +
|
|
9001
|
+
"enableRoute/disableRoute 操作 *.tcloudbaseapp.com 时必须显式传入该域名。" +
|
|
8564
9002
|
"已有自定义域名时请显式传入该域名并 createRoute/updateRoute/deleteRoute,即可实现自定义域名访问且无需证书 ID;" +
|
|
8565
9003
|
"仅 bindCustomDomain 时表示要新绑定的域名。"),
|
|
8566
9004
|
certificateId: zod_1.z
|
|
@@ -8583,9 +9021,11 @@ function registerGatewayTools(server) {
|
|
|
8583
9021
|
enable: zod_1.z
|
|
8584
9022
|
.boolean()
|
|
8585
9023
|
.optional()
|
|
8586
|
-
.describe("
|
|
8587
|
-
"bindCustomDomain 可选:enable=false
|
|
8588
|
-
"
|
|
9024
|
+
.describe("开关目标状态:enableService / authSwitch 必填(true 开启 / false 关闭);" +
|
|
9025
|
+
"bindCustomDomain 可选:enable=false 表示绑定后禁用域名(默认启用);" +
|
|
9026
|
+
"updateRoute 可选:映射到 Routes[].Enable(也可用 route.enable,route 优先;" +
|
|
9027
|
+
"也可用专用 action enableRoute/disableRoute)。" +
|
|
9028
|
+
"省略或非布尔值在 enableService/authSwitch 会返回参数错误。"),
|
|
8589
9029
|
},
|
|
8590
9030
|
annotations: {
|
|
8591
9031
|
readOnlyHint: false,
|
|
@@ -11208,8 +11648,9 @@ class TelemetryReporter {
|
|
|
11208
11648
|
const osRelease = os_1.default.release(); // 操作系统版本
|
|
11209
11649
|
const nodeVersion = process.version; // Node.js版本
|
|
11210
11650
|
const arch = os_1.default.arch(); // 系统架构
|
|
11211
|
-
//
|
|
11212
|
-
const mcpVersion = process.env.npm_package_version ||
|
|
11651
|
+
// Build-time injected version; guard for vitest / non-webpack runtimes.
|
|
11652
|
+
const mcpVersion = process.env.npm_package_version ||
|
|
11653
|
+
( true ? "2.26.0" : 0);
|
|
11213
11654
|
return {
|
|
11214
11655
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
11215
11656
|
deviceId: this.deviceId,
|
|
@@ -16744,7 +17185,7 @@ function registerSetupTools(server) {
|
|
|
16744
17185
|
server.registerTool("downloadTemplate", {
|
|
16745
17186
|
title: "下载项目模板",
|
|
16746
17187
|
description: `自动下载并部署CloudBase项目模板。
|
|
16747
|
-
**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.
|
|
17188
|
+
**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.26.0" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
|
|
16748
17189
|
inputSchema: {
|
|
16749
17190
|
template: zod_1.z
|
|
16750
17191
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|
|
@@ -18018,16 +18459,19 @@ function isDirectoryUploadTarget(localPath, cloudPath) {
|
|
|
18018
18459
|
return true;
|
|
18019
18460
|
return path_1.default.posix.extname(normalizedCloudPath) === '';
|
|
18020
18461
|
}
|
|
18021
|
-
function
|
|
18022
|
-
if (!staticDomain)
|
|
18023
|
-
return '';
|
|
18462
|
+
function hostingAccessPathname(cloudPath, localPath) {
|
|
18024
18463
|
const normalizedCloudPath = (cloudPath ?? '').trim().replace(/^\/+|\/+$/g, '');
|
|
18025
18464
|
const isDirectory = isDirectoryUploadTarget(localPath, cloudPath);
|
|
18026
18465
|
if (!normalizedCloudPath) {
|
|
18027
|
-
return
|
|
18466
|
+
return '/';
|
|
18028
18467
|
}
|
|
18029
18468
|
const pathname = isDirectory ? `${normalizedCloudPath}/` : normalizedCloudPath;
|
|
18030
|
-
return
|
|
18469
|
+
return `/${pathname}`;
|
|
18470
|
+
}
|
|
18471
|
+
function buildHostingAccessUrl(staticDomain, cloudPath, localPath) {
|
|
18472
|
+
if (!staticDomain)
|
|
18473
|
+
return '';
|
|
18474
|
+
return `https://${staticDomain}${hostingAccessPathname(cloudPath, localPath)}`;
|
|
18031
18475
|
}
|
|
18032
18476
|
function buildUploadErrorMessage(error, localPath) {
|
|
18033
18477
|
const baseMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -18208,7 +18652,7 @@ function buildDomainMutationResult(params) {
|
|
|
18208
18652
|
message: `静态托管域名${actionLabel}请求已提交。域名配置、证书校验和边缘侧传播通常需要 30 秒到 10 分钟,请继续调用 queryHosting(action="domainStatus") 确认最终结果。`,
|
|
18209
18653
|
};
|
|
18210
18654
|
}
|
|
18211
|
-
async function getHostingWebsiteConfig(cloudbase, logger) {
|
|
18655
|
+
async function getHostingWebsiteConfig(cloudbase, logger, cloudBaseOptions) {
|
|
18212
18656
|
const websiteConfig = await cloudbase.hosting.getWebsiteConfig();
|
|
18213
18657
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(logger, websiteConfig);
|
|
18214
18658
|
const hostingResult = {
|
|
@@ -18225,6 +18669,27 @@ async function getHostingWebsiteConfig(cloudbase, logger) {
|
|
|
18225
18669
|
catch {
|
|
18226
18670
|
// Ignore enrichment failures and return the website config as-is.
|
|
18227
18671
|
}
|
|
18672
|
+
try {
|
|
18673
|
+
const cdnDomain = typeof hostingResult.CdnDomain === 'string' ? hostingResult.CdnDomain : '';
|
|
18674
|
+
if (cdnDomain && typeof cloudbase.env?.describeHttpServiceRoute === 'function') {
|
|
18675
|
+
const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
18676
|
+
const routeResult = await cloudbase.env.describeHttpServiceRoute({
|
|
18677
|
+
EnvId: envId,
|
|
18678
|
+
Limit: 1000,
|
|
18679
|
+
});
|
|
18680
|
+
const routes = (0, gateway_access_urls_js_1.flattenHttpServiceRoutes)(routeResult);
|
|
18681
|
+
const reachable = (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(routes, cdnDomain, '/');
|
|
18682
|
+
hostingResult.staticDomainRouteEnabled = reachable;
|
|
18683
|
+
hostingResult.accessUrlReachable = reachable !== false;
|
|
18684
|
+
if (reachable === false) {
|
|
18685
|
+
hostingResult.routeDisabled = true;
|
|
18686
|
+
hostingResult.disabledAccessUrls = [`https://${cdnDomain}/`];
|
|
18687
|
+
}
|
|
18688
|
+
}
|
|
18689
|
+
}
|
|
18690
|
+
catch {
|
|
18691
|
+
// Gateway route lookup is best-effort enrichment only.
|
|
18692
|
+
}
|
|
18228
18693
|
return hostingResult;
|
|
18229
18694
|
}
|
|
18230
18695
|
async function resolveHostingStaticDomain(cloudbase, logger) {
|
|
@@ -18308,7 +18773,7 @@ function registerHostingTools(server) {
|
|
|
18308
18773
|
const cloudbase = await getManager();
|
|
18309
18774
|
switch (input.action) {
|
|
18310
18775
|
case 'websiteConfig': {
|
|
18311
|
-
const websiteConfig = await getHostingWebsiteConfig(cloudbase, server.logger);
|
|
18776
|
+
const websiteConfig = await getHostingWebsiteConfig(cloudbase, server.logger, cloudBaseOptions);
|
|
18312
18777
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
18313
18778
|
success: true,
|
|
18314
18779
|
data: {
|
|
@@ -18434,7 +18899,7 @@ function registerHostingTools(server) {
|
|
|
18434
18899
|
});
|
|
18435
18900
|
server.registerTool('manageHosting', {
|
|
18436
18901
|
title: '管理 CloudBase 静态托管',
|
|
18437
|
-
description: '管理 CloudBase 静态托管的变更操作。action=upload 上传本地构建产物到共享域名(域名格式:<envId>-<appId>.tcloudbaseapp.com/<cloudPath>);action=delete 删除托管文件或目录(必须 confirm=true);action=setWebsiteDocument 设置首页/错误页/路由规则;action=enableService 开通静态托管;action=bindDomain / unbindDomain / updateDomain 管理自定义域名;action=downloadFile / downloadDirectory 下载托管内容到本地。⚠️ 新项目部署优先使用 manageApps(部署到独立子域名),本工具适合已有老项目继续使用或作为 manageApps 的 fallback。manageApps 与 manageHosting 域名不同,切换会导致老链接失效。若任务只是查看配置、文件或域名状态,请改用 queryHosting。',
|
|
18902
|
+
description: '管理 CloudBase 静态托管的变更操作。action=upload 上传本地构建产物到共享域名(域名格式:<envId>-<appId>.tcloudbaseapp.com/<cloudPath>);action=delete 删除托管文件或目录(必须 confirm=true);action=setWebsiteDocument 设置首页/错误页/路由规则;action=enableService 开通静态托管;action=bindDomain / unbindDomain / updateDomain 管理自定义域名;action=downloadFile / downloadDirectory 下载托管内容到本地。⚠️ 本工具没有关闭默认域名(*.tcloudbaseapp.com)的 action;要禁用该默认公网域名,请用 manageGateway(action="disableRoute", domain=该 STATIC_STORE IsDefault 域名, path="/")(底层 ModifyHTTPServiceRoute,不是 ModifyGatewayRoute)。⚠️ 新项目部署优先使用 manageApps(部署到独立子域名),本工具适合已有老项目继续使用或作为 manageApps 的 fallback。manageApps 与 manageHosting 域名不同,切换会导致老链接失效。若任务只是查看配置、文件或域名状态,请改用 queryHosting。',
|
|
18438
18903
|
inputSchema: manageHostingInputSchema,
|
|
18439
18904
|
annotations: {
|
|
18440
18905
|
readOnlyHint: false,
|
|
@@ -18474,7 +18939,24 @@ function registerHostingTools(server) {
|
|
|
18474
18939
|
const cdnFromStore = (store.CdnDomain ?? store.StaticDomain);
|
|
18475
18940
|
const staticDomain = cdnFromStore || await resolveHostingStaticDomain(cloudbase, server.logger);
|
|
18476
18941
|
const fallbackAccessUrl = buildHostingAccessUrl(staticDomain, input.cloudPath, input.localPath);
|
|
18942
|
+
const accessPathname = hostingAccessPathname(input.cloudPath, input.localPath);
|
|
18477
18943
|
const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
18944
|
+
const getGatewayManager = async () => {
|
|
18945
|
+
const manager = await getManager();
|
|
18946
|
+
if (!manager) {
|
|
18947
|
+
throw new Error("cloudbase manager unavailable");
|
|
18948
|
+
}
|
|
18949
|
+
return manager;
|
|
18950
|
+
};
|
|
18951
|
+
const allGatewayRoutes = await (0, gateway_access_urls_js_1.resolveAllGatewayRoutes)({
|
|
18952
|
+
envId,
|
|
18953
|
+
getManager: getGatewayManager,
|
|
18954
|
+
});
|
|
18955
|
+
const staticDomainRouteEnabled = staticDomain
|
|
18956
|
+
? (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(allGatewayRoutes, staticDomain, accessPathname)
|
|
18957
|
+
: null;
|
|
18958
|
+
// null = no matching gateway route info → keep legacy fallback.
|
|
18959
|
+
const fallbackReachable = staticDomainRouteEnabled !== false;
|
|
18478
18960
|
const gatewayCandidates = Array.from(new Set([
|
|
18479
18961
|
store.StaticStoreName,
|
|
18480
18962
|
store.Name,
|
|
@@ -18484,34 +18966,38 @@ function registerHostingTools(server) {
|
|
|
18484
18966
|
]
|
|
18485
18967
|
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
18486
18968
|
.filter(Boolean)));
|
|
18487
|
-
let accessUrl = fallbackAccessUrl;
|
|
18969
|
+
let accessUrl = fallbackReachable ? fallbackAccessUrl : "";
|
|
18488
18970
|
let accessUrls = accessUrl ? [accessUrl] : [];
|
|
18489
18971
|
let accessUrlSource = accessUrl
|
|
18490
18972
|
? "hosting.staticDomain"
|
|
18491
18973
|
: undefined;
|
|
18974
|
+
let accessUrlReachable = Boolean(accessUrl);
|
|
18975
|
+
let disabledAccessUrls = [];
|
|
18976
|
+
if (fallbackAccessUrl && !fallbackReachable) {
|
|
18977
|
+
disabledAccessUrls.push(fallbackAccessUrl);
|
|
18978
|
+
}
|
|
18492
18979
|
for (const upstreamName of gatewayCandidates) {
|
|
18493
18980
|
const gateway = await (0, gateway_access_urls_js_1.resolveGatewayAccessUrls)({
|
|
18494
18981
|
envId,
|
|
18495
18982
|
upstreamResourceName: upstreamName,
|
|
18496
18983
|
upstreamResourceTypes: ["STATIC_STORE"],
|
|
18497
|
-
getManager:
|
|
18498
|
-
const manager = await getManager();
|
|
18499
|
-
if (!manager) {
|
|
18500
|
-
throw new Error("cloudbase manager unavailable");
|
|
18501
|
-
}
|
|
18502
|
-
return manager;
|
|
18503
|
-
},
|
|
18984
|
+
getManager: getGatewayManager,
|
|
18504
18985
|
});
|
|
18505
18986
|
const preferred = (0, gateway_access_urls_js_1.preferGatewayOrFallback)({
|
|
18506
18987
|
gateway,
|
|
18507
18988
|
fallbackUrl: fallbackAccessUrl || undefined,
|
|
18508
18989
|
fallbackSource: "hosting.staticDomain",
|
|
18990
|
+
fallbackReachable,
|
|
18509
18991
|
});
|
|
18510
|
-
if (preferred.accessUrl) {
|
|
18511
|
-
accessUrl = preferred.accessUrl;
|
|
18992
|
+
if (preferred.accessUrl || preferred.disabledAccessUrls.length > 0) {
|
|
18993
|
+
accessUrl = preferred.accessUrl ?? "";
|
|
18512
18994
|
accessUrls = preferred.accessUrls;
|
|
18513
18995
|
accessUrlSource = preferred.accessUrlSource;
|
|
18514
|
-
|
|
18996
|
+
accessUrlReachable = preferred.accessUrlReachable;
|
|
18997
|
+
disabledAccessUrls = preferred.disabledAccessUrls;
|
|
18998
|
+
if (preferred.accessUrl) {
|
|
18999
|
+
break;
|
|
19000
|
+
}
|
|
18515
19001
|
}
|
|
18516
19002
|
}
|
|
18517
19003
|
try {
|
|
@@ -18527,18 +19013,31 @@ function registerHostingTools(server) {
|
|
|
18527
19013
|
projectName = path_1.default.basename(input.localPath);
|
|
18528
19014
|
}
|
|
18529
19015
|
}
|
|
18530
|
-
|
|
18531
|
-
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
|
|
18535
|
-
|
|
18536
|
-
|
|
19016
|
+
// sendDeployNotification requires a concrete url; skip when no reachable accessUrl
|
|
19017
|
+
if (accessUrl) {
|
|
19018
|
+
await (0, notification_js_1.sendDeployNotification)(server, {
|
|
19019
|
+
deployType: 'hosting',
|
|
19020
|
+
url: accessUrl,
|
|
19021
|
+
projectId: envId,
|
|
19022
|
+
projectName,
|
|
19023
|
+
consoleUrl: `https://tcb.cloud.tencent.com/dev?envId=${envId}#/static-hosting`,
|
|
19024
|
+
});
|
|
19025
|
+
}
|
|
18537
19026
|
}
|
|
18538
19027
|
catch {
|
|
18539
19028
|
// Notification failure should not block uploads.
|
|
18540
19029
|
}
|
|
18541
19030
|
const uploadPrefix = input.cloudPath ?? input.files?.[0]?.cloudPath ?? '';
|
|
19031
|
+
const routeDisabled = staticDomainRouteEnabled === false;
|
|
19032
|
+
let message = '静态托管文件上传成功。若需要校验上传结果,请继续调用 queryHosting(action="findFiles") 或 queryHosting(action="listFiles")。';
|
|
19033
|
+
if (routeDisabled && accessUrlReachable) {
|
|
19034
|
+
message =
|
|
19035
|
+
'静态托管文件上传成功。默认静态托管域名的网关路由已禁用(访问会返回 GATEWAY_ROUTE_DISABLED),已改用其他可达域名作为 accessUrl。可用 manageGateway(action="updateRoute", route.enable=true) 重新启用默认域路由。';
|
|
19036
|
+
}
|
|
19037
|
+
else if (routeDisabled && !accessUrlReachable) {
|
|
19038
|
+
message =
|
|
19039
|
+
'静态托管文件上传成功,但默认静态托管域名的网关路由已禁用(GATEWAY_ROUTE_DISABLED),当前没有可达的 accessUrl。请用 manageGateway(action="updateRoute", domain="<静态域名>", path="/", upstreamResourceType="STATIC_STORE", targetName="staticstore", route.enable=true) 启用路由,或改用已启用的自定义域名 / CloudBase Sites 域名访问。';
|
|
19040
|
+
}
|
|
18542
19041
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
18543
19042
|
success: true,
|
|
18544
19043
|
data: {
|
|
@@ -18548,13 +19047,19 @@ function registerHostingTools(server) {
|
|
|
18548
19047
|
files: input.files ?? [],
|
|
18549
19048
|
ignore: input.ignore,
|
|
18550
19049
|
staticDomain,
|
|
18551
|
-
|
|
19050
|
+
staticDomainRouteEnabled,
|
|
19051
|
+
accessUrl: accessUrl || undefined,
|
|
18552
19052
|
accessUrls,
|
|
18553
19053
|
accessUrlSource,
|
|
19054
|
+
accessUrlReachable,
|
|
19055
|
+
...(disabledAccessUrls.length > 0
|
|
19056
|
+
? { disabledAccessUrls }
|
|
19057
|
+
: {}),
|
|
19058
|
+
...(routeDisabled ? { routeDisabled: true } : {}),
|
|
18554
19059
|
result,
|
|
18555
19060
|
nextActions: uploadPrefix ? [buildFindFilesNextStep(uploadPrefix.replace(/^\/+/, ''))] : undefined,
|
|
18556
19061
|
},
|
|
18557
|
-
message
|
|
19062
|
+
message,
|
|
18558
19063
|
});
|
|
18559
19064
|
}
|
|
18560
19065
|
case 'delete': {
|
|
@@ -18757,6 +19262,7 @@ function registerHostingTools(server) {
|
|
|
18757
19262
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
18758
19263
|
exports.buildVerificationUriComplete = buildVerificationUriComplete;
|
|
18759
19264
|
exports.buildDeviceAuthChallengePayload = buildDeviceAuthChallengePayload;
|
|
19265
|
+
exports.getCloudBaseApiKeyFromEnv = getCloudBaseApiKeyFromEnv;
|
|
18760
19266
|
exports.getAuthProgressStateSync = getAuthProgressStateSync;
|
|
18761
19267
|
exports.getAuthProgressState = getAuthProgressState;
|
|
18762
19268
|
exports.resolveAuthOptions = resolveAuthOptions;
|
|
@@ -18858,16 +19364,24 @@ function updateAuthProgressState(partial) {
|
|
|
18858
19364
|
});
|
|
18859
19365
|
return getAuthProgressStateSync();
|
|
18860
19366
|
}
|
|
19367
|
+
/**
|
|
19368
|
+
* Resolve CloudBase API Key from env.
|
|
19369
|
+
* Prefer CLOUDBASE_API_KEY; fall back to CLOUDBASE_APIKEY (js-sdk / node-sdk convention).
|
|
19370
|
+
*/
|
|
19371
|
+
function getCloudBaseApiKeyFromEnv() {
|
|
19372
|
+
return process.env.CLOUDBASE_API_KEY || process.env.CLOUDBASE_APIKEY || undefined;
|
|
19373
|
+
}
|
|
18861
19374
|
function normalizeLoginStateFromEnvVars(options) {
|
|
18862
19375
|
if (options?.ignoreEnvVars) {
|
|
18863
19376
|
return null;
|
|
18864
19377
|
}
|
|
18865
|
-
//
|
|
18866
|
-
const
|
|
18867
|
-
|
|
19378
|
+
// Prefer CloudBase API Key over TENCENTCLOUD_*
|
|
19379
|
+
const apiKey = getCloudBaseApiKeyFromEnv();
|
|
19380
|
+
const CLOUDBASE_ENV = process.env.CLOUDBASE_ENV_ID;
|
|
19381
|
+
if (apiKey && CLOUDBASE_ENV) {
|
|
18868
19382
|
return {
|
|
18869
19383
|
_type: 'api_key',
|
|
18870
|
-
apiKey
|
|
19384
|
+
apiKey,
|
|
18871
19385
|
envId: CLOUDBASE_ENV,
|
|
18872
19386
|
};
|
|
18873
19387
|
}
|
|
@@ -19039,7 +19553,7 @@ async function peekLoginState(options) {
|
|
|
19039
19553
|
}
|
|
19040
19554
|
async function ensureLogin(options) {
|
|
19041
19555
|
(0, logger_js_1.debug)("TENCENTCLOUD_SECRETID", { hasSecretId: !!process.env.TENCENTCLOUD_SECRETID });
|
|
19042
|
-
(0, logger_js_1.debug)("CLOUDBASE_API_KEY", { hasApiKey: !!
|
|
19556
|
+
(0, logger_js_1.debug)("CLOUDBASE_API_KEY", { hasApiKey: !!getCloudBaseApiKeyFromEnv() });
|
|
19043
19557
|
const loginState = await peekLoginState({
|
|
19044
19558
|
ignoreEnvVars: options?.ignoreEnvVars,
|
|
19045
19559
|
});
|