@cloudbase/cloudbase-mcp 2.21.1 → 2.22.0-beta.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 +21 -21
- package/dist/cli.cjs +3 -3
- package/dist/index.cjs +2528 -677
- package/dist/index.d.ts +133 -1
- package/dist/index.js +2550 -710
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import * as __WEBPACK_EXTERNAL_MODULE_https__ from "https";
|
|
|
13
13
|
import * as __WEBPACK_EXTERNAL_MODULE_net__ from "net";
|
|
14
14
|
import * as __WEBPACK_EXTERNAL_MODULE_fs__ from "fs";
|
|
15
15
|
import * as __WEBPACK_EXTERNAL_MODULE__modelcontextprotocol_sdk_server_mcp_js_45c326f0__ from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
16
|
+
import * as __WEBPACK_EXTERNAL_MODULE_node_path_02319fef__ from "node:path";
|
|
16
17
|
import * as __WEBPACK_EXTERNAL_MODULE_child_process__ from "child_process";
|
|
17
18
|
import * as __WEBPACK_EXTERNAL_MODULE_ws__ from "ws";
|
|
18
19
|
import * as __WEBPACK_EXTERNAL_MODULE__cloudbase_manager_node_6a52ea40__ from "@cloudbase/manager-node";
|
|
@@ -23,6 +24,7 @@ import * as __WEBPACK_EXTERNAL_MODULE_crypto__ from "crypto";
|
|
|
23
24
|
import * as __WEBPACK_EXTERNAL_MODULE_fs_promises_f8dae9d1__ from "fs/promises";
|
|
24
25
|
import * as __WEBPACK_EXTERNAL_MODULE_os__ from "os";
|
|
25
26
|
import * as __WEBPACK_EXTERNAL_MODULE_winston__ from "winston";
|
|
27
|
+
import * as __WEBPACK_EXTERNAL_MODULE_node_fs_75ed2103__ from "node:fs";
|
|
26
28
|
/******/ var __webpack_modules__ = ({
|
|
27
29
|
|
|
28
30
|
/***/ 622:
|
|
@@ -183,17 +185,17 @@ function buildEnvDomainManagementResult(params) {
|
|
|
183
185
|
operation: action,
|
|
184
186
|
targetDomains: domains,
|
|
185
187
|
asyncState: "PENDING",
|
|
186
|
-
message: '安全域名已提交添加请求。该变更通常需要约 10 分钟传播,请继续轮询
|
|
188
|
+
message: '安全域名已提交添加请求。该变更通常需要约 10 分钟传播,请继续轮询 queryEnv(action="domains"),直到目标域名状态为 ENABLE。',
|
|
187
189
|
propagation: {
|
|
188
190
|
requiresPolling: true,
|
|
189
|
-
pollTool: "
|
|
191
|
+
pollTool: "queryEnv",
|
|
190
192
|
pollAction: "domains",
|
|
191
193
|
pollIntervalSuggestionSeconds: 10,
|
|
192
194
|
timeoutSuggestionSeconds: 600,
|
|
193
|
-
successCondition: '目标域名出现在
|
|
195
|
+
successCondition: '目标域名出现在 queryEnv(action="domains") 返回中,且 Status 为 ENABLE。',
|
|
194
196
|
},
|
|
195
197
|
next_step: {
|
|
196
|
-
tool: "
|
|
198
|
+
tool: "queryEnv",
|
|
197
199
|
action: "domains",
|
|
198
200
|
suggested_args: {
|
|
199
201
|
action: "domains",
|
|
@@ -208,17 +210,17 @@ function buildEnvDomainManagementResult(params) {
|
|
|
208
210
|
operation: action,
|
|
209
211
|
targetDomains: domains,
|
|
210
212
|
asyncState: "PENDING",
|
|
211
|
-
message: '安全域名已提交删除请求。该变更可能需要数分钟传播,请继续轮询
|
|
213
|
+
message: '安全域名已提交删除请求。该变更可能需要数分钟传播,请继续轮询 queryEnv(action="domains"),直到目标域名不再出现。',
|
|
212
214
|
propagation: {
|
|
213
215
|
requiresPolling: true,
|
|
214
|
-
pollTool: "
|
|
216
|
+
pollTool: "queryEnv",
|
|
215
217
|
pollAction: "domains",
|
|
216
218
|
pollIntervalSuggestionSeconds: 10,
|
|
217
219
|
timeoutSuggestionSeconds: 600,
|
|
218
|
-
successCondition: '目标域名不再出现在
|
|
220
|
+
successCondition: '目标域名不再出现在 queryEnv(action="domains") 返回中。',
|
|
219
221
|
},
|
|
220
222
|
next_step: {
|
|
221
|
-
tool: "
|
|
223
|
+
tool: "queryEnv",
|
|
222
224
|
action: "domains",
|
|
223
225
|
suggested_args: {
|
|
224
226
|
action: "domains",
|
|
@@ -492,7 +494,7 @@ function buildEnvQueryListResult(params) {
|
|
|
492
494
|
const paginated = paginateEnvList(filteredList, params.filters.offset, params.filters.limit);
|
|
493
495
|
const exactEnvIdSummaryHint = params.filters.envId
|
|
494
496
|
? {
|
|
495
|
-
tool: "
|
|
497
|
+
tool: "queryEnv",
|
|
496
498
|
action: "info",
|
|
497
499
|
reason: "action=list with envId only returns a concise summary. Use action=info to fetch detailed environment information such as full resource metadata and additional environment details.",
|
|
498
500
|
}
|
|
@@ -557,13 +559,122 @@ async function enrichEnvInfoWithBilling(params) {
|
|
|
557
559
|
return params.result;
|
|
558
560
|
}
|
|
559
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* Derive a RuntimeMode hint from the EnvInfo payload so the AI agent can
|
|
564
|
+
* immediately tell which CloudBase data backends are actually present in
|
|
565
|
+
* this environment.
|
|
566
|
+
*
|
|
567
|
+
* Reality model (revised after observing a real PG environment): a single
|
|
568
|
+
* CloudBase environment can have any combination of:
|
|
569
|
+
* - PostgreSQL (CloudBase PG / pgstore): signaled by `EnvInfo.PostgreSQL[]`
|
|
570
|
+
* non-empty and/or `EnvInfo.Meta` containing `postgresql=enable`.
|
|
571
|
+
* - NoSQL document database + the matching legacy COS-style bucket:
|
|
572
|
+
* signaled by `EnvInfo.Databases[]` (with InstanceId / RUNNING) and the
|
|
573
|
+
* bucket exposed in `EnvInfo.Storages[]`.
|
|
574
|
+
* - MySQL: signaled by a non-empty `EnvInfo.MysqlInstances[]` (or similar
|
|
575
|
+
* field, name varies). In a pure PG environment this is absent.
|
|
576
|
+
*
|
|
577
|
+
* In particular: a CloudBase PG environment commonly STILL has the NoSQL
|
|
578
|
+
* Database + Storage running in parallel. NoSQL is therefore "co-present"
|
|
579
|
+
* — its collection APIs, NoSQL `securityRule`s, and the legacy
|
|
580
|
+
* `app.uploadFile()` upload flow remain valid for collections / files
|
|
581
|
+
* that already live there. The thing that is NOT a substitute for the new
|
|
582
|
+
* PG surface is using NoSQL collections to model a brand-new business
|
|
583
|
+
* table that the task explicitly puts in PG.
|
|
584
|
+
*
|
|
585
|
+
* What this enrichment does:
|
|
586
|
+
* - Adds `EnvInfo.RuntimeMode = "postgresql" | "nosql"` based on whether
|
|
587
|
+
* PG is present. This is the recommended primary backend for new
|
|
588
|
+
* business data when set to "postgresql".
|
|
589
|
+
* - Adds `EnvInfo.RuntimeBackends`, a structured snapshot of which
|
|
590
|
+
* backends are actually available (postgresql / nosql / mysql), so the
|
|
591
|
+
* agent does not have to re-read `Databases`/`Storages`/`PostgreSQL`.
|
|
592
|
+
* - Adds `EnvInfo.RuntimeModeHints` summarizing which API/tool to prefer
|
|
593
|
+
* for new code, including an explicit `MysqlNotAvailable` line when
|
|
594
|
+
* MySQL is absent — that one IS a hard "do not use" signal.
|
|
595
|
+
*/
|
|
596
|
+
function enrichEnvInfoWithRuntimeMode(result) {
|
|
597
|
+
const envInfo = result?.EnvInfo;
|
|
598
|
+
if (!envInfo || typeof envInfo !== "object") {
|
|
599
|
+
return result;
|
|
600
|
+
}
|
|
601
|
+
const pgList = Array.isArray(envInfo.PostgreSQL) ? envInfo.PostgreSQL : [];
|
|
602
|
+
const metaList = Array.isArray(envInfo.Meta) ? envInfo.Meta : [];
|
|
603
|
+
const metaPostgresEnabled = metaList.some((item) => item &&
|
|
604
|
+
typeof item.Key === "string" &&
|
|
605
|
+
/^postgre[_]?sql$/i.test(item.Key) &&
|
|
606
|
+
String(item.Value).toLowerCase() === "enable");
|
|
607
|
+
const hasPostgresql = pgList.length > 0 || metaPostgresEnabled;
|
|
608
|
+
// NoSQL document DB shows up under Databases[]; the matching legacy bucket
|
|
609
|
+
// shows up under Storages[]. Treat NoSQL as available when either side is
|
|
610
|
+
// non-empty (an old env may keep one without the other).
|
|
611
|
+
const databasesList = Array.isArray(envInfo.Databases)
|
|
612
|
+
? envInfo.Databases
|
|
613
|
+
: [];
|
|
614
|
+
const storagesList = Array.isArray(envInfo.Storages) ? envInfo.Storages : [];
|
|
615
|
+
const hasNoSql = databasesList.length > 0 || storagesList.length > 0;
|
|
616
|
+
// MySQL field name has been seen as MysqlInstances / MySQLInstances /
|
|
617
|
+
// MySQL across API versions; check any of them.
|
|
618
|
+
const mysqlList = (() => {
|
|
619
|
+
for (const k of ["MysqlInstances", "MySQLInstances", "MySQL"]) {
|
|
620
|
+
const v = envInfo[k];
|
|
621
|
+
if (Array.isArray(v) && v.length > 0)
|
|
622
|
+
return v;
|
|
623
|
+
}
|
|
624
|
+
return [];
|
|
625
|
+
})();
|
|
626
|
+
const hasMysql = mysqlList.length > 0;
|
|
627
|
+
// Primary mode: prefer PG when it is provisioned, otherwise fall back to
|
|
628
|
+
// legacy NoSQL labeling. This drives "what skill to read first / what API
|
|
629
|
+
// to reach for when starting new business code".
|
|
630
|
+
const runtimeMode = hasPostgresql
|
|
631
|
+
? "postgresql"
|
|
632
|
+
: "nosql";
|
|
633
|
+
const hints = hasPostgresql
|
|
634
|
+
? {
|
|
635
|
+
PrimaryBackend: "PostgreSQL (CloudBase PG) is provisioned — prefer it for any NEW business data the task introduces.",
|
|
636
|
+
BusinessDataAPI: "For NEW business data on PG: use CloudBase JS SDK v3 `app.rdb()` (Supabase-style chained query). Existing NoSQL collections in this env keep working through `app.database()`; do not migrate them unless the task asks.",
|
|
637
|
+
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.",
|
|
638
|
+
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.",
|
|
639
|
+
CoexistingNoSQL: hasNoSql
|
|
640
|
+
? "This env also has the legacy NoSQL Database + Storage running. Existing collections, existing `app.uploadFile()` calls, existing `managePermissions(resourceType=\"noSqlDatabase\")` rules all remain valid for legacy data."
|
|
641
|
+
: "No legacy NoSQL Database/Storage observed in this env.",
|
|
642
|
+
MysqlNotAvailable: hasMysql
|
|
643
|
+
? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
|
|
644
|
+
: "No MySQL instance is provisioned for this env. Do NOT use `manageSqlDatabase` / `querySqlDatabase` (those are MySQL-specific) and do NOT read the `relational-database-tool` skill — that family targets MySQL, not CloudBase PG.",
|
|
645
|
+
RecommendedSkills: "Read `postgresql-development` first for new PG code. `no-sql-web-sdk` is still applicable for existing NoSQL collections in this env. Skip `relational-database-tool` (MySQL-only) entirely.",
|
|
646
|
+
}
|
|
647
|
+
: {
|
|
648
|
+
PrimaryBackend: "PostgreSQL is NOT provisioned in this env — this is a legacy NoSQL CloudBase backend.",
|
|
649
|
+
BusinessDataAPI: "Use `app.database()` collections via `@cloudbase/js-sdk` for browser business data. Do not switch to `app.rdb()` here — PG is not available.",
|
|
650
|
+
Permissions: "Use `managePermissions(resourceType=\"noSqlDatabase\", securityRule=...)` for collection rules. PG-only RLS guidance (e.g. `auth.uid()` SQL policies) does not apply here.",
|
|
651
|
+
Storage: "Browser uploads use `app.uploadFile()` against the bucket exposed in `EnvInfo.Storages[].Bucket`.",
|
|
652
|
+
MysqlNotAvailable: hasMysql
|
|
653
|
+
? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
|
|
654
|
+
: "No MySQL instance in this env. `manageSqlDatabase` / `querySqlDatabase` and the `relational-database-tool` skill are not applicable.",
|
|
655
|
+
RecommendedSkills: "Read `no-sql-web-sdk` (and `cloud-storage-web` for uploads). `postgresql-development` and `relational-database-tool` are not applicable to this env.",
|
|
656
|
+
};
|
|
657
|
+
return {
|
|
658
|
+
...result,
|
|
659
|
+
EnvInfo: {
|
|
660
|
+
...envInfo,
|
|
661
|
+
RuntimeMode: runtimeMode,
|
|
662
|
+
RuntimeBackends: {
|
|
663
|
+
postgresql: hasPostgresql,
|
|
664
|
+
nosql: hasNoSql,
|
|
665
|
+
mysql: hasMysql,
|
|
666
|
+
},
|
|
667
|
+
RuntimeModeHints: hints,
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
}
|
|
560
671
|
function normalizeOptionalToolString(value) {
|
|
561
672
|
return typeof value === "string" && value.trim().length > 0
|
|
562
673
|
? value.trim()
|
|
563
674
|
: undefined;
|
|
564
675
|
}
|
|
565
676
|
/**
|
|
566
|
-
* Build enhanced error message for
|
|
677
|
+
* Build enhanced error message for queryEnv tool errors
|
|
567
678
|
* Provides actionable guidance based on error patterns
|
|
568
679
|
*/
|
|
569
680
|
function buildEnvQueryErrorMessage(error, action) {
|
|
@@ -579,7 +690,7 @@ function buildEnvQueryErrorMessage(error, action) {
|
|
|
579
690
|
suggestions.push("参数错误:可能是认证信息无效或已过期,请尝试以下步骤:");
|
|
580
691
|
suggestions.push("1. 先调用 auth(action=\"status\") 检查当前登录状态");
|
|
581
692
|
suggestions.push("2. 如果未登录,调用 auth(action=\"start_auth\", authMode=\"device\") 完成登录");
|
|
582
|
-
suggestions.push("3. 登录完成后再次调用
|
|
693
|
+
suggestions.push("3. 登录完成后再次调用 queryEnv(action=\"list\")");
|
|
583
694
|
}
|
|
584
695
|
if (hasAuthError) {
|
|
585
696
|
suggestions.push("认证错误:当前未登录或认证已过期。");
|
|
@@ -591,7 +702,7 @@ function buildEnvQueryErrorMessage(error, action) {
|
|
|
591
702
|
}
|
|
592
703
|
if (hasEnvNotFoundError) {
|
|
593
704
|
suggestions.push("环境错误:指定的环境不存在或无法访问。");
|
|
594
|
-
suggestions.push("请使用
|
|
705
|
+
suggestions.push("请使用 queryEnv(action=\"list\") 查看可用的环境列表。");
|
|
595
706
|
}
|
|
596
707
|
if (hasNetworkError) {
|
|
597
708
|
suggestions.push("网络错误:请检查网络连接,稍后重试。");
|
|
@@ -603,7 +714,7 @@ function buildEnvQueryErrorMessage(error, action) {
|
|
|
603
714
|
suggestions.push("2. 如未登录,执行 auth(action=\"start_auth\") 完成认证");
|
|
604
715
|
suggestions.push("3. 确认环境 ID 正确且可访问");
|
|
605
716
|
}
|
|
606
|
-
return `[
|
|
717
|
+
return `[queryEnv/${action}] 调用失败: ${baseMessage}\n\n解决建议:\n${suggestions.join("\n")}`;
|
|
607
718
|
}
|
|
608
719
|
function normalizeOptionalToolBoolean(value) {
|
|
609
720
|
return typeof value === "boolean" ? value : undefined;
|
|
@@ -632,519 +743,500 @@ function registerEnvTools(server) {
|
|
|
632
743
|
const supportedAuthActions = getSupportedAuthActions(server);
|
|
633
744
|
const authActionEnum = [...supportedAuthActions];
|
|
634
745
|
// auth - CloudBase (云开发) 开发阶段登录与环境绑定
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
.
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
.
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
.
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
.
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
.
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
envId: zod_1.z
|
|
664
|
-
.string()
|
|
665
|
-
.optional()
|
|
666
|
-
.describe("环境ID(CloudBase 环境唯一标识),绑定后工具将操作该环境。action=set_env 时必填"),
|
|
667
|
-
...(supportedAuthActions.includes("logout")
|
|
668
|
-
? {
|
|
669
|
-
confirm: zod_1.z
|
|
670
|
-
.literal("yes")
|
|
671
|
-
.optional()
|
|
672
|
-
.describe("action=logout 时确认操作,传 yes"),
|
|
673
|
-
}
|
|
674
|
-
: {}),
|
|
675
|
-
...(supportedAuthActions.includes("get_temp_credentials")
|
|
676
|
-
? {
|
|
677
|
-
reveal: zod_1.z
|
|
678
|
-
.boolean()
|
|
679
|
-
.optional()
|
|
680
|
-
.describe("action=get_temp_credentials 时可选。true=返回明文临时密钥;默认 false 仅返回脱敏结果"),
|
|
681
|
-
}
|
|
682
|
-
: {}),
|
|
683
|
-
},
|
|
684
|
-
annotations: {
|
|
685
|
-
readOnlyHint: false,
|
|
686
|
-
destructiveHint: false,
|
|
687
|
-
idempotentHint: false,
|
|
688
|
-
openWorldHint: true,
|
|
689
|
-
category: "env",
|
|
690
|
-
},
|
|
691
|
-
}, async (rawArgs) => {
|
|
692
|
-
const action = rawArgs.action ?? "status";
|
|
693
|
-
const authMode = rawArgs.authMode === "device" || rawArgs.authMode === "web"
|
|
694
|
-
? rawArgs.authMode
|
|
695
|
-
: undefined;
|
|
696
|
-
const oauthEndpoint = normalizeOptionalToolString(rawArgs.oauthEndpoint);
|
|
697
|
-
const clientId = normalizeOptionalToolString(rawArgs.clientId);
|
|
698
|
-
const oauthCustom = normalizeOptionalToolBoolean(rawArgs.oauthCustom);
|
|
699
|
-
const envId = rawArgs.envId;
|
|
700
|
-
const confirm = rawArgs.confirm === "yes" ? "yes" : undefined;
|
|
701
|
-
const reveal = normalizeOptionalToolBoolean(rawArgs.reveal) === true;
|
|
702
|
-
const resolvedAuthOptions = resolveToolAuthOptions(server, {
|
|
703
|
-
authMode,
|
|
704
|
-
oauthEndpoint,
|
|
705
|
-
clientId,
|
|
706
|
-
oauthCustom,
|
|
707
|
-
});
|
|
708
|
-
const authConfigSummary = (0, auth_js_1.buildAuthConfigSummary)(resolvedAuthOptions);
|
|
709
|
-
let deviceAuthInfo;
|
|
710
|
-
const onDeviceCode = (info) => {
|
|
711
|
-
deviceAuthInfo = info;
|
|
712
|
-
(0, auth_js_1.setPendingAuthProgressState)(info, "device");
|
|
713
|
-
// emitDeviceAuthNotice(server, info);
|
|
714
|
-
};
|
|
715
|
-
const authChallenge = () => (0, auth_js_1.buildDeviceAuthChallengePayload)(deviceAuthInfo);
|
|
716
|
-
try {
|
|
717
|
-
if (!supportedAuthActions.includes(action)) {
|
|
718
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
719
|
-
ok: false,
|
|
720
|
-
code: "NOT_SUPPORTED",
|
|
721
|
-
message: `当前 IDE 不支持 auth(action="${action}")。`,
|
|
722
|
-
next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
723
|
-
suggestedArgs: { action: "status" },
|
|
724
|
-
}),
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
if (action === "status") {
|
|
728
|
-
const loginState = await (0, auth_js_1.peekLoginState)();
|
|
729
|
-
const authFlowState = await (0, auth_js_1.getAuthProgressState)();
|
|
730
|
-
// 判断是否为 API Key 模式
|
|
731
|
-
const isApiKeyMode = !!(process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID);
|
|
732
|
-
const authStatus = loginState
|
|
733
|
-
? "READY"
|
|
734
|
-
: authFlowState.status === "PENDING"
|
|
735
|
-
? "PENDING"
|
|
736
|
-
: "REQUIRED";
|
|
737
|
-
let envPreparation;
|
|
738
|
-
const message = authStatus === "READY"
|
|
739
|
-
? undefined
|
|
740
|
-
: authStatus === "PENDING"
|
|
741
|
-
? "设备码授权进行中,请完成浏览器授权后再次调用 auth(action=\"status\")"
|
|
742
|
-
: isCodeBuddyIde(server)
|
|
743
|
-
? "当前未登录。CodeBuddy 暂不支持在 tool 内发起认证,请在外部完成认证后再次调用 auth(action=\"status\")。"
|
|
744
|
-
: "当前未登录,请先执行 auth(action=\"start_auth\")";
|
|
745
|
-
if (authStatus === "READY" && loginState) {
|
|
746
|
-
envPreparation = await prepareAuthEnvironment({
|
|
747
|
-
server,
|
|
748
|
-
cloudBaseOptions,
|
|
749
|
-
loginState,
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
753
|
-
ok: true,
|
|
754
|
-
code: "STATUS",
|
|
755
|
-
auth_status: authStatus,
|
|
756
|
-
...(isApiKeyMode ? { auth_mode: "api_key" } : {}),
|
|
757
|
-
auth_config: authConfigSummary,
|
|
758
|
-
...(envPreparation
|
|
759
|
-
? buildAuthEnvSetupPayload(envPreparation)
|
|
760
|
-
: {
|
|
761
|
-
env_status: "NONE",
|
|
762
|
-
current_env_id: null,
|
|
763
|
-
...buildEnvCandidatePayload([]),
|
|
764
|
-
}),
|
|
765
|
-
auth_challenge: authFlowState.status === "PENDING"
|
|
766
|
-
? (0, auth_js_1.buildDeviceAuthChallengePayload)(authFlowState.authChallenge)
|
|
767
|
-
: undefined,
|
|
768
|
-
message,
|
|
769
|
-
next_step: authStatus === "REQUIRED"
|
|
770
|
-
? buildAuthRequiredNextStep(server)
|
|
771
|
-
: authStatus === "PENDING"
|
|
772
|
-
? (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
773
|
-
suggestedArgs: { action: "status" },
|
|
774
|
-
})
|
|
775
|
-
: envPreparation?.nextStep,
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
if (action === "start_auth") {
|
|
779
|
-
// API Key 模式:尝试换取临时密钥
|
|
780
|
-
if (process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID) {
|
|
781
|
-
try {
|
|
782
|
-
const existingLoginState = await (0, auth_js_1.peekLoginState)();
|
|
783
|
-
if (existingLoginState) {
|
|
784
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
785
|
-
ok: true,
|
|
786
|
-
code: "AUTH_READY",
|
|
787
|
-
message: "当前使用 API Key 认证模式,已自动完成登录,无需手动授权。",
|
|
788
|
-
auth_mode: "api_key",
|
|
789
|
-
envId: process.env.CLOUDBASE_ENV_ID,
|
|
790
|
-
});
|
|
791
|
-
}
|
|
746
|
+
// 微信 IDE 使用票据认证,不需要登录工具
|
|
747
|
+
if (server.ide !== 'wxide') {
|
|
748
|
+
server.registerTool?.("auth", {
|
|
749
|
+
title: "CloudBase 开发阶段登录与环境",
|
|
750
|
+
description: "CloudBase(腾讯云开发)开发阶段登录与环境绑定。登录后即可访问云资源;环境(env)是云函数、数据库、静态托管等资源的隔离单元,绑定环境后其他 MCP 工具才能操作该环境。支持:查询状态、发起登录、绑定环境(set_env)、退出登录。",
|
|
751
|
+
inputSchema: {
|
|
752
|
+
action: zod_1.z
|
|
753
|
+
.enum(authActionEnum)
|
|
754
|
+
.optional()
|
|
755
|
+
.describe("动作:status=查询状态,start_auth=发起登录,set_env=绑定环境(传envId),logout=退出登录"),
|
|
756
|
+
...(supportedAuthActions.includes("start_auth")
|
|
757
|
+
? {
|
|
758
|
+
authMode: zod_1.z
|
|
759
|
+
.enum(["device", "web"])
|
|
760
|
+
.optional()
|
|
761
|
+
.describe("认证模式:device=设备码授权,web=浏览器回调授权"),
|
|
762
|
+
oauthEndpoint: zod_1.z
|
|
763
|
+
.string()
|
|
764
|
+
.optional()
|
|
765
|
+
.describe("高级可选:自定义 device-code 登录 endpoint。配置后 oauthCustom 默认按 true 处理"),
|
|
766
|
+
clientId: zod_1.z
|
|
767
|
+
.string()
|
|
768
|
+
.optional()
|
|
769
|
+
.describe("高级可选:自定义 device-code 登录 client_id,不传则使用默认值"),
|
|
770
|
+
oauthCustom: zod_1.z
|
|
771
|
+
.boolean()
|
|
772
|
+
.optional()
|
|
773
|
+
.describe("高级可选:自定义 endpoint 返回格式开关。未配置 endpoint 时默认 false;配置 endpoint 后默认 true,且不能设为 false"),
|
|
792
774
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
775
|
+
: {}),
|
|
776
|
+
envId: zod_1.z
|
|
777
|
+
.string()
|
|
778
|
+
.optional()
|
|
779
|
+
.describe("环境ID(CloudBase 环境唯一标识),绑定后工具将操作该环境。action=set_env 时必填"),
|
|
780
|
+
...(supportedAuthActions.includes("logout")
|
|
781
|
+
? {
|
|
782
|
+
confirm: zod_1.z
|
|
783
|
+
.literal("yes")
|
|
784
|
+
.optional()
|
|
785
|
+
.describe("action=logout 时确认操作,传 yes"),
|
|
796
786
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
787
|
+
: {}),
|
|
788
|
+
...(supportedAuthActions.includes("get_temp_credentials")
|
|
789
|
+
? {
|
|
790
|
+
reveal: zod_1.z
|
|
791
|
+
.boolean()
|
|
792
|
+
.optional()
|
|
793
|
+
.describe("action=get_temp_credentials 时可选。true=返回明文临时密钥;默认 false 仅返回脱敏结果"),
|
|
794
|
+
}
|
|
795
|
+
: {}),
|
|
796
|
+
},
|
|
797
|
+
annotations: {
|
|
798
|
+
readOnlyHint: false,
|
|
799
|
+
destructiveHint: false,
|
|
800
|
+
idempotentHint: false,
|
|
801
|
+
openWorldHint: true,
|
|
802
|
+
category: "env",
|
|
803
|
+
},
|
|
804
|
+
}, async (rawArgs) => {
|
|
805
|
+
const action = rawArgs.action ?? "status";
|
|
806
|
+
const authMode = rawArgs.authMode === "device" || rawArgs.authMode === "web"
|
|
807
|
+
? rawArgs.authMode
|
|
808
|
+
: undefined;
|
|
809
|
+
const oauthEndpoint = normalizeOptionalToolString(rawArgs.oauthEndpoint);
|
|
810
|
+
const clientId = normalizeOptionalToolString(rawArgs.clientId);
|
|
811
|
+
const oauthCustom = normalizeOptionalToolBoolean(rawArgs.oauthCustom);
|
|
812
|
+
const envId = rawArgs.envId;
|
|
813
|
+
const confirm = rawArgs.confirm === "yes" ? "yes" : undefined;
|
|
814
|
+
const reveal = normalizeOptionalToolBoolean(rawArgs.reveal) === true;
|
|
815
|
+
const resolvedAuthOptions = resolveToolAuthOptions(server, {
|
|
816
|
+
authMode,
|
|
817
|
+
oauthEndpoint,
|
|
818
|
+
clientId,
|
|
819
|
+
oauthCustom,
|
|
820
|
+
});
|
|
821
|
+
const authConfigSummary = (0, auth_js_1.buildAuthConfigSummary)(resolvedAuthOptions);
|
|
822
|
+
let deviceAuthInfo;
|
|
823
|
+
const onDeviceCode = (info) => {
|
|
824
|
+
deviceAuthInfo = info;
|
|
825
|
+
(0, auth_js_1.setPendingAuthProgressState)(info, "device");
|
|
826
|
+
// emitDeviceAuthNotice(server, info);
|
|
827
|
+
};
|
|
828
|
+
const authChallenge = () => (0, auth_js_1.buildDeviceAuthChallengePayload)(deviceAuthInfo);
|
|
829
|
+
try {
|
|
830
|
+
if (!supportedAuthActions.includes(action)) {
|
|
810
831
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
811
832
|
ok: false,
|
|
812
|
-
code: "
|
|
813
|
-
message:
|
|
814
|
-
auth_mode: "api_key",
|
|
815
|
-
envId: process.env.CLOUDBASE_ENV_ID,
|
|
816
|
-
endpoint,
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
const region = server.cloudBaseOptions?.region || process.env.TCB_REGION;
|
|
820
|
-
const auth = toolbox_1.AuthSupervisor.getInstance({});
|
|
821
|
-
const authFlowState = await (0, auth_js_1.getAuthProgressState)();
|
|
822
|
-
if (authFlowState.status === "PENDING" && authFlowState.authChallenge) {
|
|
823
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
824
|
-
ok: true,
|
|
825
|
-
code: "AUTH_PENDING",
|
|
826
|
-
message: "设备码授权进行中,请在浏览器中打开 verification_uri 并输入 user_code 完成授权。",
|
|
827
|
-
auth_challenge: (0, auth_js_1.buildDeviceAuthChallengePayload)(authFlowState.authChallenge),
|
|
833
|
+
code: "NOT_SUPPORTED",
|
|
834
|
+
message: `当前 IDE 不支持 auth(action="${action}")。`,
|
|
828
835
|
next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
829
836
|
suggestedArgs: { action: "status" },
|
|
830
837
|
}),
|
|
831
838
|
});
|
|
832
839
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
840
|
+
if (action === "status") {
|
|
841
|
+
const loginState = await (0, auth_js_1.peekLoginState)();
|
|
842
|
+
const authFlowState = await (0, auth_js_1.getAuthProgressState)();
|
|
843
|
+
// 判断是否为 API Key 模式
|
|
844
|
+
const isApiKeyMode = !!(process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID);
|
|
845
|
+
const authStatus = loginState
|
|
846
|
+
? "READY"
|
|
847
|
+
: authFlowState.status === "PENDING"
|
|
848
|
+
? "PENDING"
|
|
849
|
+
: "REQUIRED";
|
|
850
|
+
let envPreparation;
|
|
851
|
+
const message = authStatus === "READY"
|
|
852
|
+
? undefined
|
|
853
|
+
: authStatus === "PENDING"
|
|
854
|
+
? "设备码授权进行中,请完成浏览器授权后再次调用 auth(action=\"status\")"
|
|
855
|
+
: isCodeBuddyIde(server)
|
|
856
|
+
? "当前未登录。CodeBuddy 暂不支持在 tool 内发起认证,请在外部完成认证后再次调用 auth(action=\"status\")。"
|
|
857
|
+
: "当前未登录,请先执行 auth(action=\"start_auth\")";
|
|
858
|
+
if (authStatus === "READY" && loginState) {
|
|
859
|
+
envPreparation = await prepareAuthEnvironment({
|
|
838
860
|
server,
|
|
839
861
|
cloudBaseOptions,
|
|
840
|
-
loginState
|
|
841
|
-
});
|
|
842
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
843
|
-
ok: true,
|
|
844
|
-
code: "AUTH_READY",
|
|
845
|
-
message: envPreparation.message,
|
|
846
|
-
auth_challenge: authChallenge(),
|
|
847
|
-
...buildAuthEnvSetupPayload(envPreparation),
|
|
848
|
-
next_step: envPreparation.nextStep,
|
|
862
|
+
loginState,
|
|
849
863
|
});
|
|
850
864
|
}
|
|
851
|
-
}
|
|
852
|
-
catch {
|
|
853
|
-
// 忽略 getLoginState 错误,继续尝试发起登录
|
|
854
|
-
}
|
|
855
|
-
const validationError = (0, auth_js_1.getAuthConfigValidationError)(resolvedAuthOptions);
|
|
856
|
-
if (validationError) {
|
|
857
865
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
858
|
-
ok:
|
|
859
|
-
code: "
|
|
860
|
-
|
|
866
|
+
ok: true,
|
|
867
|
+
code: "STATUS",
|
|
868
|
+
auth_status: authStatus,
|
|
869
|
+
...(isApiKeyMode ? { auth_mode: "api_key" } : {}),
|
|
861
870
|
auth_config: authConfigSummary,
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
871
|
+
...(envPreparation
|
|
872
|
+
? buildAuthEnvSetupPayload(envPreparation)
|
|
873
|
+
: {
|
|
874
|
+
env_status: "NONE",
|
|
875
|
+
current_env_id: null,
|
|
876
|
+
...buildEnvCandidatePayload([]),
|
|
877
|
+
}),
|
|
878
|
+
auth_challenge: authFlowState.status === "PENDING"
|
|
879
|
+
? (0, auth_js_1.buildDeviceAuthChallengePayload)(authFlowState.authChallenge)
|
|
880
|
+
: undefined,
|
|
881
|
+
message,
|
|
882
|
+
next_step: authStatus === "REQUIRED"
|
|
883
|
+
? buildAuthRequiredNextStep(server)
|
|
884
|
+
: authStatus === "PENDING"
|
|
885
|
+
? (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
886
|
+
suggestedArgs: { action: "status" },
|
|
887
|
+
})
|
|
888
|
+
: envPreparation?.nextStep,
|
|
865
889
|
});
|
|
866
890
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
}
|
|
881
|
-
};
|
|
882
|
-
try {
|
|
883
|
-
// 启动 Device Flow,全流程由 toolbox 负责轮询和写入 credential,这里不等待完成
|
|
884
|
-
auth
|
|
885
|
-
.loginByWebAuth({
|
|
886
|
-
flow: "device",
|
|
887
|
-
...(resolvedAuthOptions.clientId
|
|
888
|
-
? { client_id: resolvedAuthOptions.clientId }
|
|
889
|
-
: {}),
|
|
890
|
-
...(resolvedAuthOptions.oauthEndpoint
|
|
891
|
-
? { getOAuthEndpoint: () => resolvedAuthOptions.oauthEndpoint }
|
|
892
|
-
: {}),
|
|
893
|
-
...(resolvedAuthOptions.oauthCustom
|
|
894
|
-
? { custom: true }
|
|
895
|
-
: {}),
|
|
896
|
-
onDeviceCode: deviceOnCode,
|
|
897
|
-
})
|
|
898
|
-
.then(() => {
|
|
899
|
-
(0, auth_js_1.resolveAuthProgressState)();
|
|
900
|
-
})
|
|
901
|
-
.catch((err) => {
|
|
902
|
-
(0, auth_js_1.rejectAuthProgressState)(err);
|
|
903
|
-
// 如果在拿到 device code 之前就失败,则唤醒当前调用并返回错误
|
|
904
|
-
if (!deviceAuthInfo && rejectCode) {
|
|
905
|
-
rejectCode(err);
|
|
891
|
+
if (action === "start_auth") {
|
|
892
|
+
// API Key 模式:尝试换取临时密钥
|
|
893
|
+
if (process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID) {
|
|
894
|
+
try {
|
|
895
|
+
const existingLoginState = await (0, auth_js_1.peekLoginState)();
|
|
896
|
+
if (existingLoginState) {
|
|
897
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
898
|
+
ok: true,
|
|
899
|
+
code: "AUTH_READY",
|
|
900
|
+
message: "当前使用 API Key 认证模式,已自动完成登录,无需手动授权。",
|
|
901
|
+
auth_mode: "api_key",
|
|
902
|
+
envId: process.env.CLOUDBASE_ENV_ID,
|
|
903
|
+
});
|
|
906
904
|
}
|
|
905
|
+
}
|
|
906
|
+
catch (e) {
|
|
907
|
+
// peekLoginState 内部异常,记录后 fall through
|
|
908
|
+
(0, logger_js_1.debug)("start_auth: peekLoginState threw", { error: e instanceof Error ? e.message : String(e) });
|
|
909
|
+
}
|
|
910
|
+
// API Key 换取失败:返回详细诊断信息
|
|
911
|
+
// 尝试获取更详细的错误信息
|
|
912
|
+
let diagMessage = "当前配置了 API Key 认证模式,但换取临时密钥失败。";
|
|
913
|
+
const endpoint = process.env.CLOUDBASE_API_ENDPOINT || `https://${process.env.CLOUDBASE_ENV_ID}.ap-shanghai.tcb-api.tencentcloudapi.com`;
|
|
914
|
+
diagMessage += `\n\n诊断信息:`;
|
|
915
|
+
diagMessage += `\n- CLOUDBASE_ENV_ID: ${process.env.CLOUDBASE_ENV_ID}`;
|
|
916
|
+
diagMessage += `\n- CLOUDBASE_API_KEY: ${process.env.CLOUDBASE_API_KEY.slice(0, 20)}...(已截断)`;
|
|
917
|
+
diagMessage += `\n- Endpoint: ${endpoint}`;
|
|
918
|
+
diagMessage += `\n\n可能原因:`;
|
|
919
|
+
diagMessage += `\n1. API Key 已过期或被删除`;
|
|
920
|
+
diagMessage += `\n2. Endpoint 不可达(网络/DNS 问题)`;
|
|
921
|
+
diagMessage += `\n3. CLOUDBASE_ENV_ID 与 API Key 所属环境不匹配`;
|
|
922
|
+
diagMessage += `\n\n建议:检查 MCP 配置中的 CLOUDBASE_API_KEY 和 CLOUDBASE_ENV_ID 环境变量是否正确。`;
|
|
923
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
924
|
+
ok: false,
|
|
925
|
+
code: "API_KEY_AUTH_FAILED",
|
|
926
|
+
message: diagMessage,
|
|
927
|
+
auth_mode: "api_key",
|
|
928
|
+
envId: process.env.CLOUDBASE_ENV_ID,
|
|
929
|
+
endpoint,
|
|
907
930
|
});
|
|
908
931
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
932
|
+
const region = server.cloudBaseOptions?.region || process.env.TCB_REGION;
|
|
933
|
+
const auth = toolbox_1.AuthSupervisor.getInstance({});
|
|
934
|
+
const authFlowState = await (0, auth_js_1.getAuthProgressState)();
|
|
935
|
+
if (authFlowState.status === "PENDING" && authFlowState.authChallenge) {
|
|
936
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
937
|
+
ok: true,
|
|
938
|
+
code: "AUTH_PENDING",
|
|
939
|
+
message: "设备码授权进行中,请在浏览器中打开 verification_uri 并输入 user_code 完成授权。",
|
|
940
|
+
auth_challenge: (0, auth_js_1.buildDeviceAuthChallengePayload)(authFlowState.authChallenge),
|
|
941
|
+
next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
942
|
+
suggestedArgs: { action: "status" },
|
|
943
|
+
}),
|
|
944
|
+
});
|
|
913
945
|
}
|
|
946
|
+
// 1. 如果已经有登录态,直接返回 AUTH_READY
|
|
914
947
|
try {
|
|
915
|
-
await
|
|
948
|
+
const existingLoginState = await (0, auth_js_1.peekLoginState)();
|
|
949
|
+
if (existingLoginState) {
|
|
950
|
+
const envPreparation = await prepareAuthEnvironment({
|
|
951
|
+
server,
|
|
952
|
+
cloudBaseOptions,
|
|
953
|
+
loginState: existingLoginState,
|
|
954
|
+
});
|
|
955
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
956
|
+
ok: true,
|
|
957
|
+
code: "AUTH_READY",
|
|
958
|
+
message: envPreparation.message,
|
|
959
|
+
auth_challenge: authChallenge(),
|
|
960
|
+
...buildAuthEnvSetupPayload(envPreparation),
|
|
961
|
+
next_step: envPreparation.nextStep,
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
catch {
|
|
966
|
+
// 忽略 getLoginState 错误,继续尝试发起登录
|
|
916
967
|
}
|
|
917
|
-
|
|
918
|
-
|
|
968
|
+
const validationError = (0, auth_js_1.getAuthConfigValidationError)(resolvedAuthOptions);
|
|
969
|
+
if (validationError) {
|
|
919
970
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
920
971
|
ok: false,
|
|
921
|
-
code: "
|
|
922
|
-
message:
|
|
972
|
+
code: "INVALID_ARGS",
|
|
973
|
+
message: validationError,
|
|
974
|
+
auth_config: authConfigSummary,
|
|
923
975
|
next_step: (0, tool_result_js_1.buildAuthNextStep)("start_auth", {
|
|
924
976
|
suggestedArgs: { action: "start_auth", authMode: "device" },
|
|
925
977
|
}),
|
|
926
978
|
});
|
|
927
979
|
}
|
|
928
|
-
|
|
980
|
+
// 2. 设备码模式:监听到 device code 即返回 AUTH_PENDING,后续由 toolbox 异步轮询并更新本地 credential
|
|
981
|
+
const effectiveMode = resolvedAuthOptions.authMode;
|
|
982
|
+
if (effectiveMode === "device") {
|
|
983
|
+
let resolveCode;
|
|
984
|
+
let rejectCode;
|
|
985
|
+
const codeReady = new Promise((resolve, reject) => {
|
|
986
|
+
resolveCode = resolve;
|
|
987
|
+
rejectCode = reject;
|
|
988
|
+
});
|
|
989
|
+
const deviceOnCode = (info) => {
|
|
990
|
+
onDeviceCode(info);
|
|
991
|
+
if (resolveCode) {
|
|
992
|
+
resolveCode();
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
try {
|
|
996
|
+
// 启动 Device Flow,全流程由 toolbox 负责轮询和写入 credential,这里不等待完成
|
|
997
|
+
auth
|
|
998
|
+
.loginByWebAuth({
|
|
999
|
+
flow: "device",
|
|
1000
|
+
...(resolvedAuthOptions.clientId
|
|
1001
|
+
? { client_id: resolvedAuthOptions.clientId }
|
|
1002
|
+
: {}),
|
|
1003
|
+
...(resolvedAuthOptions.oauthEndpoint
|
|
1004
|
+
? { getOAuthEndpoint: () => resolvedAuthOptions.oauthEndpoint }
|
|
1005
|
+
: {}),
|
|
1006
|
+
...(resolvedAuthOptions.oauthCustom
|
|
1007
|
+
? { custom: true }
|
|
1008
|
+
: {}),
|
|
1009
|
+
onDeviceCode: deviceOnCode,
|
|
1010
|
+
})
|
|
1011
|
+
.then(() => {
|
|
1012
|
+
(0, auth_js_1.resolveAuthProgressState)();
|
|
1013
|
+
})
|
|
1014
|
+
.catch((err) => {
|
|
1015
|
+
(0, auth_js_1.rejectAuthProgressState)(err);
|
|
1016
|
+
// 如果在拿到 device code 之前就失败,则唤醒当前调用并返回错误
|
|
1017
|
+
if (!deviceAuthInfo && rejectCode) {
|
|
1018
|
+
rejectCode(err);
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
catch (err) {
|
|
1023
|
+
if (rejectCode) {
|
|
1024
|
+
rejectCode(err);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
try {
|
|
1028
|
+
await codeReady;
|
|
1029
|
+
}
|
|
1030
|
+
catch (err) {
|
|
1031
|
+
const message = err instanceof Error ? err.message : String(err ?? "unknown error");
|
|
1032
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1033
|
+
ok: false,
|
|
1034
|
+
code: "AUTH_REQUIRED",
|
|
1035
|
+
message: `设备码登录初始化失败: ${message}`,
|
|
1036
|
+
next_step: (0, tool_result_js_1.buildAuthNextStep)("start_auth", {
|
|
1037
|
+
suggestedArgs: { action: "start_auth", authMode: "device" },
|
|
1038
|
+
}),
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
if (!deviceAuthInfo) {
|
|
1042
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1043
|
+
ok: false,
|
|
1044
|
+
code: "AUTH_REQUIRED",
|
|
1045
|
+
message: "未获取到设备码信息,请重试设备码登录",
|
|
1046
|
+
next_step: (0, tool_result_js_1.buildAuthNextStep)("start_auth", {
|
|
1047
|
+
suggestedArgs: { action: "start_auth", authMode: "device" },
|
|
1048
|
+
}),
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
const envCandidates = await fetchAvailableEnvCandidates(cloudBaseOptions, server);
|
|
1052
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1053
|
+
ok: true,
|
|
1054
|
+
code: "AUTH_PENDING",
|
|
1055
|
+
message: "已发起设备码登录,请在浏览器中打开 verification_uri 并输入 user_code 完成授权。授权完成后请再次调用 auth(action=\"status\")。",
|
|
1056
|
+
auth_challenge: authChallenge(),
|
|
1057
|
+
...buildEnvCandidatePayload(envCandidates),
|
|
1058
|
+
next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
1059
|
+
suggestedArgs: { action: "status" },
|
|
1060
|
+
}),
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
// 3. 非 Device Flow(显式 web 模式)仍然使用 getLoginState 阻塞等待
|
|
1064
|
+
const loginState = await (0, auth_js_1.ensureLogin)({
|
|
1065
|
+
region,
|
|
1066
|
+
authMode: effectiveMode,
|
|
1067
|
+
clientId: resolvedAuthOptions.clientId,
|
|
1068
|
+
oauthEndpoint: resolvedAuthOptions.oauthEndpoint,
|
|
1069
|
+
oauthCustom: resolvedAuthOptions.oauthCustom,
|
|
1070
|
+
});
|
|
1071
|
+
if (!loginState) {
|
|
929
1072
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
930
1073
|
ok: false,
|
|
931
1074
|
code: "AUTH_REQUIRED",
|
|
932
|
-
message: "
|
|
1075
|
+
message: "未获取到登录态,请先完成认证",
|
|
933
1076
|
next_step: (0, tool_result_js_1.buildAuthNextStep)("start_auth", {
|
|
934
|
-
suggestedArgs: { action: "start_auth", authMode:
|
|
1077
|
+
suggestedArgs: { action: "start_auth", authMode: effectiveMode },
|
|
935
1078
|
}),
|
|
936
1079
|
});
|
|
937
1080
|
}
|
|
938
|
-
const
|
|
1081
|
+
const envPreparation = await prepareAuthEnvironment({
|
|
1082
|
+
server,
|
|
1083
|
+
cloudBaseOptions,
|
|
1084
|
+
loginState,
|
|
1085
|
+
});
|
|
939
1086
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
940
1087
|
ok: true,
|
|
941
|
-
code: "
|
|
942
|
-
message:
|
|
1088
|
+
code: "AUTH_READY",
|
|
1089
|
+
message: envPreparation.message,
|
|
943
1090
|
auth_challenge: authChallenge(),
|
|
944
|
-
...
|
|
945
|
-
next_step:
|
|
946
|
-
suggestedArgs: { action: "status" },
|
|
947
|
-
}),
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
// 3. 非 Device Flow(显式 web 模式)仍然使用 getLoginState 阻塞等待
|
|
951
|
-
const loginState = await (0, auth_js_1.ensureLogin)({
|
|
952
|
-
region,
|
|
953
|
-
authMode: effectiveMode,
|
|
954
|
-
clientId: resolvedAuthOptions.clientId,
|
|
955
|
-
oauthEndpoint: resolvedAuthOptions.oauthEndpoint,
|
|
956
|
-
oauthCustom: resolvedAuthOptions.oauthCustom,
|
|
957
|
-
});
|
|
958
|
-
if (!loginState) {
|
|
959
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
960
|
-
ok: false,
|
|
961
|
-
code: "AUTH_REQUIRED",
|
|
962
|
-
message: "未获取到登录态,请先完成认证",
|
|
963
|
-
next_step: (0, tool_result_js_1.buildAuthNextStep)("start_auth", {
|
|
964
|
-
suggestedArgs: { action: "start_auth", authMode: effectiveMode },
|
|
965
|
-
}),
|
|
1091
|
+
...buildAuthEnvSetupPayload(envPreparation),
|
|
1092
|
+
next_step: envPreparation.nextStep,
|
|
966
1093
|
});
|
|
967
1094
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1095
|
+
if (action === "set_env") {
|
|
1096
|
+
const loginState = await (0, auth_js_1.peekLoginState)();
|
|
1097
|
+
if (!loginState) {
|
|
1098
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1099
|
+
ok: false,
|
|
1100
|
+
code: "AUTH_REQUIRED",
|
|
1101
|
+
message: isCodeBuddyIde(server)
|
|
1102
|
+
? "当前未登录。CodeBuddy 暂不支持在 tool 内发起认证,请在外部完成认证后再次调用 auth(action=\"status\")。"
|
|
1103
|
+
: "当前未登录,请先执行 auth(action=\"start_auth\")。",
|
|
1104
|
+
next_step: buildAuthRequiredNextStep(server),
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
const envCandidates = await fetchAvailableEnvCandidates(cloudBaseOptions, server);
|
|
1108
|
+
if (!envId) {
|
|
1109
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1110
|
+
ok: false,
|
|
1111
|
+
code: "INVALID_ARGS",
|
|
1112
|
+
message: "action=set_env 时必须提供 envId",
|
|
1113
|
+
...buildEnvCandidatePayload(envCandidates),
|
|
1114
|
+
next_step: buildSetEnvNextStep(envCandidates),
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
const target = envCandidates.find((item) => item.envId === envId);
|
|
1118
|
+
if (envCandidates.length > 0 && !target) {
|
|
1119
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1120
|
+
ok: false,
|
|
1121
|
+
code: "INVALID_ARGS",
|
|
1122
|
+
message: `未找到环境: ${envId}`,
|
|
1123
|
+
...buildEnvCandidatePayload(envCandidates),
|
|
1124
|
+
next_step: buildSetEnvNextStep(envCandidates),
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
await cloudbase_manager_js_1.envManager.setEnvId(envId);
|
|
985
1128
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
986
|
-
ok:
|
|
987
|
-
code: "
|
|
988
|
-
message:
|
|
989
|
-
|
|
990
|
-
: "当前未登录,请先执行 auth(action=\"start_auth\")。",
|
|
991
|
-
next_step: buildAuthRequiredNextStep(server),
|
|
1129
|
+
ok: true,
|
|
1130
|
+
code: "ENV_READY",
|
|
1131
|
+
message: `环境设置成功,当前环境: ${envId}`,
|
|
1132
|
+
current_env_id: envId,
|
|
992
1133
|
});
|
|
993
1134
|
}
|
|
994
|
-
|
|
995
|
-
|
|
1135
|
+
if (action === "logout") {
|
|
1136
|
+
// API Key 模式下不允许 logout
|
|
1137
|
+
if (process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID) {
|
|
1138
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1139
|
+
ok: false,
|
|
1140
|
+
code: "LOGOUT_NOT_ALLOWED",
|
|
1141
|
+
message: "当前使用 API Key 认证模式,不支持退出登录。如需切换认证方式,请移除 CLOUDBASE_API_KEY 环境变量后重启。",
|
|
1142
|
+
auth_mode: "api_key",
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
if (confirm !== "yes") {
|
|
1146
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1147
|
+
ok: false,
|
|
1148
|
+
code: "INVALID_ARGS",
|
|
1149
|
+
message: "action=logout 时必须传 confirm=\"yes\"",
|
|
1150
|
+
next_step: (0, tool_result_js_1.buildAuthNextStep)("logout", {
|
|
1151
|
+
suggestedArgs: { action: "logout", confirm: "yes" },
|
|
1152
|
+
}),
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
await (0, auth_js_1.logout)();
|
|
1156
|
+
(0, cloudbase_manager_js_1.resetCloudBaseManagerCache)();
|
|
996
1157
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
997
|
-
ok:
|
|
998
|
-
code: "
|
|
999
|
-
message: "
|
|
1000
|
-
...buildEnvCandidatePayload(envCandidates),
|
|
1001
|
-
next_step: buildSetEnvNextStep(envCandidates),
|
|
1158
|
+
ok: true,
|
|
1159
|
+
code: "LOGGED_OUT",
|
|
1160
|
+
message: "✅ 已退出登录",
|
|
1002
1161
|
});
|
|
1003
1162
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1163
|
+
if (action === "get_temp_credentials") {
|
|
1164
|
+
const loginState = (await (0, auth_js_1.peekLoginState)());
|
|
1165
|
+
if (!loginState) {
|
|
1166
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1167
|
+
ok: false,
|
|
1168
|
+
code: "AUTH_REQUIRED",
|
|
1169
|
+
message: "当前未登录,请先完成管理端认证后再获取临时密钥。",
|
|
1170
|
+
next_step: buildAuthRequiredNextStep(server),
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
if (confirm !== "yes") {
|
|
1174
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1175
|
+
ok: false,
|
|
1176
|
+
code: "INVALID_ARGS",
|
|
1177
|
+
message: "action=get_temp_credentials 时必须显式传 confirm=\"yes\",以确认你要导出当前管理端临时密钥。",
|
|
1178
|
+
next_step: (0, tool_result_js_1.buildAuthNextStep)("get_temp_credentials", {
|
|
1179
|
+
suggestedArgs: { action: "get_temp_credentials", confirm: "yes" },
|
|
1180
|
+
}),
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
if (!isTemporaryCredentialLoginState(loginState)) {
|
|
1184
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1185
|
+
ok: false,
|
|
1186
|
+
code: "UNSUPPORTED_CREDENTIAL_TYPE",
|
|
1187
|
+
message: "当前登录态不是可导出的临时密钥。仅支持通过 Web / device 登录得到的临时密钥,永久密钥登录不允许导出。",
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
const secretId = normalizeOptionalToolString(loginState.secretId);
|
|
1191
|
+
const secretKey = normalizeOptionalToolString(loginState.secretKey);
|
|
1192
|
+
const token = normalizeOptionalToolString(loginState.token);
|
|
1193
|
+
if (!secretId || !secretKey || !token) {
|
|
1194
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1195
|
+
ok: false,
|
|
1196
|
+
code: "INTERNAL_ERROR",
|
|
1197
|
+
message: "当前登录态缺少完整的临时密钥字段,请重新登录后再试。",
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1006
1200
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1007
|
-
ok:
|
|
1008
|
-
code: "
|
|
1009
|
-
message:
|
|
1010
|
-
|
|
1011
|
-
|
|
1201
|
+
ok: true,
|
|
1202
|
+
code: "TEMP_CREDENTIALS_READY",
|
|
1203
|
+
message: reveal
|
|
1204
|
+
? "当前管理端临时密钥已准备好,请注意避免泄露。"
|
|
1205
|
+
: "当前管理端临时密钥已准备好,默认仅返回脱敏结果。",
|
|
1206
|
+
env_id: normalizeOptionalToolString(loginState.envId) ?? null,
|
|
1207
|
+
credentials: {
|
|
1208
|
+
secretId: reveal ? secretId : maskSensitiveValue(secretId),
|
|
1209
|
+
secretKey: reveal ? secretKey : maskSensitiveValue(secretKey),
|
|
1210
|
+
token: reveal ? token : maskSensitiveValue(token),
|
|
1211
|
+
masked: !reveal,
|
|
1212
|
+
},
|
|
1012
1213
|
});
|
|
1013
1214
|
}
|
|
1014
|
-
await cloudbase_manager_js_1.envManager.setEnvId(envId);
|
|
1015
1215
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1016
|
-
ok:
|
|
1017
|
-
code: "
|
|
1018
|
-
message:
|
|
1019
|
-
|
|
1216
|
+
ok: false,
|
|
1217
|
+
code: "NOT_SUPPORTED",
|
|
1218
|
+
message: `不支持的 auth action: ${action}`,
|
|
1219
|
+
next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
1220
|
+
suggestedArgs: { action: "status" },
|
|
1221
|
+
}),
|
|
1020
1222
|
});
|
|
1021
1223
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
if (process.env.CLOUDBASE_API_KEY && process.env.CLOUDBASE_ENV_ID) {
|
|
1025
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1026
|
-
ok: false,
|
|
1027
|
-
code: "LOGOUT_NOT_ALLOWED",
|
|
1028
|
-
message: "当前使用 API Key 认证模式,不支持退出登录。如需切换认证方式,请移除 CLOUDBASE_API_KEY 环境变量后重启。",
|
|
1029
|
-
auth_mode: "api_key",
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
|
-
if (confirm !== "yes") {
|
|
1033
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1034
|
-
ok: false,
|
|
1035
|
-
code: "INVALID_ARGS",
|
|
1036
|
-
message: "action=logout 时必须传 confirm=\"yes\"",
|
|
1037
|
-
next_step: (0, tool_result_js_1.buildAuthNextStep)("logout", {
|
|
1038
|
-
suggestedArgs: { action: "logout", confirm: "yes" },
|
|
1039
|
-
}),
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
await (0, auth_js_1.logout)();
|
|
1043
|
-
(0, cloudbase_manager_js_1.resetCloudBaseManagerCache)();
|
|
1224
|
+
catch (error) {
|
|
1225
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1044
1226
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1045
|
-
ok:
|
|
1046
|
-
code: "
|
|
1047
|
-
message:
|
|
1227
|
+
ok: false,
|
|
1228
|
+
code: "INTERNAL_ERROR",
|
|
1229
|
+
message: `auth 执行失败: ${message}`,
|
|
1230
|
+
auth_challenge: authChallenge(),
|
|
1231
|
+
next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
1232
|
+
suggestedArgs: { action: "status" },
|
|
1233
|
+
}),
|
|
1048
1234
|
});
|
|
1049
1235
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
ok: false,
|
|
1055
|
-
code: "AUTH_REQUIRED",
|
|
1056
|
-
message: "当前未登录,请先完成管理端认证后再获取临时密钥。",
|
|
1057
|
-
next_step: buildAuthRequiredNextStep(server),
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
if (confirm !== "yes") {
|
|
1061
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1062
|
-
ok: false,
|
|
1063
|
-
code: "INVALID_ARGS",
|
|
1064
|
-
message: "action=get_temp_credentials 时必须显式传 confirm=\"yes\",以确认你要导出当前管理端临时密钥。",
|
|
1065
|
-
next_step: (0, tool_result_js_1.buildAuthNextStep)("get_temp_credentials", {
|
|
1066
|
-
suggestedArgs: { action: "get_temp_credentials", confirm: "yes" },
|
|
1067
|
-
}),
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
if (!isTemporaryCredentialLoginState(loginState)) {
|
|
1071
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1072
|
-
ok: false,
|
|
1073
|
-
code: "UNSUPPORTED_CREDENTIAL_TYPE",
|
|
1074
|
-
message: "当前登录态不是可导出的临时密钥。仅支持通过 Web / device 登录得到的临时密钥,永久密钥登录不允许导出。",
|
|
1075
|
-
});
|
|
1076
|
-
}
|
|
1077
|
-
const secretId = normalizeOptionalToolString(loginState.secretId);
|
|
1078
|
-
const secretKey = normalizeOptionalToolString(loginState.secretKey);
|
|
1079
|
-
const token = normalizeOptionalToolString(loginState.token);
|
|
1080
|
-
if (!secretId || !secretKey || !token) {
|
|
1081
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1082
|
-
ok: false,
|
|
1083
|
-
code: "INTERNAL_ERROR",
|
|
1084
|
-
message: "当前登录态缺少完整的临时密钥字段,请重新登录后再试。",
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1088
|
-
ok: true,
|
|
1089
|
-
code: "TEMP_CREDENTIALS_READY",
|
|
1090
|
-
message: reveal
|
|
1091
|
-
? "当前管理端临时密钥已准备好,请注意避免泄露。"
|
|
1092
|
-
: "当前管理端临时密钥已准备好,默认仅返回脱敏结果。",
|
|
1093
|
-
env_id: normalizeOptionalToolString(loginState.envId) ?? null,
|
|
1094
|
-
credentials: {
|
|
1095
|
-
secretId: reveal ? secretId : maskSensitiveValue(secretId),
|
|
1096
|
-
secretKey: reveal ? secretKey : maskSensitiveValue(secretKey),
|
|
1097
|
-
token: reveal ? token : maskSensitiveValue(token),
|
|
1098
|
-
masked: !reveal,
|
|
1099
|
-
},
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1103
|
-
ok: false,
|
|
1104
|
-
code: "NOT_SUPPORTED",
|
|
1105
|
-
message: `不支持的 auth action: ${action}`,
|
|
1106
|
-
next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
1107
|
-
suggestedArgs: { action: "status" },
|
|
1108
|
-
}),
|
|
1109
|
-
});
|
|
1110
|
-
}
|
|
1111
|
-
catch (error) {
|
|
1112
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1113
|
-
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1114
|
-
ok: false,
|
|
1115
|
-
code: "INTERNAL_ERROR",
|
|
1116
|
-
message: `auth 执行失败: ${message}`,
|
|
1117
|
-
auth_challenge: authChallenge(),
|
|
1118
|
-
next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
|
|
1119
|
-
suggestedArgs: { action: "status" },
|
|
1120
|
-
}),
|
|
1121
|
-
});
|
|
1122
|
-
}
|
|
1123
|
-
});
|
|
1124
|
-
// envQuery - 环境查询(合并 listEnvs + getEnvInfo + getEnvAuthDomains)
|
|
1125
|
-
server.registerTool?.("envQuery", {
|
|
1126
|
-
title: "CloudBase 环境查询",
|
|
1127
|
-
description: "查询 CloudBase 环境相关信息,支持查询环境列表、指定环境详情和安全域名。(原工具名:listEnvs/getEnvInfo/getEnvAuthDomains,为兼容旧AI规则可继续使用这些名称)当 action=list 时,会按 DescribeEnvs 语义做列表/筛选,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知 EnvId 对应环境的详细信息(包括资源字段和计费信息),必须使用 action=info 并传入目标环境的 envId 参数。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。",
|
|
1128
|
-
inputSchema: {
|
|
1129
|
-
action: zod_1.z
|
|
1130
|
-
.enum(["list", "info", "domains"])
|
|
1131
|
-
.describe("查询类型:list=环境列表/摘要筛选(按 DescribeEnvs 语义筛选,支持通过 envId 筛选,返回 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,不支持 expiry),info=指定环境的详细信息(必须传入 envId,返回资源字段和计费信息),domains=安全域名列表"),
|
|
1132
|
-
alias: zod_1.z.string().optional().describe("按环境别名筛选。action=list 时可选"),
|
|
1133
|
-
aliasExact: zod_1.z.boolean().optional().describe("按环境别名精确筛选。action=list 时可选;与 alias 配合使用"),
|
|
1134
|
-
envId: zod_1.z.string().optional().describe("按环境 ID 筛选。action=list 时可选(仅按 DescribeEnvs 语义做筛选,仍返回摘要);action=info 时必填(返回该环境的详细信息,包含资源字段和计费信息)。如果任务已经给出了明确的 EnvId 并要求查询详情,请直接使用 action=info + envId,而不是 action=list"),
|
|
1135
|
-
limit: zod_1.z.number().int().positive().optional().describe("返回数量上限。action=list 时可选"),
|
|
1136
|
-
offset: zod_1.z.number().int().min(0).optional().describe("分页偏移。action=list 时可选"),
|
|
1137
|
-
fields: zod_1.z
|
|
1138
|
-
.array(zod_1.z.enum(DEFAULT_ENV_FIELDS))
|
|
1139
|
-
.optional()
|
|
1140
|
-
.describe("返回字段白名单。仅支持 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault。action=list 时可选"),
|
|
1141
|
-
},
|
|
1142
|
-
annotations: {
|
|
1143
|
-
readOnlyHint: true,
|
|
1144
|
-
openWorldHint: true,
|
|
1145
|
-
category: "env",
|
|
1146
|
-
},
|
|
1147
|
-
}, async ({ action, alias, aliasExact, envId, limit, offset, fields, }) => {
|
|
1236
|
+
});
|
|
1237
|
+
} // end: wxide guard for auth tool
|
|
1238
|
+
// queryEnv - 环境查询(合并 listEnvs + getEnvInfo + getEnvAuthDomains)
|
|
1239
|
+
const queryEnvHandler = async ({ action, alias, aliasExact, envId, limit, offset, fields, }) => {
|
|
1148
1240
|
try {
|
|
1149
1241
|
let result;
|
|
1150
1242
|
switch (action) {
|
|
@@ -1158,7 +1250,8 @@ function registerEnvTools(server) {
|
|
|
1158
1250
|
// 当环境变量设置了 CLOUDBASE_ENV_ID 时(如 API Key 模式),
|
|
1159
1251
|
// 避免调用 DescribeEnvs(临时密钥可能不支持该接口),
|
|
1160
1252
|
// 改为通过 DescribeEnvInfo 获取单环境信息
|
|
1161
|
-
|
|
1253
|
+
// 注意:requestFn 模式(如微信 IDE)使用 DescribeEnvs,不走此分支
|
|
1254
|
+
const envIdFromEnv = !cloudBaseOptions?.requestFn && process.env.CLOUDBASE_ENV_ID;
|
|
1162
1255
|
if (envIdFromEnv) {
|
|
1163
1256
|
try {
|
|
1164
1257
|
const envInfo = await cloudbaseList.env.describeEnvInfo({ EnvId: envIdFromEnv });
|
|
@@ -1262,6 +1355,7 @@ function registerEnvTools(server) {
|
|
|
1262
1355
|
envId,
|
|
1263
1356
|
logger: server.logger,
|
|
1264
1357
|
});
|
|
1358
|
+
result = enrichEnvInfoWithRuntimeMode(result);
|
|
1265
1359
|
break;
|
|
1266
1360
|
case "domains":
|
|
1267
1361
|
const cloudbaseDomains = await getManager();
|
|
@@ -1325,61 +1419,91 @@ function registerEnvTools(server) {
|
|
|
1325
1419
|
],
|
|
1326
1420
|
};
|
|
1327
1421
|
}
|
|
1328
|
-
}
|
|
1329
|
-
//
|
|
1330
|
-
|
|
1331
|
-
title: "CloudBase
|
|
1332
|
-
description: "
|
|
1422
|
+
};
|
|
1423
|
+
// Register primary tool name (queryEnv)
|
|
1424
|
+
const queryEnvToolSchema = {
|
|
1425
|
+
title: "CloudBase 环境查询",
|
|
1426
|
+
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` 时,`manageSqlDatabase` / `querySqlDatabase` / `relational-database-tool` skill 都不该使用。",
|
|
1333
1427
|
inputSchema: {
|
|
1334
1428
|
action: zod_1.z
|
|
1335
|
-
.enum(["
|
|
1336
|
-
.describe("
|
|
1337
|
-
|
|
1429
|
+
.enum(["list", "info", "domains"])
|
|
1430
|
+
.describe("查询类型:list=环境列表/摘要筛选(按 DescribeEnvs 语义筛选,支持通过 envId 筛选,返回 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,不支持 expiry),info=指定环境的详细信息(必须传入 envId,返回资源字段和计费信息),domains=安全域名列表"),
|
|
1431
|
+
alias: zod_1.z.string().optional().describe("按环境别名筛选。action=list 时可选"),
|
|
1432
|
+
aliasExact: zod_1.z.boolean().optional().describe("按环境别名精确筛选。action=list 时可选;与 alias 配合使用"),
|
|
1433
|
+
envId: zod_1.z.string().optional().describe("按环境 ID 筛选。action=list 时可选(仅按 DescribeEnvs 语义做筛选,仍返回摘要);action=info 时必填(返回该环境的详细信息,包含资源字段和计费信息)。如果任务已经给出了明确的 EnvId 并要求查询详情,请直接使用 action=info + envId,而不是 action=list"),
|
|
1434
|
+
limit: zod_1.z.number().int().positive().optional().describe("返回数量上限。action=list 时可选"),
|
|
1435
|
+
offset: zod_1.z.number().int().min(0).optional().describe("分页偏移。action=list 时可选"),
|
|
1436
|
+
fields: zod_1.z
|
|
1437
|
+
.array(zod_1.z.enum(DEFAULT_ENV_FIELDS))
|
|
1438
|
+
.optional()
|
|
1439
|
+
.describe("返回字段白名单。仅支持 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault。action=list 时可选"),
|
|
1338
1440
|
},
|
|
1339
1441
|
annotations: {
|
|
1340
|
-
readOnlyHint:
|
|
1341
|
-
destructiveHint: false, // 注意:delete操作虽然是破坏性的,但这里采用较宽松的标注
|
|
1342
|
-
idempotentHint: false,
|
|
1442
|
+
readOnlyHint: true,
|
|
1343
1443
|
openWorldHint: true,
|
|
1344
1444
|
category: "env",
|
|
1345
1445
|
},
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1446
|
+
};
|
|
1447
|
+
server.registerTool?.("queryEnv", queryEnvToolSchema, queryEnvHandler);
|
|
1448
|
+
// 向后兼容:envQuery 作为 queryEnv 的别名注册
|
|
1449
|
+
server.registerTool?.("envQuery", queryEnvToolSchema, queryEnvHandler);
|
|
1450
|
+
// envDomainManagement - 环境域名管理(合并 createEnvDomain + deleteEnvDomain)
|
|
1451
|
+
// 微信 IDE 场景不需要域名管理
|
|
1452
|
+
if (server.ide !== 'wxide') {
|
|
1453
|
+
server.registerTool?.("envDomainManagement", {
|
|
1454
|
+
title: "CloudBase 环境域名管理(安全域名 / CORS 白名单)",
|
|
1455
|
+
description: "管理 CloudBase 环境的安全域名(安全域名 / CORS 白名单),支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 直接访问 CloudBase 资源时,应先用 queryEnv(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。新增或删除后通常需要继续轮询 queryEnv(action=domains) 确认状态收敛;安全域名一般约 10 分钟生效。⚠️ 重要:此工具仅用于 CORS/请求来源验证,不涉及 SSL 证书。如需绑定自定义域名供公网 HTTPS 访问,请使用 manageGateway(action=\"bindCustomDomain\")。",
|
|
1456
|
+
inputSchema: {
|
|
1457
|
+
action: zod_1.z
|
|
1458
|
+
.enum(["create", "delete"])
|
|
1459
|
+
.describe("操作类型:create=添加安全域名,delete=删除安全域名"),
|
|
1460
|
+
domains: zod_1.z.array(zod_1.z.string()).describe("安全域名数组(格式:host:port,例如 localhost:5173 或 127.0.0.1:4173)。注意:不是自定义域名,不需要证书。"),
|
|
1461
|
+
},
|
|
1462
|
+
annotations: {
|
|
1463
|
+
readOnlyHint: false,
|
|
1464
|
+
destructiveHint: false, // 注意:delete操作虽然是破坏性的,但这里采用较宽松的标注
|
|
1465
|
+
idempotentHint: false,
|
|
1466
|
+
openWorldHint: true,
|
|
1467
|
+
category: "env",
|
|
1468
|
+
},
|
|
1469
|
+
}, async ({ action, domains, }) => {
|
|
1470
|
+
try {
|
|
1471
|
+
const cloudbase = await getManager();
|
|
1472
|
+
let result;
|
|
1473
|
+
switch (action) {
|
|
1474
|
+
case "create":
|
|
1475
|
+
result = await cloudbase.env.createEnvDomain(domains);
|
|
1476
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
1477
|
+
break;
|
|
1478
|
+
case "delete":
|
|
1479
|
+
result = await cloudbase.env.deleteEnvDomain(domains);
|
|
1480
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
1481
|
+
break;
|
|
1482
|
+
default:
|
|
1483
|
+
throw new Error(`不支持的操作类型: ${action}`);
|
|
1484
|
+
}
|
|
1485
|
+
return (0, tool_result_js_1.buildJsonToolResult)(buildEnvDomainManagementResult({
|
|
1486
|
+
action,
|
|
1487
|
+
domains,
|
|
1488
|
+
result,
|
|
1489
|
+
}));
|
|
1361
1490
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1491
|
+
catch (error) {
|
|
1492
|
+
const toolPayloadResult = (0, tool_result_js_1.toolPayloadErrorToResult)(error);
|
|
1493
|
+
if (toolPayloadResult) {
|
|
1494
|
+
return toolPayloadResult;
|
|
1495
|
+
}
|
|
1496
|
+
return {
|
|
1497
|
+
content: [
|
|
1498
|
+
{
|
|
1499
|
+
type: "text",
|
|
1500
|
+
text: `域名管理操作失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
1501
|
+
},
|
|
1502
|
+
],
|
|
1503
|
+
};
|
|
1372
1504
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
{
|
|
1376
|
-
type: "text",
|
|
1377
|
-
text: `域名管理操作失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
1378
|
-
},
|
|
1379
|
-
],
|
|
1380
|
-
};
|
|
1381
|
-
}
|
|
1382
|
-
});
|
|
1505
|
+
});
|
|
1506
|
+
} // end: wxide guard for envDomainManagement
|
|
1383
1507
|
}
|
|
1384
1508
|
|
|
1385
1509
|
|
|
@@ -1703,7 +1827,7 @@ ${envIdSection}
|
|
|
1703
1827
|
## 环境信息
|
|
1704
1828
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
1705
1829
|
- Node.js版本: ${process.version}
|
|
1706
|
-
- MCP 版本:${process.env.npm_package_version || "2.
|
|
1830
|
+
- MCP 版本:${process.env.npm_package_version || "2.22.0-beta.0" || 0}
|
|
1707
1831
|
- 系统架构: ${os_1.default.arch()}
|
|
1708
1832
|
- 时间: ${new Date().toISOString()}
|
|
1709
1833
|
- 请求ID: ${requestId}
|
|
@@ -1784,7 +1908,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
1784
1908
|
requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
|
|
1785
1909
|
ide: server.ide || process.env.INTEGRATION_IDE || ''
|
|
1786
1910
|
});
|
|
1787
|
-
const mcpVersion = true ? "2.
|
|
1911
|
+
const mcpVersion = true ? "2.22.0-beta.0" : 0;
|
|
1788
1912
|
const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
|
|
1789
1913
|
const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
|
|
1790
1914
|
// 创建新的错误对象,保持原有的错误类型但更新消息
|
|
@@ -1936,6 +2060,7 @@ exports.createCloudBaseMcpServer = createCloudBaseMcpServer;
|
|
|
1936
2060
|
exports.getDefaultServer = getDefaultServer;
|
|
1937
2061
|
const mcp_js_1 = __webpack_require__(4619);
|
|
1938
2062
|
const databaseNoSQL_js_1 = __webpack_require__(3903);
|
|
2063
|
+
const databasePG_js_1 = __webpack_require__(6473);
|
|
1939
2064
|
const databaseSQL_js_1 = __webpack_require__(9782);
|
|
1940
2065
|
const download_js_1 = __webpack_require__(9097);
|
|
1941
2066
|
const env_js_1 = __webpack_require__(622);
|
|
@@ -1943,6 +2068,7 @@ const functions_js_1 = __webpack_require__(5936);
|
|
|
1943
2068
|
const hosting_js_1 = __webpack_require__(7279);
|
|
1944
2069
|
const rag_js_1 = __webpack_require__(4215);
|
|
1945
2070
|
const setup_js_1 = __webpack_require__(6556);
|
|
2071
|
+
const storagePG_js_1 = __webpack_require__(9189);
|
|
1946
2072
|
const storage_js_1 = __webpack_require__(4848);
|
|
1947
2073
|
// import { registerMiniprogramTools } from "./tools/miniprogram.js";
|
|
1948
2074
|
const types_js_1 = __webpack_require__(2344);
|
|
@@ -1965,6 +2091,8 @@ const tool_wrapper_js_1 = __webpack_require__(1363);
|
|
|
1965
2091
|
const DEFAULT_PLUGINS = [
|
|
1966
2092
|
"env",
|
|
1967
2093
|
"database",
|
|
2094
|
+
"pg_database",
|
|
2095
|
+
"pg_storage",
|
|
1968
2096
|
"functions",
|
|
1969
2097
|
"hosting",
|
|
1970
2098
|
"storage",
|
|
@@ -1987,13 +2115,31 @@ function registerDatabase(server) {
|
|
|
1987
2115
|
if (!(0, tencent_cloud_js_1.isInternationalRegion)(region)) {
|
|
1988
2116
|
(0, databaseNoSQL_js_1.registerDatabaseTools)(server);
|
|
1989
2117
|
}
|
|
1990
|
-
|
|
2118
|
+
const pgPluginEnabled = server.enabledPlugins?.some((pluginName) => pluginName === "pg_database" || pluginName === "pg_storage");
|
|
2119
|
+
if (!pgPluginEnabled) {
|
|
2120
|
+
(0, databaseSQL_js_1.registerSQLDatabaseTools)(server);
|
|
2121
|
+
}
|
|
1991
2122
|
(0, dataModel_js_1.registerDataModelTools)(server);
|
|
1992
2123
|
}
|
|
2124
|
+
function registerMysqlDatabase(server) {
|
|
2125
|
+
(0, databaseSQL_js_1.registerSQLDatabaseTools)(server);
|
|
2126
|
+
}
|
|
2127
|
+
function registerNoSQLDatabase(server) {
|
|
2128
|
+
const region = server.cloudBaseOptions?.region || process.env.TCB_REGION;
|
|
2129
|
+
if (!(0, tencent_cloud_js_1.isInternationalRegion)(region)) {
|
|
2130
|
+
(0, databaseNoSQL_js_1.registerDatabaseTools)(server);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
1993
2133
|
// 可用插件映射
|
|
1994
2134
|
const AVAILABLE_PLUGINS = {
|
|
1995
2135
|
env: { name: "env", register: env_js_1.registerEnvTools },
|
|
1996
2136
|
database: { name: "database", register: registerDatabase },
|
|
2137
|
+
mysql_database: { name: "mysql_database", register: registerMysqlDatabase },
|
|
2138
|
+
pg_database: { name: "pg_database", register: databasePG_js_1.registerPGDatabaseTools },
|
|
2139
|
+
pg_storage: { name: "pg_storage", register: storagePG_js_1.registerPGStorageTools },
|
|
2140
|
+
"database-nosql": { name: "database-nosql", register: registerNoSQLDatabase },
|
|
2141
|
+
"database-sql": { name: "database-sql", register: databaseSQL_js_1.registerSQLDatabaseTools },
|
|
2142
|
+
"data-model": { name: "data-model", register: dataModel_js_1.registerDataModelTools },
|
|
1997
2143
|
functions: { name: "functions", register: functions_js_1.registerFunctionTools },
|
|
1998
2144
|
hosting: { name: "hosting", register: hosting_js_1.registerHostingTools },
|
|
1999
2145
|
storage: { name: "storage", register: storage_js_1.registerStorageTools },
|
|
@@ -2017,6 +2163,9 @@ const PLUGIN_ALIASES = {
|
|
|
2017
2163
|
"security-rules": "permissions",
|
|
2018
2164
|
"secret-rule": "permissions",
|
|
2019
2165
|
"secret-rules": "permissions",
|
|
2166
|
+
mysql: "mysql_database",
|
|
2167
|
+
"mysql-database": "mysql_database",
|
|
2168
|
+
"sql-database": "mysql_database",
|
|
2020
2169
|
users: "permissions",
|
|
2021
2170
|
};
|
|
2022
2171
|
function normalizePluginName(name) {
|
|
@@ -2081,7 +2230,7 @@ function parseEnabledPlugins(pluginsEnabled, pluginsDisabled) {
|
|
|
2081
2230
|
* await server.connect(transport);
|
|
2082
2231
|
*/
|
|
2083
2232
|
async function createCloudBaseMcpServer(options) {
|
|
2084
|
-
const { name = "cloudbase-mcp", version = "1.0.0", enableTelemetry = true, cloudBaseOptions, authOptions, cloudMode = false, ide, logger, pluginsEnabled, pluginsDisabled, } = options ?? {};
|
|
2233
|
+
const { name = "cloudbase-mcp", version = "1.0.0", enableTelemetry = true, cloudBaseOptions, authOptions, cloudMode = false, ide, logger, pluginsEnabled, pluginsDisabled, pluginOptions, } = options ?? {};
|
|
2085
2234
|
// Enable cloud mode if specified
|
|
2086
2235
|
if (cloudMode) {
|
|
2087
2236
|
(0, cloud_mode_js_1.enableCloudMode)();
|
|
@@ -2096,20 +2245,31 @@ async function createCloudBaseMcpServer(options) {
|
|
|
2096
2245
|
...(ide === "CodeBuddy" ? { logging: {} } : {}),
|
|
2097
2246
|
},
|
|
2098
2247
|
});
|
|
2248
|
+
// 初始化 toolDefs,用于外部提取工具列表(如微信 IDE)
|
|
2249
|
+
server.toolDefs = [];
|
|
2099
2250
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
2100
|
-
server.registerTool = ((name, meta, handler) =>
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2251
|
+
server.registerTool = ((name, meta, handler) => {
|
|
2252
|
+
// 同步记录到 toolDefs
|
|
2253
|
+
server.toolDefs.push({
|
|
2254
|
+
name,
|
|
2255
|
+
description: meta?.description ?? meta?.title ?? '',
|
|
2256
|
+
inputSchema: meta?.inputSchema ?? {},
|
|
2257
|
+
handler,
|
|
2258
|
+
});
|
|
2259
|
+
return originalRegisterTool(name, meta, async (args) => {
|
|
2260
|
+
try {
|
|
2261
|
+
return await handler(args);
|
|
2109
2262
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2263
|
+
catch (error) {
|
|
2264
|
+
if ((0, tool_result_js_1.isToolPayloadError)(error)) {
|
|
2265
|
+
// 在结构化错误返回中注入 MCP 版本号
|
|
2266
|
+
const payload = { ...error.payload, mcpVersion: version };
|
|
2267
|
+
return (0, tool_result_js_1.buildJsonToolResult)(payload);
|
|
2268
|
+
}
|
|
2269
|
+
throw error;
|
|
2270
|
+
}
|
|
2271
|
+
});
|
|
2272
|
+
});
|
|
2113
2273
|
// Only set logging handler if logging capability is declared
|
|
2114
2274
|
if (ide === "CodeBuddy") {
|
|
2115
2275
|
server.server.setRequestHandler(types_js_1.SetLevelRequestSchema, (request, extra) => {
|
|
@@ -2124,6 +2284,10 @@ async function createCloudBaseMcpServer(options) {
|
|
|
2124
2284
|
if (authOptions) {
|
|
2125
2285
|
server.authOptions = authOptions;
|
|
2126
2286
|
}
|
|
2287
|
+
// Store pluginOptions in server instance for plugins to access
|
|
2288
|
+
if (pluginOptions) {
|
|
2289
|
+
server.pluginOptions = pluginOptions;
|
|
2290
|
+
}
|
|
2127
2291
|
// Store ide in server instance for telemetry
|
|
2128
2292
|
if (ide) {
|
|
2129
2293
|
server.ide = ide;
|
|
@@ -2141,6 +2305,7 @@ async function createCloudBaseMcpServer(options) {
|
|
|
2141
2305
|
}
|
|
2142
2306
|
// Register plugins based on configuration
|
|
2143
2307
|
const enabledPlugins = parseEnabledPlugins(pluginsEnabled, pluginsDisabled);
|
|
2308
|
+
server.enabledPlugins = enabledPlugins;
|
|
2144
2309
|
for (const pluginName of enabledPlugins) {
|
|
2145
2310
|
const plugin = AVAILABLE_PLUGINS[pluginName];
|
|
2146
2311
|
if (plugin) {
|
|
@@ -2231,8 +2396,8 @@ function registerLogTools(server) {
|
|
|
2231
2396
|
const cloudBaseOptions = server.cloudBaseOptions;
|
|
2232
2397
|
const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
|
|
2233
2398
|
server.registerTool?.("queryLogs", {
|
|
2234
|
-
title: "
|
|
2235
|
-
description: "日志域统一只读入口。支持检查日志服务状态并搜索 CLS 日志。" +
|
|
2399
|
+
title: "查询 CloudBase 日志服务",
|
|
2400
|
+
description: "CloudBase 日志域统一只读入口。支持检查日志服务状态并搜索 CLS 日志。" +
|
|
2236
2401
|
"\n\n**重要区分**:" +
|
|
2237
2402
|
"\n- 查询云函数日志:使用 `queryFunctions(action=\"listFunctionLogs\", functionName=\"xxx\")`" +
|
|
2238
2403
|
"\n- 查询 CLS 日志(跨服务日志聚合):使用本工具 `queryLogs(action=\"searchLogs\")`" +
|
|
@@ -3284,7 +3449,7 @@ if (shouldUseConsole()) {
|
|
|
3284
3449
|
stderrLevels: ['error', 'warn', 'info', 'debug'], // All logs go to stderr
|
|
3285
3450
|
}));
|
|
3286
3451
|
}
|
|
3287
|
-
if (!(0, cloud_mode_js_1.isCloudMode)()) {
|
|
3452
|
+
if (!(0, cloud_mode_js_1.isCloudMode)() && process.env.CLOUDBASE_MCP_DISABLE_LOG_FILE !== 'true') {
|
|
3288
3453
|
// File transport with daily rotation
|
|
3289
3454
|
transports.push(new DailyRotateFile({
|
|
3290
3455
|
dirname: logDir,
|
|
@@ -3703,8 +3868,9 @@ async function getDatabaseInstanceId(options) {
|
|
|
3703
3868
|
async function getCloudBaseManager(options = {}) {
|
|
3704
3869
|
const { requireEnvId = true, cloudBaseOptions, authStrategy = 'fail_fast', } = options;
|
|
3705
3870
|
const hasDirectCredentials = !!(cloudBaseOptions?.secretId && cloudBaseOptions?.secretKey);
|
|
3706
|
-
|
|
3707
|
-
|
|
3871
|
+
const hasRequestFn = !!(cloudBaseOptions?.requestFn);
|
|
3872
|
+
// 如果传入了完整凭据或 requestFn,优先使用显式 CloudBase 配置
|
|
3873
|
+
if (cloudBaseOptions && (hasDirectCredentials || hasRequestFn)) {
|
|
3708
3874
|
let resolvedEnvId = cloudBaseOptions.envId;
|
|
3709
3875
|
if (requireEnvId && !resolvedEnvId) {
|
|
3710
3876
|
const envCandidates = await listAvailableEnvCandidates({ cloudBaseOptions });
|
|
@@ -4018,6 +4184,13 @@ async function callNoSqlContentApi(options) {
|
|
|
4018
4184
|
instanceIdSource: "envId",
|
|
4019
4185
|
requestId: result?.RequestId,
|
|
4020
4186
|
});
|
|
4187
|
+
// 检查 API 层返回的业务错误(如集合不存在)并抛出
|
|
4188
|
+
if (result?.Error) {
|
|
4189
|
+
const err = new Error(`${result.Error.Code}: ${result.Error.Message}`);
|
|
4190
|
+
err.code = result.Error.Code;
|
|
4191
|
+
err.requestId = result.RequestId;
|
|
4192
|
+
throw err;
|
|
4193
|
+
}
|
|
4021
4194
|
return result;
|
|
4022
4195
|
}
|
|
4023
4196
|
catch (error) {
|
|
@@ -4031,7 +4204,7 @@ async function callNoSqlContentApi(options) {
|
|
|
4031
4204
|
throw error;
|
|
4032
4205
|
}
|
|
4033
4206
|
}
|
|
4034
|
-
async function waitForCollectionReady({ cloudbase, collectionName, logger, timeoutMs = COLLECTION_READY_TIMEOUT_MS, pollIntervalMs = COLLECTION_READY_POLL_INTERVAL_MS, }) {
|
|
4207
|
+
async function waitForCollectionReady({ cloudbase, collectionName, logger, cloudBaseOptions, timeoutMs = COLLECTION_READY_TIMEOUT_MS, pollIntervalMs = COLLECTION_READY_POLL_INTERVAL_MS, }) {
|
|
4035
4208
|
const startedAt = Date.now();
|
|
4036
4209
|
const deadline = startedAt + timeoutMs;
|
|
4037
4210
|
let lastError;
|
|
@@ -4045,17 +4218,40 @@ async function waitForCollectionReady({ cloudbase, collectionName, logger, timeo
|
|
|
4045
4218
|
});
|
|
4046
4219
|
while (Date.now() <= deadline) {
|
|
4047
4220
|
try {
|
|
4048
|
-
//
|
|
4049
|
-
|
|
4050
|
-
const
|
|
4051
|
-
|
|
4221
|
+
// 用 PutItem 探测集合是否真正就绪(DescribeTable 可能误报),插入一个空占位文档
|
|
4222
|
+
// 如果成功,立即删除该文档;如果仍 ResourceNotFound,继续等待
|
|
4223
|
+
const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
4224
|
+
const probeDoc = JSON.stringify({ _readyProbe: true, _ts: Date.now() });
|
|
4225
|
+
const probeResult = await cloudbase.commonService("tcb", "2018-06-08").call({
|
|
4226
|
+
Action: "PutItem",
|
|
4052
4227
|
Param: {
|
|
4053
4228
|
EnvId: envId,
|
|
4054
4229
|
TableName: collectionName,
|
|
4230
|
+
MgoDocs: [probeDoc],
|
|
4055
4231
|
},
|
|
4056
4232
|
});
|
|
4057
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(logger,
|
|
4058
|
-
|
|
4233
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(logger, probeResult);
|
|
4234
|
+
if (probeResult?.Error) {
|
|
4235
|
+
// 集合还未就绪,继续轮询
|
|
4236
|
+
throw new Error(probeResult.Error.Message);
|
|
4237
|
+
}
|
|
4238
|
+
// 集合就绪,清理探测文档
|
|
4239
|
+
if (Array.isArray(probeResult?.InsertedIds) && probeResult.InsertedIds.length > 0) {
|
|
4240
|
+
try {
|
|
4241
|
+
await cloudbase.commonService("tcb", "2018-06-08").call({
|
|
4242
|
+
Action: "DeleteItem",
|
|
4243
|
+
Param: {
|
|
4244
|
+
EnvId: envId,
|
|
4245
|
+
TableName: collectionName,
|
|
4246
|
+
MgoQuery: JSON.stringify({ _readyProbe: true }),
|
|
4247
|
+
MgoIsMulti: true,
|
|
4248
|
+
},
|
|
4249
|
+
});
|
|
4250
|
+
}
|
|
4251
|
+
catch {
|
|
4252
|
+
// 清理失败不影响主流程
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4059
4255
|
logger?.({
|
|
4060
4256
|
type: "toolInfo",
|
|
4061
4257
|
toolName: "writeNoSqlDatabaseStructure",
|
|
@@ -4094,8 +4290,8 @@ function registerDatabaseTools(server) {
|
|
|
4094
4290
|
const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
|
|
4095
4291
|
// readNoSqlDatabaseStructure
|
|
4096
4292
|
server.registerTool?.("readNoSqlDatabaseStructure", {
|
|
4097
|
-
title: "读取 NoSQL 数据库结构",
|
|
4098
|
-
description: "读取 NoSQL 数据库集合与索引结构,支持列出集合、查看集合详情、列出索引以及检查索引是否存在。本工具为服务端管理工具,用于管理端查询数据库结构,不用于编写客户端代码。",
|
|
4293
|
+
title: "读取 CloudBase NoSQL 数据库结构",
|
|
4294
|
+
description: "读取 CloudBase NoSQL 数据库集合与索引结构,支持列出集合、查看集合详情、列出索引以及检查索引是否存在。本工具为服务端管理工具,用于管理端查询数据库结构,不用于编写客户端代码。",
|
|
4099
4295
|
inputSchema: {
|
|
4100
4296
|
action: zod_1.z.enum([
|
|
4101
4297
|
"listCollections",
|
|
@@ -4282,8 +4478,8 @@ checkIndex: 检查指定索引是否存在`),
|
|
|
4282
4478
|
});
|
|
4283
4479
|
// writeNoSqlDatabaseStructure
|
|
4284
4480
|
server.registerTool?.("writeNoSqlDatabaseStructure", {
|
|
4285
|
-
title: "创建并管理 NoSQL 数据库集合",
|
|
4286
|
-
description: "创建、删除和管理 NoSQL 数据库集合(collection)。支持创建新集合、删除现有集合,以及通过 updateCollection 的 updateOptions.CreateIndexes / updateOptions.DropIndexes 添加索引和删除索引。当需要新建集合时,使用 action=createCollection。本工具为服务端管理工具,用于管理端操作集合和索引结构,不用于编写客户端代码。",
|
|
4481
|
+
title: "创建并管理 CloudBase NoSQL 数据库集合",
|
|
4482
|
+
description: "创建、删除和管理 CloudBase NoSQL 数据库集合(collection)。支持创建新集合、删除现有集合,以及通过 updateCollection 的 updateOptions.CreateIndexes / updateOptions.DropIndexes 添加索引和删除索引。当需要新建集合时,使用 action=createCollection。本工具为服务端管理工具,用于管理端操作集合和索引结构,不用于编写客户端代码。",
|
|
4287
4483
|
inputSchema: {
|
|
4288
4484
|
action: zod_1.z.enum([
|
|
4289
4485
|
"createCollection",
|
|
@@ -4343,8 +4539,14 @@ deleteCollection: 删除集合`),
|
|
|
4343
4539
|
Action: "DescribeTable",
|
|
4344
4540
|
Param: { EnvId: envId, TableName: collectionName },
|
|
4345
4541
|
});
|
|
4346
|
-
|
|
4347
|
-
|
|
4542
|
+
if (descResult?.Error) {
|
|
4543
|
+
// 集合不存在(ResourceNotFound)或其他错误,继续创建
|
|
4544
|
+
existsRequestId = descResult.RequestId ?? "";
|
|
4545
|
+
}
|
|
4546
|
+
else {
|
|
4547
|
+
collectionExists = true;
|
|
4548
|
+
existsRequestId = descResult?.RequestId ?? "";
|
|
4549
|
+
}
|
|
4348
4550
|
}
|
|
4349
4551
|
catch (e) {
|
|
4350
4552
|
existsRequestId = e?.requestId ?? "";
|
|
@@ -4377,6 +4579,7 @@ deleteCollection: 删除集合`),
|
|
|
4377
4579
|
cloudbase,
|
|
4378
4580
|
collectionName,
|
|
4379
4581
|
logger: server.logger,
|
|
4582
|
+
cloudBaseOptions: server.cloudBaseOptions,
|
|
4380
4583
|
});
|
|
4381
4584
|
return {
|
|
4382
4585
|
content: [
|
|
@@ -4459,8 +4662,8 @@ deleteCollection: 删除集合`),
|
|
|
4459
4662
|
});
|
|
4460
4663
|
// readNoSqlDatabaseContent
|
|
4461
4664
|
server.registerTool?.("readNoSqlDatabaseContent", {
|
|
4462
|
-
title: "查询并获取 NoSQL 数据库数据记录",
|
|
4463
|
-
description: "
|
|
4665
|
+
title: "查询并获取 CloudBase NoSQL 数据库数据记录",
|
|
4666
|
+
description: "查询 CloudBase NoSQL 数据库中的数据记录。支持按条件筛选、分页、排序,适用于管理端数据查询与运维。",
|
|
4464
4667
|
inputSchema: {
|
|
4465
4668
|
collectionName: zod_1.z.string().describe("集合名称"),
|
|
4466
4669
|
instanceId: zod_1.z
|
|
@@ -4546,8 +4749,10 @@ deleteCollection: 删除集合`),
|
|
|
4546
4749
|
});
|
|
4547
4750
|
// writeNoSqlDatabaseContent
|
|
4548
4751
|
server.registerTool?.("writeNoSqlDatabaseContent", {
|
|
4549
|
-
title: "修改 NoSQL 数据库数据记录",
|
|
4550
|
-
description: "修改 NoSQL
|
|
4752
|
+
title: "修改 CloudBase NoSQL 数据库数据记录",
|
|
4753
|
+
description: "修改 CloudBase NoSQL 数据库中的数据记录。支持插入、更新(含 $set/$inc/$push 等操作符)、删除、upsert 等操作,适用于管理端数据写入与运维。" +
|
|
4754
|
+
"⚠️ 服务端写入不含 _openid:若集合依赖客户端 SDK(@cloudbase/js-sdk 或微信小程序 wx.cloud.database())的行级安全规则(如 doc._openid == auth.openid),服务端写入时需手动补充 _openid 字段,否则客户端将无法读取到该数据。" +
|
|
4755
|
+
"⚠️ 部分更新嵌套字段须使用点号路径,如 `$set: {\"shipping.city\": \"guangzhou\"}`,直接传嵌套对象会覆盖整个字段。",
|
|
4551
4756
|
inputSchema: {
|
|
4552
4757
|
action: zod_1.z
|
|
4553
4758
|
.enum(["insert", "update", "delete"])
|
|
@@ -6287,7 +6492,7 @@ async function registerRagTools(server) {
|
|
|
6287
6492
|
.map((skill) => `文档名:${path.basename(path.dirname(skill.absolutePath))} 文档介绍:${skill.description}`)
|
|
6288
6493
|
.join("\n")}
|
|
6289
6494
|
|
|
6290
|
-
OpenAPI 文档 (openapi)
|
|
6495
|
+
OpenAPI 文档 (openapi) 查询只需要传 mode="openapi" 和 apiName,不要传 action;action 仅用于 mode="docs"。当前支持 ${openapis.length} 个 API 文档,分别是:
|
|
6291
6496
|
${openapis
|
|
6292
6497
|
.map((api) => `API名:${api.name} API介绍:${api.description}`)
|
|
6293
6498
|
.join("\n")}`,
|
|
@@ -6295,7 +6500,7 @@ async function registerRagTools(server) {
|
|
|
6295
6500
|
mode: SearchKnowledgeModeEnum,
|
|
6296
6501
|
skillName: buildOptionalStringEnum(skillNames, "mode=skill 时指定。技能名称。"),
|
|
6297
6502
|
apiName: buildOptionalStringEnum(openapiNames, "mode=openapi 时指定。API 名称。"),
|
|
6298
|
-
action: CloudBaseDocsActionEnum.optional().describe("mode=docs
|
|
6503
|
+
action: CloudBaseDocsActionEnum.optional().describe("仅 mode=docs 时指定;mode=openapi 不要传 action。CloudBase 文档操作类型:listModules=列出所有文档模块,listModuleDocs=获取指定模块的目录结构,findByName=按名称/路径/URL 智能查找,readDoc=读取指定文档 Markdown,searchDocs=全文搜索官方文档。"),
|
|
6299
6504
|
moduleName: zod_1.z
|
|
6300
6505
|
.string()
|
|
6301
6506
|
.optional()
|
|
@@ -7251,12 +7456,13 @@ async function resolveStoragePublicAccess(params) {
|
|
|
7251
7456
|
function registerStorageTools(server) {
|
|
7252
7457
|
// 获取 cloudBaseOptions,如果没有则为 undefined
|
|
7253
7458
|
const cloudBaseOptions = server.cloudBaseOptions;
|
|
7459
|
+
const storageOverrides = server.pluginOptions?.storage;
|
|
7254
7460
|
// 创建闭包函数来获取 CloudBase Manager
|
|
7255
7461
|
const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
|
|
7256
7462
|
// Tool 1: queryStorage - 查询存储信息(只读操作)
|
|
7257
7463
|
server.registerTool("queryStorage", {
|
|
7258
7464
|
title: "查询 CloudBase 存储信息",
|
|
7259
|
-
description: "查询 CloudBase 云存储信息,支持列出目录文件、获取文件信息、获取临时下载链接等只读操作。返回的文件信息包括文件名、大小、修改时间、下载链接等。注意:action=url 返回的 temporaryUrl 是临时签名链接,有效期由 maxAge 参数决定(默认1小时),不要当作永久公网地址使用。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain
|
|
7465
|
+
description: "查询 CloudBase 云存储信息,支持列出目录文件、获取文件信息、获取临时下载链接等只读操作。返回的文件信息包括文件名、大小、修改时间、下载链接等。注意:action=url 返回的 temporaryUrl 是临时签名链接,有效期由 maxAge 参数决定(默认1小时),不要当作永久公网地址使用。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。\n\n💡 存储桶 ACL 权限管理请使用 permissions 工具:queryPermissions(action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\") 查询,managePermissions(action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"READONLY\") 设置。\n\n📦 CloudBase PG / pgstore 环境:`DescribeEnvs.Storages[]` 列出的 bucket 是旧 NoSQL 后端的,不等于 pgstore bucket。本工具用于查看常规存储;为 PG 浏览器上传准备 bucket 时,请确认目标 bucket 是 pgstore 后端可用的,否则浏览器 `app.storage.from().upload(...)` 会得到 `STORAGE_BUCKET_NOT_FOUND` 并出现 `PUT https://undefined/`。",
|
|
7260
7466
|
inputSchema: queryStorageInputSchema,
|
|
7261
7467
|
annotations: {
|
|
7262
7468
|
readOnlyHint: true,
|
|
@@ -7272,7 +7478,14 @@ function registerStorageTools(server) {
|
|
|
7272
7478
|
const storageService = manager.storage;
|
|
7273
7479
|
switch (input.action) {
|
|
7274
7480
|
case 'list': {
|
|
7275
|
-
|
|
7481
|
+
let files;
|
|
7482
|
+
if (storageOverrides?.listFiles) {
|
|
7483
|
+
files = await storageOverrides.listFiles({ cloudPath: input.cloudPath });
|
|
7484
|
+
}
|
|
7485
|
+
else {
|
|
7486
|
+
const result = await storageService.listDirectoryFiles(input.cloudPath);
|
|
7487
|
+
files = result || [];
|
|
7488
|
+
}
|
|
7276
7489
|
return {
|
|
7277
7490
|
content: [
|
|
7278
7491
|
{
|
|
@@ -7282,17 +7495,23 @@ function registerStorageTools(server) {
|
|
|
7282
7495
|
data: {
|
|
7283
7496
|
action: 'list',
|
|
7284
7497
|
cloudPath: input.cloudPath,
|
|
7285
|
-
files
|
|
7286
|
-
totalCount:
|
|
7498
|
+
files,
|
|
7499
|
+
totalCount: files.length
|
|
7287
7500
|
},
|
|
7288
|
-
message: `Successfully listed ${
|
|
7501
|
+
message: `Successfully listed ${files.length} files in directory '${input.cloudPath}'`
|
|
7289
7502
|
}, null, 2)
|
|
7290
7503
|
}
|
|
7291
7504
|
]
|
|
7292
7505
|
};
|
|
7293
7506
|
}
|
|
7294
7507
|
case 'info': {
|
|
7295
|
-
|
|
7508
|
+
let fileInfo;
|
|
7509
|
+
if (storageOverrides?.getFileInfo) {
|
|
7510
|
+
fileInfo = await storageOverrides.getFileInfo({ cloudPath: input.cloudPath });
|
|
7511
|
+
}
|
|
7512
|
+
else {
|
|
7513
|
+
fileInfo = await storageService.getFileInfo(input.cloudPath);
|
|
7514
|
+
}
|
|
7296
7515
|
return {
|
|
7297
7516
|
content: [
|
|
7298
7517
|
{
|
|
@@ -7302,7 +7521,7 @@ function registerStorageTools(server) {
|
|
|
7302
7521
|
data: {
|
|
7303
7522
|
action: 'info',
|
|
7304
7523
|
cloudPath: input.cloudPath,
|
|
7305
|
-
fileInfo
|
|
7524
|
+
fileInfo
|
|
7306
7525
|
},
|
|
7307
7526
|
message: `Successfully retrieved file info for '${input.cloudPath}'`
|
|
7308
7527
|
}, null, 2)
|
|
@@ -7311,10 +7530,24 @@ function registerStorageTools(server) {
|
|
|
7311
7530
|
};
|
|
7312
7531
|
}
|
|
7313
7532
|
case 'url': {
|
|
7314
|
-
|
|
7533
|
+
let temporaryUrl = "";
|
|
7534
|
+
let fileId = "";
|
|
7535
|
+
if (storageOverrides?.getFileUrl) {
|
|
7536
|
+
const urlResult = await storageOverrides.getFileUrl({
|
|
7315
7537
|
cloudPath: input.cloudPath,
|
|
7316
|
-
maxAge: input.maxAge || 3600
|
|
7317
|
-
}
|
|
7538
|
+
maxAge: input.maxAge || 3600,
|
|
7539
|
+
});
|
|
7540
|
+
temporaryUrl = urlResult.url;
|
|
7541
|
+
fileId = urlResult.fileId || "";
|
|
7542
|
+
}
|
|
7543
|
+
else {
|
|
7544
|
+
const result = await storageService.getTemporaryUrl([{
|
|
7545
|
+
cloudPath: input.cloudPath,
|
|
7546
|
+
maxAge: input.maxAge || 3600
|
|
7547
|
+
}]);
|
|
7548
|
+
temporaryUrl = result[0]?.url || "";
|
|
7549
|
+
fileId = result[0]?.fileId || "";
|
|
7550
|
+
}
|
|
7318
7551
|
const publicAccess = await resolveStoragePublicAccess({
|
|
7319
7552
|
cloudPath: input.cloudPath,
|
|
7320
7553
|
cloudBaseOptions,
|
|
@@ -7329,9 +7562,9 @@ function registerStorageTools(server) {
|
|
|
7329
7562
|
data: {
|
|
7330
7563
|
action: 'url',
|
|
7331
7564
|
cloudPath: input.cloudPath,
|
|
7332
|
-
temporaryUrl
|
|
7565
|
+
temporaryUrl,
|
|
7333
7566
|
expireTime: `${input.maxAge || 3600}秒`,
|
|
7334
|
-
fileId
|
|
7567
|
+
fileId,
|
|
7335
7568
|
storageCdnDomain: publicAccess.storageCdnDomain,
|
|
7336
7569
|
publicUrl: publicAccess.publicUrl,
|
|
7337
7570
|
note: "temporaryUrl 是临时签名链接,会按 expireTime 过期。publicUrl 基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导,⚠️ 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先将目标路径设置为公有读。"
|
|
@@ -7346,10 +7579,15 @@ function registerStorageTools(server) {
|
|
|
7346
7579
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), STORAGE_READ_TEMP_PREFIX));
|
|
7347
7580
|
const localPath = path.join(tempDir, getStorageTempFileName(input.cloudPath));
|
|
7348
7581
|
try {
|
|
7349
|
-
|
|
7350
|
-
cloudPath: input.cloudPath,
|
|
7351
|
-
|
|
7352
|
-
|
|
7582
|
+
if (storageOverrides?.downloadFile) {
|
|
7583
|
+
await storageOverrides.downloadFile({ cloudPath: input.cloudPath, localPath });
|
|
7584
|
+
}
|
|
7585
|
+
else {
|
|
7586
|
+
await storageService.downloadFile({
|
|
7587
|
+
cloudPath: input.cloudPath,
|
|
7588
|
+
localPath
|
|
7589
|
+
});
|
|
7590
|
+
}
|
|
7353
7591
|
const buffer = await fs.readFile(localPath);
|
|
7354
7592
|
const decoded = decodeInlineTextContent(buffer);
|
|
7355
7593
|
return {
|
|
@@ -7385,7 +7623,7 @@ function registerStorageTools(server) {
|
|
|
7385
7623
|
// Tool 2: manageStorage - 管理存储文件(写操作)
|
|
7386
7624
|
server.registerTool("manageStorage", {
|
|
7387
7625
|
title: "管理 CloudBase 存储文件",
|
|
7388
|
-
description: "管理 CloudBase 云存储文件,仅用于 COS/Storage 对象,不用于静态网站托管。支持上传文件/目录、下载文件/目录、删除文件/目录等操作。删除操作需要设置force=true进行确认,防止误删除重要文件。注意:上传后返回的 temporaryUrl 是临时签名链接,1小时后过期,不要当作永久公网地址写入配置或持久化存储。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。\n\n💡 存储桶 ACL 权限管理请使用 permissions 工具:queryPermissions(action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\") 查询,managePermissions(action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"READONLY\")
|
|
7626
|
+
description: "管理 CloudBase 云存储文件,仅用于 COS/Storage 对象,不用于静态网站托管。支持上传文件/目录、下载文件/目录、删除文件/目录等操作。删除操作需要设置force=true进行确认,防止误删除重要文件。注意:上传后返回的 temporaryUrl 是临时签名链接,1小时后过期,不要当作永久公网地址写入配置或持久化存储。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。\n\n💡 存储桶 ACL 权限管理请使用 permissions 工具:queryPermissions(action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\") 查询,managePermissions(action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"READONLY\") 设置。\n\n📦 CloudBase PG / pgstore 桶必须先创建后使用(与 Supabase Storage 一致:upload 前 bucket 必须存在)。浏览器 SDK `app.storage.from().upload(path, file)` 不会自动建桶,且 `path` 的第一段就是 bucket 名(例如 `covers/foo.png` → bucket=`covers`);`from('covers')` 这个参数当前不会被拼到 path 里。如果上传时浏览器看到 `STORAGE_BUCKET_NOT_FOUND` 或 `PUT https://undefined/`(DevTools 表现为 `net::ERR_NAME_NOT_RESOLVED`),先用本工具或控制台确认 / 创建对应的 pgstore bucket,再让前端重试上传,不要让前端把上传失败静默吞掉。`DescribeEnvs.Storages[]` 返回的旧 NoSQL bucket(形如 `<hash>-<envId>-<appId>`)不是可用的 pgstore bucket,切勿当作默认目标使用。",
|
|
7389
7627
|
inputSchema: manageStorageInputSchema,
|
|
7390
7628
|
annotations: {
|
|
7391
7629
|
readOnlyHint: false,
|
|
@@ -7404,32 +7642,69 @@ function registerStorageTools(server) {
|
|
|
7404
7642
|
switch (input.action) {
|
|
7405
7643
|
case 'upload': {
|
|
7406
7644
|
if (input.isDirectory) {
|
|
7407
|
-
|
|
7408
|
-
localPath: input.localPath,
|
|
7645
|
+
if (storageOverrides?.uploadDirectory) {
|
|
7646
|
+
await storageOverrides.uploadDirectory({ localPath: input.localPath, cloudPath: input.cloudPath });
|
|
7647
|
+
}
|
|
7648
|
+
else {
|
|
7649
|
+
await storageService.uploadDirectory({
|
|
7650
|
+
localPath: input.localPath,
|
|
7651
|
+
cloudPath: input.cloudPath,
|
|
7652
|
+
onProgress: (progressData) => {
|
|
7653
|
+
console.log("Upload directory progress:", progressData);
|
|
7654
|
+
}
|
|
7655
|
+
});
|
|
7656
|
+
}
|
|
7657
|
+
}
|
|
7658
|
+
else {
|
|
7659
|
+
if (storageOverrides?.uploadFile) {
|
|
7660
|
+
await storageOverrides.uploadFile({ localPath: input.localPath, cloudPath: input.cloudPath });
|
|
7661
|
+
}
|
|
7662
|
+
else {
|
|
7663
|
+
await storageService.uploadFile({
|
|
7664
|
+
localPath: input.localPath,
|
|
7665
|
+
cloudPath: input.cloudPath,
|
|
7666
|
+
onProgress: (progressData) => {
|
|
7667
|
+
console.log("Upload file progress:", progressData);
|
|
7668
|
+
}
|
|
7669
|
+
});
|
|
7670
|
+
}
|
|
7671
|
+
}
|
|
7672
|
+
// 上传成功后获取临时 URL 和公网访问信息
|
|
7673
|
+
let temporaryUrl = "";
|
|
7674
|
+
let fileId = "";
|
|
7675
|
+
if (storageOverrides?.getFileUrl) {
|
|
7676
|
+
const urlResult = await storageOverrides.getFileUrl({
|
|
7409
7677
|
cloudPath: input.cloudPath,
|
|
7410
|
-
|
|
7411
|
-
console.log("Upload directory progress:", progressData);
|
|
7412
|
-
}
|
|
7678
|
+
maxAge: 3600,
|
|
7413
7679
|
});
|
|
7680
|
+
temporaryUrl = urlResult.url;
|
|
7681
|
+
fileId = urlResult.fileId || "";
|
|
7414
7682
|
}
|
|
7415
7683
|
else {
|
|
7416
|
-
|
|
7417
|
-
|
|
7684
|
+
try {
|
|
7685
|
+
const fileUrls = await storageService.getTemporaryUrl([{
|
|
7686
|
+
cloudPath: input.cloudPath,
|
|
7687
|
+
maxAge: 3600
|
|
7688
|
+
}]);
|
|
7689
|
+
temporaryUrl = fileUrls[0]?.url || "";
|
|
7690
|
+
fileId = fileUrls[0]?.fileId || "";
|
|
7691
|
+
}
|
|
7692
|
+
catch {
|
|
7693
|
+
// StorageOverrides 下 getTemporaryUrl 可能不可用
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
let publicAccess = {};
|
|
7697
|
+
try {
|
|
7698
|
+
publicAccess = await resolveStoragePublicAccess({
|
|
7418
7699
|
cloudPath: input.cloudPath,
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
}
|
|
7700
|
+
cloudBaseOptions,
|
|
7701
|
+
manager,
|
|
7422
7702
|
});
|
|
7423
7703
|
}
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
const publicAccess = await resolveStoragePublicAccess({
|
|
7429
|
-
cloudPath: input.cloudPath,
|
|
7430
|
-
cloudBaseOptions,
|
|
7431
|
-
manager,
|
|
7432
|
-
});
|
|
7704
|
+
catch {
|
|
7705
|
+
// DescribeEnvs 可能不支持(IDE 模式下会绕过)
|
|
7706
|
+
publicAccess = {};
|
|
7707
|
+
}
|
|
7433
7708
|
return {
|
|
7434
7709
|
content: [
|
|
7435
7710
|
{
|
|
@@ -7441,7 +7716,7 @@ function registerStorageTools(server) {
|
|
|
7441
7716
|
localPath: input.localPath,
|
|
7442
7717
|
cloudPath: input.cloudPath,
|
|
7443
7718
|
isDirectory: input.isDirectory,
|
|
7444
|
-
temporaryUrl
|
|
7719
|
+
temporaryUrl,
|
|
7445
7720
|
expireTime: "1小时",
|
|
7446
7721
|
storageCdnDomain: publicAccess.storageCdnDomain,
|
|
7447
7722
|
publicUrl: publicAccess.publicUrl,
|
|
@@ -7455,16 +7730,26 @@ function registerStorageTools(server) {
|
|
|
7455
7730
|
}
|
|
7456
7731
|
case 'download': {
|
|
7457
7732
|
if (input.isDirectory) {
|
|
7458
|
-
|
|
7459
|
-
cloudPath: input.cloudPath,
|
|
7460
|
-
|
|
7461
|
-
|
|
7733
|
+
if (storageOverrides?.downloadDirectory) {
|
|
7734
|
+
await storageOverrides.downloadDirectory({ cloudPath: input.cloudPath, localPath: input.localPath });
|
|
7735
|
+
}
|
|
7736
|
+
else {
|
|
7737
|
+
await storageService.downloadDirectory({
|
|
7738
|
+
cloudPath: input.cloudPath,
|
|
7739
|
+
localPath: input.localPath
|
|
7740
|
+
});
|
|
7741
|
+
}
|
|
7462
7742
|
}
|
|
7463
7743
|
else {
|
|
7464
|
-
|
|
7465
|
-
cloudPath: input.cloudPath,
|
|
7466
|
-
|
|
7467
|
-
|
|
7744
|
+
if (storageOverrides?.downloadFile) {
|
|
7745
|
+
await storageOverrides.downloadFile({ cloudPath: input.cloudPath, localPath: input.localPath });
|
|
7746
|
+
}
|
|
7747
|
+
else {
|
|
7748
|
+
await storageService.downloadFile({
|
|
7749
|
+
cloudPath: input.cloudPath,
|
|
7750
|
+
localPath: input.localPath
|
|
7751
|
+
});
|
|
7752
|
+
}
|
|
7468
7753
|
}
|
|
7469
7754
|
return {
|
|
7470
7755
|
content: [
|
|
@@ -7500,10 +7785,20 @@ function registerStorageTools(server) {
|
|
|
7500
7785
|
};
|
|
7501
7786
|
}
|
|
7502
7787
|
if (input.isDirectory) {
|
|
7503
|
-
|
|
7788
|
+
if (storageOverrides?.deleteDirectory) {
|
|
7789
|
+
await storageOverrides.deleteDirectory({ cloudPath: input.cloudPath });
|
|
7790
|
+
}
|
|
7791
|
+
else {
|
|
7792
|
+
await storageService.deleteDirectory(input.cloudPath);
|
|
7793
|
+
}
|
|
7504
7794
|
}
|
|
7505
7795
|
else {
|
|
7506
|
-
|
|
7796
|
+
if (storageOverrides?.deleteFiles) {
|
|
7797
|
+
await storageOverrides.deleteFiles({ cloudPaths: [input.cloudPath] });
|
|
7798
|
+
}
|
|
7799
|
+
else {
|
|
7800
|
+
await storageService.deleteFile([input.cloudPath]);
|
|
7801
|
+
}
|
|
7507
7802
|
}
|
|
7508
7803
|
return {
|
|
7509
7804
|
content: [
|
|
@@ -8658,7 +8953,7 @@ function buildWebSdkHint(loginMethods) {
|
|
|
8658
8953
|
}
|
|
8659
8954
|
return {
|
|
8660
8955
|
blocked: false,
|
|
8661
|
-
register: "
|
|
8956
|
+
register: "direct username/password signUp is SDK/provider dependent; verify before use, otherwise create users through a backend or management API boundary",
|
|
8662
8957
|
login: "auth.signInWithPassword({ username, password })",
|
|
8663
8958
|
accountInputType: "text",
|
|
8664
8959
|
avoidEmailHelpers: true,
|
|
@@ -9113,6 +9408,13 @@ function registerAppAuthTools(server) {
|
|
|
9113
9408
|
}
|
|
9114
9409
|
|
|
9115
9410
|
|
|
9411
|
+
/***/ }),
|
|
9412
|
+
|
|
9413
|
+
/***/ 5663:
|
|
9414
|
+
/***/ ((module) => {
|
|
9415
|
+
|
|
9416
|
+
module.exports = __WEBPACK_EXTERNAL_MODULE_node_path_02319fef__;
|
|
9417
|
+
|
|
9116
9418
|
/***/ }),
|
|
9117
9419
|
|
|
9118
9420
|
/***/ 5814:
|
|
@@ -9182,7 +9484,7 @@ class TelemetryReporter {
|
|
|
9182
9484
|
const nodeVersion = process.version; // Node.js版本
|
|
9183
9485
|
const arch = os_1.default.arch(); // 系统架构
|
|
9184
9486
|
// 从构建时注入的版本号获取MCP版本信息
|
|
9185
|
-
const mcpVersion = process.env.npm_package_version || "2.
|
|
9487
|
+
const mcpVersion = process.env.npm_package_version || "2.22.0-beta.0" || 0;
|
|
9186
9488
|
return {
|
|
9187
9489
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
9188
9490
|
deviceId: this.deviceId,
|
|
@@ -9573,6 +9875,7 @@ exports.MANAGE_FUNCTION_ACTIONS = [
|
|
|
9573
9875
|
"attachLayer",
|
|
9574
9876
|
"detachLayer",
|
|
9575
9877
|
"updateFunctionLayers",
|
|
9878
|
+
"incrementalDeployFunction", // 增量部署,需通过 pluginOptions.functions 注入实现
|
|
9576
9879
|
];
|
|
9577
9880
|
const VPC_SCHEMA = zod_1.z.object({
|
|
9578
9881
|
vpcId: zod_1.z.string(),
|
|
@@ -9754,6 +10057,7 @@ function wrapFunctionOperationError(operation, functionName, functionRootPath, e
|
|
|
9754
10057
|
}
|
|
9755
10058
|
function registerFunctionTools(server) {
|
|
9756
10059
|
const cloudBaseOptions = server.cloudBaseOptions;
|
|
10060
|
+
const deployOverrides = server.pluginOptions?.functions;
|
|
9757
10061
|
const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
|
|
9758
10062
|
const buildEnvelope = (data, message, nextActions) => ({
|
|
9759
10063
|
success: true,
|
|
@@ -10118,6 +10422,16 @@ function registerFunctionTools(server) {
|
|
|
10118
10422
|
ensureActionAllowedInCloudMode(input);
|
|
10119
10423
|
switch (input.action) {
|
|
10120
10424
|
case "createFunction": {
|
|
10425
|
+
if (deployOverrides?.createFunction) {
|
|
10426
|
+
const result = await deployOverrides.createFunction({
|
|
10427
|
+
functionName: String(input.func?.name ?? input.functionName ?? ''),
|
|
10428
|
+
functionRootPath: input.functionRootPath ?? '',
|
|
10429
|
+
runtime: input.func?.runtime,
|
|
10430
|
+
force: input.force,
|
|
10431
|
+
installDependency: input.func?.installDependency,
|
|
10432
|
+
});
|
|
10433
|
+
return buildEnvelope({ action: input.action, result }, '云函数部署成功(override)');
|
|
10434
|
+
}
|
|
10121
10435
|
if (!input.func?.name || typeof input.func.name !== "string") {
|
|
10122
10436
|
throw new Error("createFunction 操作时,func.name 参数是必需的");
|
|
10123
10437
|
}
|
|
@@ -10205,6 +10519,15 @@ function registerFunctionTools(server) {
|
|
|
10205
10519
|
}, message, nextActions);
|
|
10206
10520
|
}
|
|
10207
10521
|
case "updateFunctionCode": {
|
|
10522
|
+
if (deployOverrides?.updateFunctionCode) {
|
|
10523
|
+
const result = await deployOverrides.updateFunctionCode({
|
|
10524
|
+
functionName: input.functionName ?? '',
|
|
10525
|
+
functionRootPath: input.functionRootPath ?? '',
|
|
10526
|
+
force: input.force,
|
|
10527
|
+
installDependency: input.func?.installDependency,
|
|
10528
|
+
});
|
|
10529
|
+
return buildEnvelope({ action: input.action, result }, '云函数代码更新成功(override)');
|
|
10530
|
+
}
|
|
10208
10531
|
if (!input.functionName) {
|
|
10209
10532
|
throw new Error("updateFunctionCode 操作时,functionName 参数是必需的");
|
|
10210
10533
|
}
|
|
@@ -10538,133 +10861,1404 @@ function registerFunctionTools(server) {
|
|
|
10538
10861
|
]);
|
|
10539
10862
|
}
|
|
10540
10863
|
default:
|
|
10864
|
+
// incrementalDeployFunction:无默认实现,必须通过 pluginOptions 注入
|
|
10865
|
+
if (input.action === 'incrementalDeployFunction') {
|
|
10866
|
+
const fn = deployOverrides?.incrementalDeployFunction;
|
|
10867
|
+
if (!fn) {
|
|
10868
|
+
throw new Error('incrementalDeployFunction 需要通过 pluginOptions.functions.incrementalDeployFunction 注入实现(仅在支持的 IDE 环境中可用)');
|
|
10869
|
+
}
|
|
10870
|
+
const result = await fn({
|
|
10871
|
+
functionName: input.functionName ?? '',
|
|
10872
|
+
functionRootPath: input.functionRootPath ?? '',
|
|
10873
|
+
incrementalFile: input.incrementalFile ?? '',
|
|
10874
|
+
});
|
|
10875
|
+
return buildEnvelope({ action: input.action, result }, '云函数增量部署成功');
|
|
10876
|
+
}
|
|
10541
10877
|
throw new Error(`不支持的操作类型: ${input.action}`);
|
|
10542
10878
|
}
|
|
10543
10879
|
};
|
|
10544
|
-
server.registerTool?.("queryFunctions", {
|
|
10545
|
-
title: "查询 CloudBase 云函数",
|
|
10546
|
-
description: "CloudBase 云函数统一只读入口。通过更自解释的 action 查询 CloudBase 云函数列表、函数详情、执行日志、层、触发器和代码下载地址。" +
|
|
10547
|
-
"\n\n**分页说明**:`listFunctions`、`listLayers` 支持 `limit` 和 `offset` 参数。" +
|
|
10548
|
-
"\n- `limit`: 分页数量,默认值由后端决定" +
|
|
10549
|
-
"\n- `offset`: 分页偏移,从 0 开始" +
|
|
10550
|
-
"\n- 示例:`queryFunctions(action=\"listFunctions\", offset=10, limit=10)`" +
|
|
10551
|
-
"\n\n**查询 CloudBase 云函数日志**:使用 `action=\"listFunctionLogs\"`,需要提供 `functionName` 参数。" +
|
|
10552
|
-
"\n- 示例:`queryFunctions(action=\"listFunctionLogs\", functionName=\"my-function\")`" +
|
|
10553
|
-
"\n- 如需查看日志详情:`queryFunctions(action=\"getFunctionLogDetail\", requestId=\"xxx\")`" +
|
|
10554
|
-
"\n\n**定时任务 / cron / 定时跑**:使用 `listFunctionTriggers` 查询函数的 timer 触发器配置。" +
|
|
10555
|
-
"\n\n**区分 `queryLogs` 工具**:" +
|
|
10556
|
-
"\n- 本工具用于查询特定 CloudBase 云函数的执行日志" +
|
|
10557
|
-
"\n- `queryLogs` 工具用于搜索 CLS 日志服务(跨服务日志聚合)",
|
|
10880
|
+
server.registerTool?.("queryFunctions", {
|
|
10881
|
+
title: "查询 CloudBase 云函数",
|
|
10882
|
+
description: "CloudBase 云函数统一只读入口。通过更自解释的 action 查询 CloudBase 云函数列表、函数详情、执行日志、层、触发器和代码下载地址。" +
|
|
10883
|
+
"\n\n**分页说明**:`listFunctions`、`listLayers` 支持 `limit` 和 `offset` 参数。" +
|
|
10884
|
+
"\n- `limit`: 分页数量,默认值由后端决定" +
|
|
10885
|
+
"\n- `offset`: 分页偏移,从 0 开始" +
|
|
10886
|
+
"\n- 示例:`queryFunctions(action=\"listFunctions\", offset=10, limit=10)`" +
|
|
10887
|
+
"\n\n**查询 CloudBase 云函数日志**:使用 `action=\"listFunctionLogs\"`,需要提供 `functionName` 参数。" +
|
|
10888
|
+
"\n- 示例:`queryFunctions(action=\"listFunctionLogs\", functionName=\"my-function\")`" +
|
|
10889
|
+
"\n- 如需查看日志详情:`queryFunctions(action=\"getFunctionLogDetail\", requestId=\"xxx\")`" +
|
|
10890
|
+
"\n\n**定时任务 / cron / 定时跑**:使用 `listFunctionTriggers` 查询函数的 timer 触发器配置。" +
|
|
10891
|
+
"\n\n**区分 `queryLogs` 工具**:" +
|
|
10892
|
+
"\n- 本工具用于查询特定 CloudBase 云函数的执行日志" +
|
|
10893
|
+
"\n- `queryLogs` 工具用于搜索 CLS 日志服务(跨服务日志聚合)",
|
|
10894
|
+
inputSchema: {
|
|
10895
|
+
action: zod_1.z
|
|
10896
|
+
.enum(exports.QUERY_FUNCTION_ACTIONS)
|
|
10897
|
+
.describe("只读操作类型:" +
|
|
10898
|
+
"\n- `listFunctions`: 列出所有 CloudBase 云函数" +
|
|
10899
|
+
"\n- `getFunctionDetail`: 获取 CloudBase 云函数详情(需要 functionName)" +
|
|
10900
|
+
"\n- `listFunctionLogs`: 查询 CloudBase 云函数执行日志(需要 functionName)" +
|
|
10901
|
+
"\n- `getFunctionLogDetail`: 获取日志详情(需要 requestId)" +
|
|
10902
|
+
"\n- `listFunctionLayers`: 列出函数绑定的层" +
|
|
10903
|
+
"\n- `listLayers`: 列出所有层" +
|
|
10904
|
+
"\n- `listLayerVersions`: 列出层的版本(注意:是 Versions 不是 Version)" +
|
|
10905
|
+
"\n- `getLayerVersionDetail`: 获取层版本详情" +
|
|
10906
|
+
"\n- `listFunctionTriggers`: 列出函数触发器(用于查看定时任务 / cron / timer 配置)" +
|
|
10907
|
+
"\n- `getFunctionDownloadUrl`: 获取函数代码下载地址"),
|
|
10908
|
+
functionName: zod_1.z
|
|
10909
|
+
.string()
|
|
10910
|
+
.optional()
|
|
10911
|
+
.describe("CloudBase 云函数名称。`getFunctionDetail`、`listFunctionLogs`、`listFunctionLayers`、`listFunctionTriggers`、`getFunctionDownloadUrl` 时必填"),
|
|
10912
|
+
limit: zod_1.z.number().optional().describe("分页数量(limit)。列表类 action 可选,默认值由后端决定"),
|
|
10913
|
+
offset: zod_1.z.number().optional().describe("分页偏移(offset)。列表类 action 可选,默认 0"),
|
|
10914
|
+
codeSecret: zod_1.z.string().optional().describe("代码保护密钥,用于解密函数代码"),
|
|
10915
|
+
startTime: zod_1.z
|
|
10916
|
+
.string()
|
|
10917
|
+
.optional()
|
|
10918
|
+
.describe("日志查询开始时间,格式必须为 YYYY-MM-DD HH:mm:ss(如 2024-01-01 00:00:00)。" +
|
|
10919
|
+
"与 endTime 间隔不能超过一天。不传时默认查询最近一天"),
|
|
10920
|
+
endTime: zod_1.z
|
|
10921
|
+
.string()
|
|
10922
|
+
.optional()
|
|
10923
|
+
.describe("日志查询结束时间,格式必须为 YYYY-MM-DD HH:mm:ss(如 2024-01-01 23:59:59)。" +
|
|
10924
|
+
"与 startTime 间隔不能超过一天。不传时默认为当前时间"),
|
|
10925
|
+
requestId: zod_1.z
|
|
10926
|
+
.string()
|
|
10927
|
+
.optional()
|
|
10928
|
+
.describe("日志请求 ID。`getFunctionLogDetail` 操作必填,可从 `listFunctionLogs` 结果中获取"),
|
|
10929
|
+
qualifier: zod_1.z.string().optional().describe("函数版本别名,如 $LATEST、$DEFAULT。日志查询时可选"),
|
|
10930
|
+
runtime: zod_1.z.string().optional().describe("层查询的运行时筛选,如 Nodejs18.15"),
|
|
10931
|
+
searchKey: zod_1.z.string().optional().describe("层名称搜索关键字"),
|
|
10932
|
+
layerName: zod_1.z.string().optional().describe("层名称。`listLayerVersions`、`getLayerVersionDetail` 操作必填"),
|
|
10933
|
+
layerVersion: zod_1.z.number().optional().describe("层版本号。`getLayerVersionDetail` 操作必填"),
|
|
10934
|
+
},
|
|
10935
|
+
annotations: {
|
|
10936
|
+
readOnlyHint: true,
|
|
10937
|
+
openWorldHint: true,
|
|
10938
|
+
category: "functions",
|
|
10939
|
+
},
|
|
10940
|
+
}, async (input) => withEnvelope(() => handleQueryFunctions(input)));
|
|
10941
|
+
server.registerTool?.("manageFunctions", {
|
|
10942
|
+
title: "管理 CloudBase 云函数",
|
|
10943
|
+
description: "CloudBase 云函数统一写入口。支持创建函数、更新代码、更新配置、调用函数、管理定时跑 / 定时任务 / scheduled job 的 timer 触发器和层绑定。" +
|
|
10944
|
+
"如果要创建 cron 定时任务,先用 createFunction 创建函数,再用 createFunctionTrigger 创建 timer 触发器(支持7段cron表达式),deleteFunctionTrigger 删除触发器。" +
|
|
10945
|
+
"危险操作需要显式 confirm=true。",
|
|
10946
|
+
inputSchema: {
|
|
10947
|
+
action: zod_1.z
|
|
10948
|
+
.enum(exports.MANAGE_FUNCTION_ACTIONS)
|
|
10949
|
+
.describe("写操作类型,例如 createFunction、updateFunctionCode、incrementalDeployFunction、invokeFunction、deleteFunction、" +
|
|
10950
|
+
"createFunctionTrigger(定时任务 / cron / timer)、deleteFunctionTrigger、attachLayer、detachLayer"),
|
|
10951
|
+
func: CREATE_FUNCTION_SCHEMA.optional().describe("createFunction 操作的函数配置"),
|
|
10952
|
+
functionRootPath: zod_1.z.string().optional().describe("创建或更新函数代码时默认推荐的本地目录方式。" +
|
|
10953
|
+
"必须是直接包含函数文件夹的目录绝对路径(如 /abs/path/cloudfunctions 或 /abs/path/functions)," +
|
|
10954
|
+
"不要传项目根目录(如 /abs/path),也不要传到函数名子目录(如 /abs/path/cloudfunctions/hello)。" +
|
|
10955
|
+
"本地应按 cloudfunctions/<functionName>/index.js 或 functions/<functionName>/index.js 布局," +
|
|
10956
|
+
"此参数传 cloudfunctions 或 functions 目录的绝对路径。" +
|
|
10957
|
+
"SDK 会自动拼接函数名子目录,无需预先压缩 zip 或 base64 编码。"),
|
|
10958
|
+
force: zod_1.z.boolean().optional().describe("createFunction 时是否覆盖"),
|
|
10959
|
+
functionName: zod_1.z.string().optional().describe("函数名称。大多数 action 使用该字段作为统一目标"),
|
|
10960
|
+
zipFile: zod_1.z.string().optional().describe("仅兼容特殊场景:预先准备好的代码包 base64 编码。普通 createFunction/updateFunctionCode 默认不要先压缩 zip,优先使用 functionRootPath。"),
|
|
10961
|
+
handler: zod_1.z.string().optional().describe("函数入口"),
|
|
10962
|
+
timeout: zod_1.z.number().optional().describe("配置更新时的超时时间"),
|
|
10963
|
+
envVariables: zod_1.z.record(zod_1.z.string()).optional().describe("配置更新时要合并的环境变量"),
|
|
10964
|
+
vpc: VPC_SCHEMA.optional().describe("配置更新时的 VPC 信息"),
|
|
10965
|
+
params: zod_1.z.record(zod_1.z.any()).optional().describe("invokeFunction 的调用参数"),
|
|
10966
|
+
triggers: zod_1.z
|
|
10967
|
+
.array(TRIGGER_SCHEMA)
|
|
10968
|
+
.optional()
|
|
10969
|
+
.describe("createFunctionTrigger 的触发器列表,用于定时跑 / 定时任务 / scheduled job。timer 触发器使用7段 cron 表达式(秒 分 时 日 月 星期 年)," +
|
|
10970
|
+
'如 "0 */5 * * * * *" 表示每5分钟执行一次'),
|
|
10971
|
+
triggerName: zod_1.z.string().optional().describe("deleteFunctionTrigger 的目标触发器名称"),
|
|
10972
|
+
layerName: zod_1.z.string().optional().describe("层名称"),
|
|
10973
|
+
layerVersion: zod_1.z.number().optional().describe("层版本号"),
|
|
10974
|
+
contentPath: zod_1.z.string().optional().describe("层内容路径,可为目录或 ZIP 文件"),
|
|
10975
|
+
base64Content: zod_1.z.string().optional().describe("层内容的 base64 编码"),
|
|
10976
|
+
runtimes: zod_1.z.array(zod_1.z.string()).optional().describe("层适用的运行时列表"),
|
|
10977
|
+
description: zod_1.z.string().optional().describe("层版本描述"),
|
|
10978
|
+
licenseInfo: zod_1.z.string().optional().describe("层许可证信息"),
|
|
10979
|
+
layers: zod_1.z
|
|
10980
|
+
.array(MANAGE_LAYER_SCHEMA)
|
|
10981
|
+
.optional()
|
|
10982
|
+
.describe("updateFunctionLayers 的目标层列表,顺序即最终顺序"),
|
|
10983
|
+
codeSecret: zod_1.z.string().optional().describe("层绑定时的代码保护密钥"),
|
|
10984
|
+
confirm: zod_1.z.boolean().optional().describe("危险操作确认开关。deleteFunction、deleteFunctionTrigger、deleteLayerVersion、detachLayer 等删除类操作需要显式传入 confirm=true"),
|
|
10985
|
+
incrementalFile: zod_1.z.string().optional().describe("incrementalDeployFunction 增量部署时的变更文件路径"),
|
|
10986
|
+
},
|
|
10987
|
+
annotations: {
|
|
10988
|
+
readOnlyHint: false,
|
|
10989
|
+
destructiveHint: true,
|
|
10990
|
+
idempotentHint: false,
|
|
10991
|
+
openWorldHint: true,
|
|
10992
|
+
category: "functions",
|
|
10993
|
+
},
|
|
10994
|
+
}, async (input) => withEnvelope(() => handleManageFunctions(input)));
|
|
10995
|
+
}
|
|
10996
|
+
|
|
10997
|
+
|
|
10998
|
+
/***/ }),
|
|
10999
|
+
|
|
11000
|
+
/***/ 6220:
|
|
11001
|
+
/***/ ((module) => {
|
|
11002
|
+
|
|
11003
|
+
module.exports = __WEBPACK_EXTERNAL_MODULE_ws__;
|
|
11004
|
+
|
|
11005
|
+
/***/ }),
|
|
11006
|
+
|
|
11007
|
+
/***/ 6473:
|
|
11008
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
11009
|
+
|
|
11010
|
+
|
|
11011
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11012
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11013
|
+
};
|
|
11014
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
11015
|
+
exports.registerPGDatabaseTools = registerPGDatabaseTools;
|
|
11016
|
+
const node_fs_1 = __webpack_require__(9627);
|
|
11017
|
+
const node_path_1 = __importDefault(__webpack_require__(5663));
|
|
11018
|
+
const zod_1 = __webpack_require__(2971);
|
|
11019
|
+
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
11020
|
+
const tool_result_js_1 = __webpack_require__(9835);
|
|
11021
|
+
const CATEGORY = "PostgreSQL database";
|
|
11022
|
+
const QUERY_PG_DATABASE = "queryPgDatabase";
|
|
11023
|
+
const MANAGE_PG_DATABASE = "managePgDatabase";
|
|
11024
|
+
const QUERY_ACTIONS = [
|
|
11025
|
+
"context",
|
|
11026
|
+
"objects",
|
|
11027
|
+
"metadata",
|
|
11028
|
+
"schema",
|
|
11029
|
+
"sql",
|
|
11030
|
+
];
|
|
11031
|
+
const MANAGE_ACTIONS = [
|
|
11032
|
+
"init",
|
|
11033
|
+
"execute",
|
|
11034
|
+
"dryRun",
|
|
11035
|
+
"planMigration",
|
|
11036
|
+
"applyMigration",
|
|
11037
|
+
"listMigrations",
|
|
11038
|
+
"migrationDetail",
|
|
11039
|
+
"rollbackMigration",
|
|
11040
|
+
];
|
|
11041
|
+
function buildPgToolResult(payload) {
|
|
11042
|
+
return (0, tool_result_js_1.buildJsonToolResult)(payload);
|
|
11043
|
+
}
|
|
11044
|
+
function buildNextAction(tool, action, reason, suggestedArgs) {
|
|
11045
|
+
return {
|
|
11046
|
+
tool,
|
|
11047
|
+
action,
|
|
11048
|
+
suggested_args: suggestedArgs,
|
|
11049
|
+
required_params: undefined,
|
|
11050
|
+
reason,
|
|
11051
|
+
};
|
|
11052
|
+
}
|
|
11053
|
+
function getPgContextStorePath() {
|
|
11054
|
+
return (process.env.CLOUDBASE_PG_CONTEXT_PATH ??
|
|
11055
|
+
node_path_1.default.join(process.env.HOME ?? ".", ".cloudbase-mcp", "pg-context.json"));
|
|
11056
|
+
}
|
|
11057
|
+
async function loadStoredPgContext() {
|
|
11058
|
+
if (!process.env.CLOUDBASE_PG_CONTEXT_PATH) {
|
|
11059
|
+
return undefined;
|
|
11060
|
+
}
|
|
11061
|
+
try {
|
|
11062
|
+
const raw = await node_fs_1.promises.readFile(getPgContextStorePath(), "utf8");
|
|
11063
|
+
return JSON.parse(raw);
|
|
11064
|
+
}
|
|
11065
|
+
catch {
|
|
11066
|
+
return undefined;
|
|
11067
|
+
}
|
|
11068
|
+
}
|
|
11069
|
+
async function savePgContext(server, context) {
|
|
11070
|
+
server.pgRuntimeContext = context;
|
|
11071
|
+
if (!process.env.CLOUDBASE_PG_CONTEXT_PATH) {
|
|
11072
|
+
return;
|
|
11073
|
+
}
|
|
11074
|
+
const storePath = getPgContextStorePath();
|
|
11075
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(storePath), { recursive: true });
|
|
11076
|
+
await node_fs_1.promises.writeFile(storePath, JSON.stringify(context, null, 2), {
|
|
11077
|
+
encoding: "utf8",
|
|
11078
|
+
mode: 0o600,
|
|
11079
|
+
});
|
|
11080
|
+
await node_fs_1.promises.chmod(storePath, 0o600).catch(() => undefined);
|
|
11081
|
+
}
|
|
11082
|
+
async function getPgContext(server) {
|
|
11083
|
+
if (server.pgRuntimeContext) {
|
|
11084
|
+
return server.pgRuntimeContext;
|
|
11085
|
+
}
|
|
11086
|
+
const stored = await loadStoredPgContext();
|
|
11087
|
+
if (stored) {
|
|
11088
|
+
server.pgRuntimeContext = stored;
|
|
11089
|
+
}
|
|
11090
|
+
return stored;
|
|
11091
|
+
}
|
|
11092
|
+
function buildMissingContextResult() {
|
|
11093
|
+
return buildPgToolResult({
|
|
11094
|
+
success: false,
|
|
11095
|
+
errorCode: "PG_CONTEXT_NOT_INITIALIZED",
|
|
11096
|
+
message: "PostgreSQL context is not initialized yet. Call managePgDatabase(action=init) to bind the current CloudBase environment's built-in PostgreSQL runtime.",
|
|
11097
|
+
nextActions: [
|
|
11098
|
+
buildNextAction(MANAGE_PG_DATABASE, "init", "初始化 CloudBase PG 云端执行上下文,然后再查询 schema 或执行 SQL。", { action: "init" }),
|
|
11099
|
+
],
|
|
11100
|
+
});
|
|
11101
|
+
}
|
|
11102
|
+
function sanitizePgContext(context) {
|
|
11103
|
+
return {
|
|
11104
|
+
envId: context.envId,
|
|
11105
|
+
instanceId: context.instanceId,
|
|
11106
|
+
defaultSchema: context.defaultSchema,
|
|
11107
|
+
runtimeMode: context.runtimeMode,
|
|
11108
|
+
bootstrapMode: context.bootstrapMode,
|
|
11109
|
+
role: context.role,
|
|
11110
|
+
};
|
|
11111
|
+
}
|
|
11112
|
+
function normalizeLimit(limit, fallback = 20, max = 200) {
|
|
11113
|
+
if (!Number.isFinite(limit)) {
|
|
11114
|
+
return fallback;
|
|
11115
|
+
}
|
|
11116
|
+
return Math.max(1, Math.min(max, Math.floor(limit)));
|
|
11117
|
+
}
|
|
11118
|
+
function stripLeadingSqlComments(sql) {
|
|
11119
|
+
let normalized = sql.trim();
|
|
11120
|
+
while (normalized.length > 0) {
|
|
11121
|
+
if (normalized.startsWith("--")) {
|
|
11122
|
+
normalized = normalized.replace(/^--.*(?:\r?\n|$)/, "").trimStart();
|
|
11123
|
+
continue;
|
|
11124
|
+
}
|
|
11125
|
+
if (normalized.startsWith("#")) {
|
|
11126
|
+
normalized = normalized.replace(/^#.*(?:\r?\n|$)/, "").trimStart();
|
|
11127
|
+
continue;
|
|
11128
|
+
}
|
|
11129
|
+
if (normalized.startsWith("/*")) {
|
|
11130
|
+
normalized = normalized.replace(/^\/\*[\s\S]*?\*\//, "").trimStart();
|
|
11131
|
+
continue;
|
|
11132
|
+
}
|
|
11133
|
+
break;
|
|
11134
|
+
}
|
|
11135
|
+
return normalized;
|
|
11136
|
+
}
|
|
11137
|
+
function getSqlVerb(sql) {
|
|
11138
|
+
const normalized = stripLeadingSqlComments(sql);
|
|
11139
|
+
const match = normalized.match(/^([a-zA-Z]+)/);
|
|
11140
|
+
return match ? match[1].toUpperCase() : "";
|
|
11141
|
+
}
|
|
11142
|
+
function isReadOnlySql(sql) {
|
|
11143
|
+
const normalized = stripLeadingSqlComments(sql);
|
|
11144
|
+
const verb = getSqlVerb(normalized);
|
|
11145
|
+
const readOnlyVerbs = new Set([
|
|
11146
|
+
"SELECT",
|
|
11147
|
+
"SHOW",
|
|
11148
|
+
"EXPLAIN",
|
|
11149
|
+
"WITH",
|
|
11150
|
+
"VALUES",
|
|
11151
|
+
]);
|
|
11152
|
+
if (!readOnlyVerbs.has(verb)) {
|
|
11153
|
+
return false;
|
|
11154
|
+
}
|
|
11155
|
+
return !/\b(INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|TRUNCATE|REPLACE|RENAME|GRANT|REVOKE|COMMENT|VACUUM|ANALYZE|FOR\s+UPDATE|FOR\s+SHARE)\b/i.test(normalized);
|
|
11156
|
+
}
|
|
11157
|
+
function buildLimitedReadOnlySql(sql, limit) {
|
|
11158
|
+
const normalized = stripLeadingSqlComments(sql).replace(/;\s*$/, "");
|
|
11159
|
+
const verb = getSqlVerb(normalized);
|
|
11160
|
+
if (!["SELECT", "WITH", "VALUES"].includes(verb)) {
|
|
11161
|
+
return sql;
|
|
11162
|
+
}
|
|
11163
|
+
return `SELECT * FROM (${normalized}) AS cloudbase_mcp_readonly_limit LIMIT ${limit}`;
|
|
11164
|
+
}
|
|
11165
|
+
function isDestructiveSql(sql) {
|
|
11166
|
+
const normalized = stripLeadingSqlComments(sql);
|
|
11167
|
+
const verb = getSqlVerb(normalized);
|
|
11168
|
+
if (["DROP", "TRUNCATE", "DELETE"].includes(verb)) {
|
|
11169
|
+
return true;
|
|
11170
|
+
}
|
|
11171
|
+
if (verb === "ALTER") {
|
|
11172
|
+
return /\b(DROP|RENAME)\b/i.test(normalized);
|
|
11173
|
+
}
|
|
11174
|
+
return false;
|
|
11175
|
+
}
|
|
11176
|
+
function classifySqlRisk(sql) {
|
|
11177
|
+
const normalized = stripLeadingSqlComments(sql);
|
|
11178
|
+
const verb = getSqlVerb(normalized);
|
|
11179
|
+
if (!verb) {
|
|
11180
|
+
return {
|
|
11181
|
+
risk: "unknown_risk",
|
|
11182
|
+
readOnly: false,
|
|
11183
|
+
requiresConfirm: true,
|
|
11184
|
+
};
|
|
11185
|
+
}
|
|
11186
|
+
if (isReadOnlySql(normalized)) {
|
|
11187
|
+
return {
|
|
11188
|
+
risk: "read_only",
|
|
11189
|
+
readOnly: true,
|
|
11190
|
+
requiresConfirm: false,
|
|
11191
|
+
};
|
|
11192
|
+
}
|
|
11193
|
+
if (isDestructiveSql(normalized)) {
|
|
11194
|
+
return {
|
|
11195
|
+
risk: "destructive",
|
|
11196
|
+
readOnly: false,
|
|
11197
|
+
requiresConfirm: true,
|
|
11198
|
+
};
|
|
11199
|
+
}
|
|
11200
|
+
if (/\b(GRANT|REVOKE|CREATE\s+POLICY|ALTER\s+POLICY|DROP\s+POLICY|ENABLE\s+ROW\s+LEVEL\s+SECURITY|DISABLE\s+ROW\s+LEVEL\s+SECURITY)\b/i.test(normalized)) {
|
|
11201
|
+
return {
|
|
11202
|
+
risk: "security_change",
|
|
11203
|
+
readOnly: false,
|
|
11204
|
+
requiresConfirm: true,
|
|
11205
|
+
};
|
|
11206
|
+
}
|
|
11207
|
+
if (["CREATE", "ALTER", "COMMENT"].includes(verb)) {
|
|
11208
|
+
return {
|
|
11209
|
+
risk: "schema_change",
|
|
11210
|
+
readOnly: false,
|
|
11211
|
+
requiresConfirm: true,
|
|
11212
|
+
};
|
|
11213
|
+
}
|
|
11214
|
+
if (["INSERT", "UPDATE", "DELETE", "MERGE"].includes(verb)) {
|
|
11215
|
+
return {
|
|
11216
|
+
risk: "normal_write",
|
|
11217
|
+
readOnly: false,
|
|
11218
|
+
requiresConfirm: true,
|
|
11219
|
+
};
|
|
11220
|
+
}
|
|
11221
|
+
return {
|
|
11222
|
+
risk: "unknown_risk",
|
|
11223
|
+
readOnly: false,
|
|
11224
|
+
requiresConfirm: true,
|
|
11225
|
+
};
|
|
11226
|
+
}
|
|
11227
|
+
function quoteIdentifier(identifier) {
|
|
11228
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
11229
|
+
}
|
|
11230
|
+
function buildSchemaNextAction(reason, objectName) {
|
|
11231
|
+
return buildNextAction(QUERY_PG_DATABASE, "schema", reason, {
|
|
11232
|
+
action: "schema",
|
|
11233
|
+
objectName,
|
|
11234
|
+
});
|
|
11235
|
+
}
|
|
11236
|
+
function parseSchemaQualifiedName(objectName) {
|
|
11237
|
+
const match = objectName
|
|
11238
|
+
.trim()
|
|
11239
|
+
.match(/^([A-Za-z_][A-Za-z0-9_$]*)\.([A-Za-z_][A-Za-z0-9_$]*)$/);
|
|
11240
|
+
if (!match) {
|
|
11241
|
+
return null;
|
|
11242
|
+
}
|
|
11243
|
+
return {
|
|
11244
|
+
schema: match[1],
|
|
11245
|
+
name: match[2],
|
|
11246
|
+
};
|
|
11247
|
+
}
|
|
11248
|
+
function serializeValue(value) {
|
|
11249
|
+
if (value instanceof Date) {
|
|
11250
|
+
return value.toISOString();
|
|
11251
|
+
}
|
|
11252
|
+
if (typeof value === "bigint") {
|
|
11253
|
+
return value.toString();
|
|
11254
|
+
}
|
|
11255
|
+
if (Array.isArray(value)) {
|
|
11256
|
+
return value.map((item) => serializeValue(item));
|
|
11257
|
+
}
|
|
11258
|
+
if (value && typeof value === "object") {
|
|
11259
|
+
return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [
|
|
11260
|
+
key,
|
|
11261
|
+
serializeValue(nestedValue),
|
|
11262
|
+
]));
|
|
11263
|
+
}
|
|
11264
|
+
return value;
|
|
11265
|
+
}
|
|
11266
|
+
function truncateText(value, maxLength = 180) {
|
|
11267
|
+
if (value === null || value === undefined) {
|
|
11268
|
+
return null;
|
|
11269
|
+
}
|
|
11270
|
+
const text = String(value);
|
|
11271
|
+
return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
|
|
11272
|
+
}
|
|
11273
|
+
function buildSchemaTable(schema, name) {
|
|
11274
|
+
return `${schema}.${name}`;
|
|
11275
|
+
}
|
|
11276
|
+
function parseTargetTableFromSql(sql, defaultSchema) {
|
|
11277
|
+
const normalized = stripLeadingSqlComments(sql);
|
|
11278
|
+
const identifier = String.raw `((?:[A-Za-z_][A-Za-z0-9_$]*\.)?[A-Za-z_][A-Za-z0-9_$]*)`;
|
|
11279
|
+
const patterns = [
|
|
11280
|
+
new RegExp(String.raw `\bcreate\s+(?:temporary\s+|temp\s+|unlogged\s+)?table\s+(?:if\s+not\s+exists\s+)?${identifier}`, "i"),
|
|
11281
|
+
new RegExp(String.raw `\b(?:alter|drop|truncate)\s+table\s+(?:if\s+exists\s+)?${identifier}`, "i"),
|
|
11282
|
+
new RegExp(String.raw `\binsert\s+into\s+${identifier}`, "i"),
|
|
11283
|
+
new RegExp(String.raw `\bupdate\s+${identifier}`, "i"),
|
|
11284
|
+
new RegExp(String.raw `\b(?:delete\s+from|from)\s+${identifier}`, "i"),
|
|
11285
|
+
];
|
|
11286
|
+
const match = patterns
|
|
11287
|
+
.map((pattern) => normalized.match(pattern))
|
|
11288
|
+
.find(Boolean);
|
|
11289
|
+
if (!match?.[1]) {
|
|
11290
|
+
return undefined;
|
|
11291
|
+
}
|
|
11292
|
+
if (match[1].includes(".")) {
|
|
11293
|
+
return match[1];
|
|
11294
|
+
}
|
|
11295
|
+
return `${defaultSchema}.${match[1]}`;
|
|
11296
|
+
}
|
|
11297
|
+
function summarizeQueryResult(result, limit) {
|
|
11298
|
+
const rows = result.rows.map((row) => serializeValue(row));
|
|
11299
|
+
const trimmedRows = rows.slice(0, limit);
|
|
11300
|
+
return {
|
|
11301
|
+
columns: result.fields?.map((field) => field.name) ??
|
|
11302
|
+
(trimmedRows[0] ? Object.keys(trimmedRows[0]) : []),
|
|
11303
|
+
rows: trimmedRows,
|
|
11304
|
+
returnedRows: rows.length,
|
|
11305
|
+
truncated: rows.length > trimmedRows.length,
|
|
11306
|
+
truncatedCount: Math.max(rows.length - trimmedRows.length, 0),
|
|
11307
|
+
};
|
|
11308
|
+
}
|
|
11309
|
+
async function withPgClient(context, deps, callback) {
|
|
11310
|
+
const client = await deps.createClient(context);
|
|
11311
|
+
await client.connect();
|
|
11312
|
+
try {
|
|
11313
|
+
return await callback(client);
|
|
11314
|
+
}
|
|
11315
|
+
finally {
|
|
11316
|
+
await client.end();
|
|
11317
|
+
}
|
|
11318
|
+
}
|
|
11319
|
+
async function getTableRowCount(client, schema, table) {
|
|
11320
|
+
const qualifiedName = `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
|
|
11321
|
+
const result = await client.query(`SELECT COUNT(*)::bigint AS row_count FROM ${qualifiedName}`);
|
|
11322
|
+
const rawValue = result.rows[0]?.row_count;
|
|
11323
|
+
const parsed = Number(rawValue);
|
|
11324
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
11325
|
+
}
|
|
11326
|
+
async function listObjects(client, schema, limit) {
|
|
11327
|
+
const result = await client.query(`
|
|
11328
|
+
SELECT
|
|
11329
|
+
n.nspname AS schema,
|
|
11330
|
+
c.relname AS name,
|
|
11331
|
+
CASE c.relkind
|
|
11332
|
+
WHEN 'r' THEN 'table'
|
|
11333
|
+
WHEN 'p' THEN 'partitioned_table'
|
|
11334
|
+
WHEN 'v' THEN 'view'
|
|
11335
|
+
WHEN 'm' THEN 'materialized_view'
|
|
11336
|
+
WHEN 'f' THEN 'foreign_table'
|
|
11337
|
+
ELSE c.relkind::text
|
|
11338
|
+
END AS kind,
|
|
11339
|
+
CASE
|
|
11340
|
+
WHEN c.reltuples >= 0 THEN c.reltuples::bigint
|
|
11341
|
+
ELSE NULL
|
|
11342
|
+
END AS estimated_rows
|
|
11343
|
+
FROM pg_class c
|
|
11344
|
+
INNER JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
11345
|
+
WHERE c.relkind IN ('r', 'p', 'v', 'm', 'f')
|
|
11346
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
11347
|
+
AND n.nspname NOT LIKE 'pg_toast%'
|
|
11348
|
+
AND ($1::text IS NULL OR n.nspname = $1)
|
|
11349
|
+
ORDER BY n.nspname, c.relname
|
|
11350
|
+
LIMIT $2
|
|
11351
|
+
`, [schema ?? null, limit]);
|
|
11352
|
+
return result.rows.map((row) => ({
|
|
11353
|
+
schema: String(row.schema),
|
|
11354
|
+
name: String(row.name),
|
|
11355
|
+
schemaTable: buildSchemaTable(String(row.schema), String(row.name)),
|
|
11356
|
+
kind: String(row.kind),
|
|
11357
|
+
estimatedRows: row.estimated_rows === null || row.estimated_rows === undefined
|
|
11358
|
+
? null
|
|
11359
|
+
: Number(row.estimated_rows),
|
|
11360
|
+
}));
|
|
11361
|
+
}
|
|
11362
|
+
async function summarizeMetadata(client, schema, limit) {
|
|
11363
|
+
const result = await client.query(`
|
|
11364
|
+
SELECT
|
|
11365
|
+
n.nspname AS schema,
|
|
11366
|
+
c.relname AS name,
|
|
11367
|
+
CASE c.relkind
|
|
11368
|
+
WHEN 'r' THEN 'table'
|
|
11369
|
+
WHEN 'p' THEN 'partitioned_table'
|
|
11370
|
+
WHEN 'v' THEN 'view'
|
|
11371
|
+
WHEN 'm' THEN 'materialized_view'
|
|
11372
|
+
ELSE c.relkind::text
|
|
11373
|
+
END AS kind,
|
|
11374
|
+
CASE
|
|
11375
|
+
WHEN c.reltuples >= 0 THEN c.reltuples::bigint
|
|
11376
|
+
ELSE NULL
|
|
11377
|
+
END AS estimated_rows,
|
|
11378
|
+
c.relrowsecurity AS rls_enabled,
|
|
11379
|
+
COALESCE(col_counts.column_count, 0)::int AS column_count
|
|
11380
|
+
FROM pg_class c
|
|
11381
|
+
INNER JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
11382
|
+
LEFT JOIN LATERAL (
|
|
11383
|
+
SELECT COUNT(*) AS column_count
|
|
11384
|
+
FROM information_schema.columns cols
|
|
11385
|
+
WHERE cols.table_schema = n.nspname
|
|
11386
|
+
AND cols.table_name = c.relname
|
|
11387
|
+
) AS col_counts ON TRUE
|
|
11388
|
+
WHERE c.relkind IN ('r', 'p', 'v', 'm')
|
|
11389
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
11390
|
+
AND n.nspname NOT LIKE 'pg_toast%'
|
|
11391
|
+
AND ($1::text IS NULL OR n.nspname = $1)
|
|
11392
|
+
ORDER BY n.nspname, c.relname
|
|
11393
|
+
LIMIT $2
|
|
11394
|
+
`, [schema ?? null, limit]);
|
|
11395
|
+
const summaries = [];
|
|
11396
|
+
for (const row of result.rows) {
|
|
11397
|
+
const summary = {
|
|
11398
|
+
schema: String(row.schema),
|
|
11399
|
+
name: String(row.name),
|
|
11400
|
+
schemaTable: buildSchemaTable(String(row.schema), String(row.name)),
|
|
11401
|
+
kind: String(row.kind),
|
|
11402
|
+
estimatedRows: row.estimated_rows === null || row.estimated_rows === undefined
|
|
11403
|
+
? null
|
|
11404
|
+
: Number(row.estimated_rows),
|
|
11405
|
+
columnCount: Number(row.column_count ?? 0),
|
|
11406
|
+
rlsEnabled: Boolean(row.rls_enabled),
|
|
11407
|
+
};
|
|
11408
|
+
if (summary.kind === "table" || summary.kind === "partitioned_table") {
|
|
11409
|
+
summary.rowCount = summary.estimatedRows ?? null;
|
|
11410
|
+
summary.rowCountSource = "estimated";
|
|
11411
|
+
}
|
|
11412
|
+
else {
|
|
11413
|
+
summary.rowCount = null;
|
|
11414
|
+
summary.rowCountSource = "not_applicable";
|
|
11415
|
+
}
|
|
11416
|
+
summaries.push(summary);
|
|
11417
|
+
}
|
|
11418
|
+
return summaries;
|
|
11419
|
+
}
|
|
11420
|
+
async function readSchemaInfo(client, schema, table) {
|
|
11421
|
+
const objectCheck = await client.query(`
|
|
11422
|
+
SELECT
|
|
11423
|
+
CASE c.relkind
|
|
11424
|
+
WHEN 'r' THEN 'table'
|
|
11425
|
+
WHEN 'p' THEN 'partitioned_table'
|
|
11426
|
+
WHEN 'v' THEN 'view'
|
|
11427
|
+
WHEN 'm' THEN 'materialized_view'
|
|
11428
|
+
WHEN 'f' THEN 'foreign_table'
|
|
11429
|
+
ELSE c.relkind::text
|
|
11430
|
+
END AS kind,
|
|
11431
|
+
c.relrowsecurity AS row_security_enabled,
|
|
11432
|
+
c.relforcerowsecurity AS force_row_security
|
|
11433
|
+
FROM pg_class c
|
|
11434
|
+
INNER JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
11435
|
+
WHERE n.nspname = $1 AND c.relname = $2
|
|
11436
|
+
LIMIT 1
|
|
11437
|
+
`, [schema, table]);
|
|
11438
|
+
if (!objectCheck.rows[0]) {
|
|
11439
|
+
return null;
|
|
11440
|
+
}
|
|
11441
|
+
const columnsResult = await client.query(`
|
|
11442
|
+
SELECT
|
|
11443
|
+
column_name,
|
|
11444
|
+
data_type,
|
|
11445
|
+
udt_name,
|
|
11446
|
+
is_nullable,
|
|
11447
|
+
column_default,
|
|
11448
|
+
character_maximum_length,
|
|
11449
|
+
numeric_precision,
|
|
11450
|
+
numeric_scale
|
|
11451
|
+
FROM information_schema.columns
|
|
11452
|
+
WHERE table_schema = $1 AND table_name = $2
|
|
11453
|
+
ORDER BY ordinal_position
|
|
11454
|
+
`, [schema, table]);
|
|
11455
|
+
const primaryKeyResult = await client.query(`
|
|
11456
|
+
SELECT kcu.column_name
|
|
11457
|
+
FROM information_schema.table_constraints tc
|
|
11458
|
+
INNER JOIN information_schema.key_column_usage kcu
|
|
11459
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
11460
|
+
AND tc.table_schema = kcu.table_schema
|
|
11461
|
+
AND tc.table_name = kcu.table_name
|
|
11462
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
11463
|
+
AND tc.table_schema = $1
|
|
11464
|
+
AND tc.table_name = $2
|
|
11465
|
+
ORDER BY kcu.ordinal_position
|
|
11466
|
+
`, [schema, table]);
|
|
11467
|
+
const foreignKeyResult = await client.query(`
|
|
11468
|
+
SELECT
|
|
11469
|
+
tc.constraint_name,
|
|
11470
|
+
kcu.column_name,
|
|
11471
|
+
ccu.table_schema AS foreign_table_schema,
|
|
11472
|
+
ccu.table_name AS foreign_table_name,
|
|
11473
|
+
ccu.column_name AS foreign_column_name
|
|
11474
|
+
FROM information_schema.table_constraints tc
|
|
11475
|
+
INNER JOIN information_schema.key_column_usage kcu
|
|
11476
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
11477
|
+
AND tc.table_schema = kcu.table_schema
|
|
11478
|
+
AND tc.table_name = kcu.table_name
|
|
11479
|
+
INNER JOIN information_schema.constraint_column_usage ccu
|
|
11480
|
+
ON tc.constraint_name = ccu.constraint_name
|
|
11481
|
+
AND tc.table_schema = ccu.table_schema
|
|
11482
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
11483
|
+
AND tc.table_schema = $1
|
|
11484
|
+
AND tc.table_name = $2
|
|
11485
|
+
ORDER BY tc.constraint_name, kcu.ordinal_position
|
|
11486
|
+
`, [schema, table]);
|
|
11487
|
+
const indexesResult = await client.query(`
|
|
11488
|
+
SELECT indexname, indexdef
|
|
11489
|
+
FROM pg_indexes
|
|
11490
|
+
WHERE schemaname = $1 AND tablename = $2
|
|
11491
|
+
ORDER BY indexname
|
|
11492
|
+
`, [schema, table]);
|
|
11493
|
+
const policiesResult = await client.query(`
|
|
11494
|
+
SELECT policyname, permissive, roles, cmd, qual, with_check
|
|
11495
|
+
FROM pg_policies
|
|
11496
|
+
WHERE schemaname = $1 AND tablename = $2
|
|
11497
|
+
ORDER BY policyname
|
|
11498
|
+
`, [schema, table]);
|
|
11499
|
+
let rowCount = null;
|
|
11500
|
+
try {
|
|
11501
|
+
rowCount = await getTableRowCount(client, schema, table);
|
|
11502
|
+
}
|
|
11503
|
+
catch {
|
|
11504
|
+
rowCount = null;
|
|
11505
|
+
}
|
|
11506
|
+
const columns = columnsResult.rows.map((row) => ({
|
|
11507
|
+
name: String(row.column_name),
|
|
11508
|
+
dataType: String(row.data_type),
|
|
11509
|
+
isNullable: String(row.is_nullable).toUpperCase() === "YES",
|
|
11510
|
+
defaultValue: row.column_default === undefined
|
|
11511
|
+
? null
|
|
11512
|
+
: truncateText(row.column_default, 120),
|
|
11513
|
+
}));
|
|
11514
|
+
return {
|
|
11515
|
+
schemaTable: buildSchemaTable(schema, table),
|
|
11516
|
+
kind: String(objectCheck.rows[0].kind),
|
|
11517
|
+
rowCount,
|
|
11518
|
+
columns,
|
|
11519
|
+
primaryKey: primaryKeyResult.rows.map((row) => String(row.column_name)),
|
|
11520
|
+
foreignKeys: foreignKeyResult.rows.map((row) => ({
|
|
11521
|
+
constraintName: String(row.constraint_name),
|
|
11522
|
+
columnName: String(row.column_name),
|
|
11523
|
+
references: buildSchemaTable(String(row.foreign_table_schema), String(row.foreign_table_name)),
|
|
11524
|
+
referencedColumn: String(row.foreign_column_name),
|
|
11525
|
+
})),
|
|
11526
|
+
indexes: indexesResult.rows.map((row) => ({
|
|
11527
|
+
name: String(row.indexname),
|
|
11528
|
+
definition: truncateText(row.indexdef, 180),
|
|
11529
|
+
})),
|
|
11530
|
+
security: {
|
|
11531
|
+
rowLevelSecurityEnabled: Boolean(objectCheck.rows[0].row_security_enabled),
|
|
11532
|
+
forceRowLevelSecurity: Boolean(objectCheck.rows[0].force_row_security),
|
|
11533
|
+
policies: policiesResult.rows.map((row) => ({
|
|
11534
|
+
name: String(row.policyname),
|
|
11535
|
+
permissive: String(row.permissive),
|
|
11536
|
+
roles: Array.isArray(row.roles)
|
|
11537
|
+
? row.roles.map((role) => String(role))
|
|
11538
|
+
: [],
|
|
11539
|
+
command: String(row.cmd),
|
|
11540
|
+
using: truncateText(row.qual, 180),
|
|
11541
|
+
withCheck: truncateText(row.with_check, 180),
|
|
11542
|
+
})),
|
|
11543
|
+
},
|
|
11544
|
+
};
|
|
11545
|
+
}
|
|
11546
|
+
function createDefaultDependencies(server) {
|
|
11547
|
+
return {
|
|
11548
|
+
createClient: async (context) => createManagerPgClient(context, server?.cloudBaseOptions),
|
|
11549
|
+
};
|
|
11550
|
+
}
|
|
11551
|
+
function isExecutePGSqlDatabase(value) {
|
|
11552
|
+
return Boolean(value &&
|
|
11553
|
+
typeof value === "object" &&
|
|
11554
|
+
"executePGSql" in value &&
|
|
11555
|
+
typeof value.executePGSql === "function");
|
|
11556
|
+
}
|
|
11557
|
+
function isCloudBaseWithCommonService(value) {
|
|
11558
|
+
return Boolean(value &&
|
|
11559
|
+
typeof value === "object" &&
|
|
11560
|
+
"commonService" in value &&
|
|
11561
|
+
typeof value.commonService === "function");
|
|
11562
|
+
}
|
|
11563
|
+
async function executeManagerPGSql(context, sql, cloudBaseOptions) {
|
|
11564
|
+
const manager = await (0, cloudbase_manager_js_1.getCloudBaseManager)({
|
|
11565
|
+
cloudBaseOptions: cloudBaseOptions
|
|
11566
|
+
? {
|
|
11567
|
+
...cloudBaseOptions,
|
|
11568
|
+
envId: context.envId,
|
|
11569
|
+
}
|
|
11570
|
+
: {
|
|
11571
|
+
envId: context.envId,
|
|
11572
|
+
},
|
|
11573
|
+
});
|
|
11574
|
+
const options = {
|
|
11575
|
+
Sql: sql,
|
|
11576
|
+
Role: context.role,
|
|
11577
|
+
EnvId: context.envId,
|
|
11578
|
+
};
|
|
11579
|
+
if (isExecutePGSqlDatabase(manager.database)) {
|
|
11580
|
+
return manager.database.executePGSql(options);
|
|
11581
|
+
}
|
|
11582
|
+
if (isCloudBaseWithCommonService(manager)) {
|
|
11583
|
+
const result = await manager.commonService("tcb", "2018-06-08").call({
|
|
11584
|
+
Action: "ExecutePGSql",
|
|
11585
|
+
Param: options,
|
|
11586
|
+
});
|
|
11587
|
+
return "Response" in result && result.Response ? result.Response : result;
|
|
11588
|
+
}
|
|
11589
|
+
throw new Error("Current @cloudbase/manager-node runtime does not expose database.executePGSql or commonService fallback. Upgrade to @cloudbase/manager-node >= 5.4.0.");
|
|
11590
|
+
}
|
|
11591
|
+
/** Call a CloudBase PG migration API via commonService fallback */
|
|
11592
|
+
async function callPgMigrationApi(context, action, params, cloudBaseOptions) {
|
|
11593
|
+
const manager = await (0, cloudbase_manager_js_1.getCloudBaseManager)({
|
|
11594
|
+
cloudBaseOptions: cloudBaseOptions
|
|
11595
|
+
? { ...cloudBaseOptions, envId: context.envId }
|
|
11596
|
+
: { envId: context.envId },
|
|
11597
|
+
});
|
|
11598
|
+
if (!isCloudBaseWithCommonService(manager)) {
|
|
11599
|
+
throw new Error("Current @cloudbase/manager-node runtime does not support migration APIs. Upgrade to @cloudbase/manager-node >= 5.4.0.");
|
|
11600
|
+
}
|
|
11601
|
+
const result = await manager.commonService("tcb", "2018-06-08").call({
|
|
11602
|
+
Action: action,
|
|
11603
|
+
Param: { EnvId: context.envId, ...params },
|
|
11604
|
+
});
|
|
11605
|
+
return "Response" in result && result.Response
|
|
11606
|
+
? result.Response
|
|
11607
|
+
: result;
|
|
11608
|
+
}
|
|
11609
|
+
function parseManagerRows(result) {
|
|
11610
|
+
const columns = result.Columns ?? [];
|
|
11611
|
+
return (result.Rows ?? []).map((rowText) => {
|
|
11612
|
+
let values;
|
|
11613
|
+
try {
|
|
11614
|
+
values = JSON.parse(rowText);
|
|
11615
|
+
}
|
|
11616
|
+
catch {
|
|
11617
|
+
values = [];
|
|
11618
|
+
}
|
|
11619
|
+
const arrayValues = Array.isArray(values) ? values : [];
|
|
11620
|
+
return Object.fromEntries(columns.map((column, index) => [column, arrayValues[index] ?? null]));
|
|
11621
|
+
});
|
|
11622
|
+
}
|
|
11623
|
+
function renderPgLiteral(value) {
|
|
11624
|
+
if (value === null || value === undefined) {
|
|
11625
|
+
return "NULL";
|
|
11626
|
+
}
|
|
11627
|
+
if (typeof value === "number") {
|
|
11628
|
+
return Number.isFinite(value) ? String(value) : "NULL";
|
|
11629
|
+
}
|
|
11630
|
+
if (typeof value === "boolean") {
|
|
11631
|
+
return value ? "TRUE" : "FALSE";
|
|
11632
|
+
}
|
|
11633
|
+
if (value instanceof Date) {
|
|
11634
|
+
return `'${value.toISOString().replace(/'/g, "''")}'`;
|
|
11635
|
+
}
|
|
11636
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
11637
|
+
}
|
|
11638
|
+
function renderParameterizedSql(sql, values) {
|
|
11639
|
+
if (!values || values.length === 0) {
|
|
11640
|
+
return sql;
|
|
11641
|
+
}
|
|
11642
|
+
return sql.replace(/\$(\d+)/g, (placeholder, indexText) => {
|
|
11643
|
+
const index = Number(indexText);
|
|
11644
|
+
if (!Number.isInteger(index) || index < 1 || index > values.length) {
|
|
11645
|
+
return placeholder;
|
|
11646
|
+
}
|
|
11647
|
+
return renderPgLiteral(values[index - 1]);
|
|
11648
|
+
});
|
|
11649
|
+
}
|
|
11650
|
+
function inferCommand(sql) {
|
|
11651
|
+
return getSqlVerb(sql) || undefined;
|
|
11652
|
+
}
|
|
11653
|
+
function createManagerPgClient(context, cloudBaseOptions) {
|
|
11654
|
+
return {
|
|
11655
|
+
async connect() {
|
|
11656
|
+
return undefined;
|
|
11657
|
+
},
|
|
11658
|
+
async query(sql, values) {
|
|
11659
|
+
const renderedSql = renderParameterizedSql(sql, values);
|
|
11660
|
+
const result = await executeManagerPGSql(context, renderedSql, cloudBaseOptions);
|
|
11661
|
+
const rows = parseManagerRows(result);
|
|
11662
|
+
return {
|
|
11663
|
+
rows,
|
|
11664
|
+
rowCount: result.AffectedRows ?? rows.length,
|
|
11665
|
+
command: inferCommand(renderedSql),
|
|
11666
|
+
fields: result.Columns?.map((name) => ({ name })) ?? [],
|
|
11667
|
+
};
|
|
11668
|
+
},
|
|
11669
|
+
async end() {
|
|
11670
|
+
return undefined;
|
|
11671
|
+
},
|
|
11672
|
+
};
|
|
11673
|
+
}
|
|
11674
|
+
function sleep(ms) {
|
|
11675
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11676
|
+
}
|
|
11677
|
+
async function ensurePgReady(context, deps, options) {
|
|
11678
|
+
const maxAttempts = options?.maxAttempts ?? 20;
|
|
11679
|
+
const retryDelayMs = options?.retryDelayMs ?? 1000;
|
|
11680
|
+
let lastError;
|
|
11681
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
11682
|
+
try {
|
|
11683
|
+
await withPgClient(context, deps, async (client) => {
|
|
11684
|
+
await client.query("SELECT 1");
|
|
11685
|
+
});
|
|
11686
|
+
return;
|
|
11687
|
+
}
|
|
11688
|
+
catch (error) {
|
|
11689
|
+
lastError = error;
|
|
11690
|
+
if (attempt === maxAttempts) {
|
|
11691
|
+
break;
|
|
11692
|
+
}
|
|
11693
|
+
await sleep(retryDelayMs);
|
|
11694
|
+
}
|
|
11695
|
+
}
|
|
11696
|
+
const reason = lastError instanceof Error ? lastError.message : String(lastError);
|
|
11697
|
+
throw new Error(`PostgreSQL is not ready after ${maxAttempts} attempts. Last error: ${reason}`);
|
|
11698
|
+
}
|
|
11699
|
+
async function handleInit(args, server, deps) {
|
|
11700
|
+
const existing = await getPgContext(server);
|
|
11701
|
+
const resolvedEnvId = args.envId ??
|
|
11702
|
+
server.cloudBaseOptions?.envId ??
|
|
11703
|
+
process.env.CLOUDBASE_ENV_ID ??
|
|
11704
|
+
existing?.envId;
|
|
11705
|
+
if (!resolvedEnvId) {
|
|
11706
|
+
return buildPgToolResult({
|
|
11707
|
+
success: false,
|
|
11708
|
+
errorCode: "ENV_ID_REQUIRED",
|
|
11709
|
+
message: "CloudBase PostgreSQL only supports the built-in cloud runtime. Provide envId or bind a CloudBase environment before calling managePgDatabase(action=init).",
|
|
11710
|
+
});
|
|
11711
|
+
}
|
|
11712
|
+
const bootstrapResult = {
|
|
11713
|
+
instanceId: args.instanceId ?? "cloudbase-pg",
|
|
11714
|
+
defaultSchema: args.defaultSchema ?? "public",
|
|
11715
|
+
status: "ready",
|
|
11716
|
+
runtimeMode: "cloudbase-manager",
|
|
11717
|
+
role: args.role ?? "cloudbase_admin",
|
|
11718
|
+
};
|
|
11719
|
+
const now = new Date().toISOString();
|
|
11720
|
+
const context = {
|
|
11721
|
+
envId: resolvedEnvId,
|
|
11722
|
+
instanceId: bootstrapResult.instanceId,
|
|
11723
|
+
defaultSchema: bootstrapResult.defaultSchema,
|
|
11724
|
+
runtimeMode: "cloudbase-manager",
|
|
11725
|
+
bootstrapMode: "cloud",
|
|
11726
|
+
role: bootstrapResult.role,
|
|
11727
|
+
createdAt: existing?.createdAt ?? now,
|
|
11728
|
+
updatedAt: now,
|
|
11729
|
+
};
|
|
11730
|
+
await ensurePgReady(context, deps, deps.readyCheckOptions);
|
|
11731
|
+
await savePgContext(server, context);
|
|
11732
|
+
return buildPgToolResult({
|
|
11733
|
+
success: true,
|
|
11734
|
+
data: {
|
|
11735
|
+
context: {
|
|
11736
|
+
...sanitizePgContext(context),
|
|
11737
|
+
},
|
|
11738
|
+
status: bootstrapResult.status,
|
|
11739
|
+
},
|
|
11740
|
+
message: "CloudBase PG 云端执行上下文已就绪。先查询对象或 schema,再执行写入 SQL。",
|
|
11741
|
+
nextActions: [
|
|
11742
|
+
buildNextAction(QUERY_PG_DATABASE, "objects", "List schema-qualified database objects before drilling into a specific table.", { action: "objects", limit: 20 }),
|
|
11743
|
+
buildSchemaNextAction("Inspect a concrete schema-qualified table after you know which object matters.", `${context.defaultSchema}.your_table`),
|
|
11744
|
+
],
|
|
11745
|
+
});
|
|
11746
|
+
}
|
|
11747
|
+
async function handleQueryContext(server) {
|
|
11748
|
+
const context = await getPgContext(server);
|
|
11749
|
+
if (!context) {
|
|
11750
|
+
return buildMissingContextResult();
|
|
11751
|
+
}
|
|
11752
|
+
return buildPgToolResult({
|
|
11753
|
+
success: true,
|
|
11754
|
+
data: {
|
|
11755
|
+
context: {
|
|
11756
|
+
...sanitizePgContext(context),
|
|
11757
|
+
},
|
|
11758
|
+
},
|
|
11759
|
+
message: "Resolved current CloudBase PostgreSQL context.",
|
|
11760
|
+
nextActions: [
|
|
11761
|
+
buildNextAction(QUERY_PG_DATABASE, "objects", "List schema-qualified objects before inspecting an individual table.", { action: "objects", limit: 20 }),
|
|
11762
|
+
],
|
|
11763
|
+
});
|
|
11764
|
+
}
|
|
11765
|
+
async function handleListObjects(args, context, deps) {
|
|
11766
|
+
const limit = normalizeLimit(args.limit);
|
|
11767
|
+
const objects = await withPgClient(context, deps, (client) => listObjects(client, args.schema, limit));
|
|
11768
|
+
return buildPgToolResult({
|
|
11769
|
+
success: true,
|
|
11770
|
+
data: {
|
|
11771
|
+
objects,
|
|
11772
|
+
schemaFilter: args.schema ?? null,
|
|
11773
|
+
limit,
|
|
11774
|
+
},
|
|
11775
|
+
message: objects.length > 0
|
|
11776
|
+
? `Listed ${objects.length} schema-qualified PostgreSQL objects. Inspect one object schema before writing SQL.`
|
|
11777
|
+
: "No PostgreSQL objects matched the current filter.",
|
|
11778
|
+
nextActions: objects.length > 0
|
|
11779
|
+
? [
|
|
11780
|
+
buildSchemaNextAction("Inspect the most relevant object schema before querying data.", objects[0].schemaTable),
|
|
11781
|
+
]
|
|
11782
|
+
: [
|
|
11783
|
+
buildNextAction(QUERY_PG_DATABASE, "context", "Re-check the current PG context if no objects were expectedly returned.", { action: "context" }),
|
|
11784
|
+
],
|
|
11785
|
+
});
|
|
11786
|
+
}
|
|
11787
|
+
async function handleMetadata(args, context, deps) {
|
|
11788
|
+
const limit = normalizeLimit(args.limit);
|
|
11789
|
+
const tables = await withPgClient(context, deps, (client) => summarizeMetadata(client, args.schema, limit));
|
|
11790
|
+
return buildPgToolResult({
|
|
11791
|
+
success: true,
|
|
11792
|
+
data: {
|
|
11793
|
+
tables,
|
|
11794
|
+
schemaFilter: args.schema ?? null,
|
|
11795
|
+
limit,
|
|
11796
|
+
},
|
|
11797
|
+
message: tables.length > 0
|
|
11798
|
+
? `Summarized ${tables.length} PostgreSQL objects with row-count and RLS hints. Use schema inspection before composing joins or mutations.`
|
|
11799
|
+
: "No PostgreSQL objects matched the current metadata filter.",
|
|
11800
|
+
nextActions: tables.length > 0
|
|
11801
|
+
? [
|
|
11802
|
+
buildSchemaNextAction("Inspect the relevant table schema to confirm columns, keys, and policies.", tables[0].schemaTable),
|
|
11803
|
+
]
|
|
11804
|
+
: [
|
|
11805
|
+
buildNextAction(QUERY_PG_DATABASE, "objects", "List objects first if metadata is empty or too restrictive.", { action: "objects", schema: args.schema, limit }),
|
|
11806
|
+
],
|
|
11807
|
+
});
|
|
11808
|
+
}
|
|
11809
|
+
async function handleReadOnlySql(args, context, deps) {
|
|
11810
|
+
if (!args.sql?.trim()) {
|
|
11811
|
+
return buildPgToolResult({
|
|
11812
|
+
success: false,
|
|
11813
|
+
errorCode: "SQL_REQUIRED",
|
|
11814
|
+
message: "Provide a read-only SQL statement when action=sql.",
|
|
11815
|
+
});
|
|
11816
|
+
}
|
|
11817
|
+
if (!isReadOnlySql(args.sql)) {
|
|
11818
|
+
return buildPgToolResult({
|
|
11819
|
+
success: false,
|
|
11820
|
+
errorCode: "READ_ONLY_SQL_REQUIRED",
|
|
11821
|
+
message: "queryPgDatabase(action=sql) only accepts read-only SQL. For DDL/DML (CREATE/ALTER/INSERT/UPDATE/DELETE/...), call managePgDatabase(action=execute) with confirm=true and the same SQL.",
|
|
11822
|
+
nextActions: [
|
|
11823
|
+
buildNextAction(MANAGE_PG_DATABASE, "execute", "Re-issue this statement via managePgDatabase(action=execute) with confirm=true.", { action: "execute", sql: args.sql, confirm: true }),
|
|
11824
|
+
buildSchemaNextAction("Inspect schema first if you are deciding which write operation is needed.", `${context.defaultSchema}.your_table`),
|
|
11825
|
+
],
|
|
11826
|
+
});
|
|
11827
|
+
}
|
|
11828
|
+
const limit = normalizeLimit(args.limit);
|
|
11829
|
+
const limitedSql = buildLimitedReadOnlySql(args.sql, limit);
|
|
11830
|
+
const result = await withPgClient(context, deps, (client) => client.query(limitedSql));
|
|
11831
|
+
const summary = summarizeQueryResult(result, limit);
|
|
11832
|
+
return buildPgToolResult({
|
|
11833
|
+
success: true,
|
|
11834
|
+
data: {
|
|
11835
|
+
...summary,
|
|
11836
|
+
command: result.command ?? "SELECT",
|
|
11837
|
+
rowCount: result.rowCount ?? summary.returnedRows,
|
|
11838
|
+
},
|
|
11839
|
+
message: summary.truncated
|
|
11840
|
+
? `Read-only SQL executed successfully. Showing ${summary.rows.length} of ${summary.returnedRows} rows to control token usage.`
|
|
11841
|
+
: "Read-only SQL executed successfully.",
|
|
11842
|
+
nextActions: [
|
|
11843
|
+
buildSchemaNextAction("Inspect the table schema if you need to refine joins, filters, or mutations.", parseTargetTableFromSql(args.sql, context.defaultSchema) ??
|
|
11844
|
+
`${context.defaultSchema}.your_table`),
|
|
11845
|
+
],
|
|
11846
|
+
});
|
|
11847
|
+
}
|
|
11848
|
+
async function handleExecuteSql(args, context, deps) {
|
|
11849
|
+
if (!args.sql?.trim()) {
|
|
11850
|
+
return buildPgToolResult({
|
|
11851
|
+
success: false,
|
|
11852
|
+
errorCode: "SQL_REQUIRED",
|
|
11853
|
+
message: "Provide a SQL statement when action=execute.",
|
|
11854
|
+
});
|
|
11855
|
+
}
|
|
11856
|
+
const classification = classifySqlRisk(args.sql);
|
|
11857
|
+
if (!classification.readOnly && !args.confirm) {
|
|
11858
|
+
return buildPgToolResult({
|
|
11859
|
+
success: false,
|
|
11860
|
+
errorCode: "CONFIRM_REQUIRED",
|
|
11861
|
+
message: `This SQL is classified as ${classification.risk}. Re-run managePgDatabase(action=execute) with the same sql and confirm=true to proceed.`,
|
|
11862
|
+
data: {
|
|
11863
|
+
classification,
|
|
11864
|
+
},
|
|
11865
|
+
nextActions: [
|
|
11866
|
+
buildNextAction(MANAGE_PG_DATABASE, "execute", "Re-issue with confirm=true to actually run the SQL.", { action: "execute", sql: args.sql, confirm: true }),
|
|
11867
|
+
buildNextAction(QUERY_PG_DATABASE, "metadata", "Inspect table size and shape before destructive changes.", { action: "metadata", limit: 20 }),
|
|
11868
|
+
],
|
|
11869
|
+
});
|
|
11870
|
+
}
|
|
11871
|
+
const result = await withPgClient(context, deps, (client) => client.query(args.sql));
|
|
11872
|
+
const targetTable = parseTargetTableFromSql(args.sql, context.defaultSchema);
|
|
11873
|
+
return buildPgToolResult({
|
|
11874
|
+
success: true,
|
|
11875
|
+
data: {
|
|
11876
|
+
classification,
|
|
11877
|
+
command: result.command ?? getSqlVerb(args.sql),
|
|
11878
|
+
rowCount: result.rowCount ?? null,
|
|
11879
|
+
previewRows: result.rows
|
|
11880
|
+
.slice(0, 5)
|
|
11881
|
+
.map((row) => serializeValue(row)),
|
|
11882
|
+
targetTable: targetTable ?? null,
|
|
11883
|
+
},
|
|
11884
|
+
message: "Write SQL executed successfully.",
|
|
11885
|
+
nextActions: classification.risk === "schema_change" && targetTable
|
|
11886
|
+
? [
|
|
11887
|
+
buildNextAction(QUERY_PG_DATABASE, "schema", "Inspect the table schema, columns, indexes, and RLS policies after schema changes.", {
|
|
11888
|
+
action: "schema",
|
|
11889
|
+
objectName: targetTable,
|
|
11890
|
+
}),
|
|
11891
|
+
]
|
|
11892
|
+
: [
|
|
11893
|
+
buildNextAction(QUERY_PG_DATABASE, "sql", "Verify the mutation with a focused read-only SQL query.", {
|
|
11894
|
+
action: "sql",
|
|
11895
|
+
sql: targetTable
|
|
11896
|
+
? `SELECT * FROM ${targetTable} LIMIT 20`
|
|
11897
|
+
: "SELECT 1",
|
|
11898
|
+
limit: 20,
|
|
11899
|
+
}),
|
|
11900
|
+
],
|
|
11901
|
+
});
|
|
11902
|
+
}
|
|
11903
|
+
async function handleDryRun(args) {
|
|
11904
|
+
if (!args.sql?.trim()) {
|
|
11905
|
+
return buildPgToolResult({
|
|
11906
|
+
success: false,
|
|
11907
|
+
errorCode: "SQL_REQUIRED",
|
|
11908
|
+
message: "Provide a SQL statement when action=dryRun.",
|
|
11909
|
+
});
|
|
11910
|
+
}
|
|
11911
|
+
const classification = classifySqlRisk(args.sql);
|
|
11912
|
+
return buildPgToolResult({
|
|
11913
|
+
success: true,
|
|
11914
|
+
data: {
|
|
11915
|
+
classification,
|
|
11916
|
+
wouldExecute: false,
|
|
11917
|
+
sqlPreview: args.sql.trim().slice(0, 500),
|
|
11918
|
+
},
|
|
11919
|
+
message: classification.readOnly
|
|
11920
|
+
? "SQL dry run completed. This statement is read-only; use queryPgDatabase(action=sql) to execute it."
|
|
11921
|
+
: "SQL dry run completed. Write SQL requires managePgDatabase(action=execute, confirm=true).",
|
|
11922
|
+
nextActions: [
|
|
11923
|
+
buildNextAction(classification.readOnly ? QUERY_PG_DATABASE : MANAGE_PG_DATABASE, classification.readOnly ? "sql" : "execute", classification.readOnly
|
|
11924
|
+
? "Execute the read-only SQL through queryPgDatabase."
|
|
11925
|
+
: "Execute the write SQL only after explicit confirmation.", classification.readOnly
|
|
11926
|
+
? { action: "sql", sql: args.sql, limit: 20 }
|
|
11927
|
+
: { action: "execute", sql: args.sql, confirm: true }),
|
|
11928
|
+
],
|
|
11929
|
+
});
|
|
11930
|
+
}
|
|
11931
|
+
async function handlePlanMigration(args, context, deps, cloudBaseOptions) {
|
|
11932
|
+
if (!args.sql?.trim()) {
|
|
11933
|
+
return buildPgToolResult({
|
|
11934
|
+
success: false,
|
|
11935
|
+
errorCode: "SQL_REQUIRED",
|
|
11936
|
+
message: "Provide migration SQL when action=planMigration.",
|
|
11937
|
+
});
|
|
11938
|
+
}
|
|
11939
|
+
try {
|
|
11940
|
+
const result = await callPgMigrationApi(context, "PreviewPGUserMigrations", {
|
|
11941
|
+
Sql: args.sql,
|
|
11942
|
+
}, cloudBaseOptions);
|
|
11943
|
+
return buildPgToolResult({
|
|
11944
|
+
success: true,
|
|
11945
|
+
data: result,
|
|
11946
|
+
message: "Migration plan generated via PreviewPGUserMigrations.",
|
|
11947
|
+
nextActions: [
|
|
11948
|
+
buildNextAction(MANAGE_PG_DATABASE, "applyMigration", "Review the plan above. If it looks correct, call applyMigration to execute.", { action: "applyMigration", sql: args.sql, confirm: true }),
|
|
11949
|
+
],
|
|
11950
|
+
});
|
|
11951
|
+
}
|
|
11952
|
+
catch (error) {
|
|
11953
|
+
return buildPgToolResult({
|
|
11954
|
+
success: false,
|
|
11955
|
+
errorCode: "MIGRATION_API_ERROR",
|
|
11956
|
+
message: `PreviewPGUserMigrations failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
11957
|
+
});
|
|
11958
|
+
}
|
|
11959
|
+
}
|
|
11960
|
+
async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
|
|
11961
|
+
if (!args.sql?.trim()) {
|
|
11962
|
+
return buildPgToolResult({
|
|
11963
|
+
success: false,
|
|
11964
|
+
errorCode: "SQL_REQUIRED",
|
|
11965
|
+
message: "Provide migration SQL when action=applyMigration.",
|
|
11966
|
+
});
|
|
11967
|
+
}
|
|
11968
|
+
if (args.confirm !== true) {
|
|
11969
|
+
return buildPgToolResult({
|
|
11970
|
+
success: false,
|
|
11971
|
+
errorCode: "CONFIRM_REQUIRED",
|
|
11972
|
+
message: "PushPGUserMigrations requires confirm=true. Run with confirm=true to proceed.",
|
|
11973
|
+
});
|
|
11974
|
+
}
|
|
11975
|
+
try {
|
|
11976
|
+
const result = await callPgMigrationApi(context, "PushPGUserMigrations", {
|
|
11977
|
+
Sql: args.sql,
|
|
11978
|
+
}, cloudBaseOptions);
|
|
11979
|
+
return buildPgToolResult({
|
|
11980
|
+
success: true,
|
|
11981
|
+
data: result,
|
|
11982
|
+
message: "Migrations applied via PushPGUserMigrations.",
|
|
11983
|
+
});
|
|
11984
|
+
}
|
|
11985
|
+
catch (error) {
|
|
11986
|
+
return buildPgToolResult({
|
|
11987
|
+
success: false,
|
|
11988
|
+
errorCode: "MIGRATION_API_ERROR",
|
|
11989
|
+
message: `PushPGUserMigrations failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
11990
|
+
});
|
|
11991
|
+
}
|
|
11992
|
+
}
|
|
11993
|
+
async function handleListMigrations(args, context, deps, cloudBaseOptions) {
|
|
11994
|
+
try {
|
|
11995
|
+
const result = await callPgMigrationApi(context, "ListPGUserMigrations", {}, cloudBaseOptions);
|
|
11996
|
+
return buildPgToolResult({
|
|
11997
|
+
success: true,
|
|
11998
|
+
data: result,
|
|
11999
|
+
message: "Migration list retrieved via ListPGUserMigrations.",
|
|
12000
|
+
});
|
|
12001
|
+
}
|
|
12002
|
+
catch (error) {
|
|
12003
|
+
return buildPgToolResult({
|
|
12004
|
+
success: false,
|
|
12005
|
+
errorCode: "MIGRATION_API_ERROR",
|
|
12006
|
+
message: `ListPGUserMigrations failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
12007
|
+
});
|
|
12008
|
+
}
|
|
12009
|
+
}
|
|
12010
|
+
async function handleMigrationDetail(args, context, deps, cloudBaseOptions) {
|
|
12011
|
+
if (!args.objectName?.trim()) {
|
|
12012
|
+
return buildPgToolResult({
|
|
12013
|
+
success: false,
|
|
12014
|
+
errorCode: "OBJECT_NAME_REQUIRED",
|
|
12015
|
+
message: "Provide objectName (migration ID) when action=migrationDetail.",
|
|
12016
|
+
});
|
|
12017
|
+
}
|
|
12018
|
+
try {
|
|
12019
|
+
const result = await callPgMigrationApi(context, "DescribePGUserMigration", {
|
|
12020
|
+
ObjectId: args.objectName,
|
|
12021
|
+
}, cloudBaseOptions);
|
|
12022
|
+
return buildPgToolResult({
|
|
12023
|
+
success: true,
|
|
12024
|
+
data: result,
|
|
12025
|
+
message: "Migration detail retrieved via DescribePGUserMigration.",
|
|
12026
|
+
});
|
|
12027
|
+
}
|
|
12028
|
+
catch (error) {
|
|
12029
|
+
return buildPgToolResult({
|
|
12030
|
+
success: false,
|
|
12031
|
+
errorCode: "MIGRATION_API_ERROR",
|
|
12032
|
+
message: `DescribePGUserMigration failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
12033
|
+
});
|
|
12034
|
+
}
|
|
12035
|
+
}
|
|
12036
|
+
async function handleRollbackMigration(args, context, deps, cloudBaseOptions) {
|
|
12037
|
+
if (!args.objectName?.trim()) {
|
|
12038
|
+
return buildPgToolResult({
|
|
12039
|
+
success: false,
|
|
12040
|
+
errorCode: "OBJECT_NAME_REQUIRED",
|
|
12041
|
+
message: "Provide objectName (migration ID) when action=rollbackMigration.",
|
|
12042
|
+
});
|
|
12043
|
+
}
|
|
12044
|
+
if (args.confirm !== true) {
|
|
12045
|
+
return buildPgToolResult({
|
|
12046
|
+
success: false,
|
|
12047
|
+
errorCode: "CONFIRM_REQUIRED",
|
|
12048
|
+
message: "RollbackPGUserMigrations requires confirm=true. Run with confirm=true to proceed.",
|
|
12049
|
+
});
|
|
12050
|
+
}
|
|
12051
|
+
try {
|
|
12052
|
+
const result = await callPgMigrationApi(context, "RollbackPGUserMigrations", {
|
|
12053
|
+
ObjectId: args.objectName,
|
|
12054
|
+
}, cloudBaseOptions);
|
|
12055
|
+
return buildPgToolResult({
|
|
12056
|
+
success: true,
|
|
12057
|
+
data: result,
|
|
12058
|
+
message: "Migration rolled back via RollbackPGUserMigrations.",
|
|
12059
|
+
});
|
|
12060
|
+
}
|
|
12061
|
+
catch (error) {
|
|
12062
|
+
return buildPgToolResult({
|
|
12063
|
+
success: false,
|
|
12064
|
+
errorCode: "MIGRATION_API_ERROR",
|
|
12065
|
+
message: `RollbackPGUserMigrations failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
12066
|
+
});
|
|
12067
|
+
}
|
|
12068
|
+
}
|
|
12069
|
+
async function handleGetPgSchema(args, context, deps) {
|
|
12070
|
+
const objectName = args.objectName ?? "";
|
|
12071
|
+
const parsed = parseSchemaQualifiedName(objectName);
|
|
12072
|
+
if (!parsed) {
|
|
12073
|
+
return buildPgToolResult({
|
|
12074
|
+
success: false,
|
|
12075
|
+
errorCode: "SCHEMA_QUALIFIED_NAME_REQUIRED",
|
|
12076
|
+
message: "queryPgDatabase(action=schema) requires a schema-qualified object name like public.users.",
|
|
12077
|
+
});
|
|
12078
|
+
}
|
|
12079
|
+
const schemaInfo = await withPgClient(context, deps, (client) => readSchemaInfo(client, parsed.schema, parsed.name));
|
|
12080
|
+
if (!schemaInfo) {
|
|
12081
|
+
return buildPgToolResult({
|
|
12082
|
+
success: false,
|
|
12083
|
+
errorCode: "OBJECT_NOT_FOUND",
|
|
12084
|
+
message: `PostgreSQL object ${objectName} was not found in the current CloudBase PG context.`,
|
|
12085
|
+
nextActions: [
|
|
12086
|
+
buildNextAction(QUERY_PG_DATABASE, "objects", "List schema-qualified objects to find the correct table or view name.", { action: "objects", schema: parsed.schema, limit: 20 }),
|
|
12087
|
+
],
|
|
12088
|
+
});
|
|
12089
|
+
}
|
|
12090
|
+
const security = schemaInfo.security;
|
|
12091
|
+
const rlsWithoutPolicies = security.rowLevelSecurityEnabled && security.policies.length === 0;
|
|
12092
|
+
return buildPgToolResult({
|
|
12093
|
+
success: true,
|
|
12094
|
+
data: schemaInfo,
|
|
12095
|
+
message: rlsWithoutPolicies
|
|
12096
|
+
? "Resolved PostgreSQL schema, key, index, and security details. WARNING: RLS is enabled but no policies were found; browser/client reads and writes will be denied until policies are created or RLS is disabled."
|
|
12097
|
+
: "Resolved PostgreSQL schema, key, index, and security details. Use this structure before composing multi-table SQL.",
|
|
12098
|
+
nextActions: [
|
|
12099
|
+
...(rlsWithoutPolicies
|
|
12100
|
+
? [
|
|
12101
|
+
buildNextAction(MANAGE_PG_DATABASE, "execute", "Create SELECT/INSERT/UPDATE/DELETE RLS policies or disable RLS before browser-side app.rdb() CRUD.", {
|
|
12102
|
+
action: "execute",
|
|
12103
|
+
sql: `-- Example: CREATE POLICY ... ON ${objectName} FOR SELECT USING (true);`,
|
|
12104
|
+
confirm: true,
|
|
12105
|
+
}),
|
|
12106
|
+
]
|
|
12107
|
+
: []),
|
|
12108
|
+
buildNextAction(QUERY_PG_DATABASE, "metadata", "Check row-count and RLS hints for nearby tables before more complex queries.", { action: "metadata", schema: parsed.schema, limit: 20 }),
|
|
12109
|
+
buildNextAction(QUERY_PG_DATABASE, "sql", "Run a focused read-only query now that the schema is known.", {
|
|
12110
|
+
action: "sql",
|
|
12111
|
+
sql: `SELECT * FROM ${objectName} LIMIT 20`,
|
|
12112
|
+
limit: 20,
|
|
12113
|
+
}),
|
|
12114
|
+
],
|
|
12115
|
+
});
|
|
12116
|
+
}
|
|
12117
|
+
function registerPGDatabaseTools(server, providedDeps) {
|
|
12118
|
+
const deps = {
|
|
12119
|
+
...createDefaultDependencies(server),
|
|
12120
|
+
...providedDeps,
|
|
12121
|
+
};
|
|
12122
|
+
server.registerTool?.(QUERY_PG_DATABASE, {
|
|
12123
|
+
title: "查询 PostgreSQL 上下文、对象、元数据或执行只读 SQL",
|
|
12124
|
+
description: "查询 CloudBase PostgreSQL 数据库。支持获取当前 PG 上下文、列出带 schema 的数据库对象、读取轻量元数据、检查单个对象结构,以及执行只读 SQL。",
|
|
10558
12125
|
inputSchema: {
|
|
10559
12126
|
action: zod_1.z
|
|
10560
|
-
.enum(
|
|
10561
|
-
.describe("
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
"\n- `listFunctionLogs`: 查询 CloudBase 云函数执行日志(需要 functionName)" +
|
|
10565
|
-
"\n- `getFunctionLogDetail`: 获取日志详情(需要 requestId)" +
|
|
10566
|
-
"\n- `listFunctionLayers`: 列出函数绑定的层" +
|
|
10567
|
-
"\n- `listLayers`: 列出所有层" +
|
|
10568
|
-
"\n- `listLayerVersions`: 列出层的版本(注意:是 Versions 不是 Version)" +
|
|
10569
|
-
"\n- `getLayerVersionDetail`: 获取层版本详情" +
|
|
10570
|
-
"\n- `listFunctionTriggers`: 列出函数触发器(用于查看定时任务 / cron / timer 配置)" +
|
|
10571
|
-
"\n- `getFunctionDownloadUrl`: 获取函数代码下载地址"),
|
|
10572
|
-
functionName: zod_1.z
|
|
10573
|
-
.string()
|
|
10574
|
-
.optional()
|
|
10575
|
-
.describe("CloudBase 云函数名称。`getFunctionDetail`、`listFunctionLogs`、`listFunctionLayers`、`listFunctionTriggers`、`getFunctionDownloadUrl` 时必填"),
|
|
10576
|
-
limit: zod_1.z.number().optional().describe("分页数量(limit)。列表类 action 可选,默认值由后端决定"),
|
|
10577
|
-
offset: zod_1.z.number().optional().describe("分页偏移(offset)。列表类 action 可选,默认 0"),
|
|
10578
|
-
codeSecret: zod_1.z.string().optional().describe("代码保护密钥,用于解密函数代码"),
|
|
10579
|
-
startTime: zod_1.z
|
|
12127
|
+
.enum(QUERY_ACTIONS)
|
|
12128
|
+
.describe("操作类型:context=获取当前 PostgreSQL 上下文;objects=列出带 schema 的数据库对象;metadata=获取轻量表元数据;schema=检查单个带 schema 的对象结构;sql=执行只读 SQL"),
|
|
12129
|
+
sql: zod_1.z.string().optional().describe("action=sql 时使用的只读 SQL"),
|
|
12130
|
+
objectName: zod_1.z
|
|
10580
12131
|
.string()
|
|
10581
12132
|
.optional()
|
|
10582
|
-
.describe("
|
|
10583
|
-
|
|
10584
|
-
endTime: zod_1.z
|
|
12133
|
+
.describe("action=schema 时使用的带 schema 的 PostgreSQL 对象名,例如 public.users"),
|
|
12134
|
+
schema: zod_1.z
|
|
10585
12135
|
.string()
|
|
10586
12136
|
.optional()
|
|
10587
|
-
.describe("
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
.
|
|
12137
|
+
.describe("可选的 schema 过滤条件,用于 action=objects 或 action=metadata"),
|
|
12138
|
+
limit: zod_1.z
|
|
12139
|
+
.number()
|
|
12140
|
+
.int()
|
|
12141
|
+
.min(1)
|
|
12142
|
+
.max(200)
|
|
10591
12143
|
.optional()
|
|
10592
|
-
.describe("
|
|
10593
|
-
qualifier: zod_1.z.string().optional().describe("函数版本别名,如 $LATEST、$DEFAULT。日志查询时可选"),
|
|
10594
|
-
runtime: zod_1.z.string().optional().describe("层查询的运行时筛选,如 Nodejs18.15"),
|
|
10595
|
-
searchKey: zod_1.z.string().optional().describe("层名称搜索关键字"),
|
|
10596
|
-
layerName: zod_1.z.string().optional().describe("层名称。`listLayerVersions`、`getLayerVersionDetail` 操作必填"),
|
|
10597
|
-
layerVersion: zod_1.z.number().optional().describe("层版本号。`getLayerVersionDetail` 操作必填"),
|
|
12144
|
+
.describe("可选的摘要数量上限,用于对象、元数据或 SQL 返回行数,默认 20,最大 200。"),
|
|
10598
12145
|
},
|
|
10599
12146
|
annotations: {
|
|
10600
12147
|
readOnlyHint: true,
|
|
10601
12148
|
openWorldHint: true,
|
|
10602
|
-
category:
|
|
12149
|
+
category: CATEGORY,
|
|
10603
12150
|
},
|
|
10604
|
-
}, async (
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
12151
|
+
}, async (args) => {
|
|
12152
|
+
if (args.action === "context") {
|
|
12153
|
+
return handleQueryContext(server);
|
|
12154
|
+
}
|
|
12155
|
+
const context = await getPgContext(server);
|
|
12156
|
+
if (!context) {
|
|
12157
|
+
return buildMissingContextResult();
|
|
12158
|
+
}
|
|
12159
|
+
switch (args.action) {
|
|
12160
|
+
case "objects":
|
|
12161
|
+
return handleListObjects(args, context, deps);
|
|
12162
|
+
case "metadata":
|
|
12163
|
+
return handleMetadata(args, context, deps);
|
|
12164
|
+
case "schema":
|
|
12165
|
+
return handleGetPgSchema(args, context, deps);
|
|
12166
|
+
case "sql":
|
|
12167
|
+
return handleReadOnlySql(args, context, deps);
|
|
12168
|
+
default:
|
|
12169
|
+
return buildPgToolResult({
|
|
12170
|
+
success: false,
|
|
12171
|
+
errorCode: "UNSUPPORTED_ACTION",
|
|
12172
|
+
message: `Unsupported PostgreSQL query action: ${args.action}`,
|
|
12173
|
+
});
|
|
12174
|
+
}
|
|
12175
|
+
});
|
|
12176
|
+
server.registerTool?.(MANAGE_PG_DATABASE, {
|
|
12177
|
+
title: "管理 PostgreSQL 上下文或执行写入 SQL",
|
|
12178
|
+
description: "管理 CloudBase PostgreSQL MCP 上下文。仅支持 CloudBase 内置 PostgreSQL 云端 Manager SDK 执行路径、SQL 风险预检、迁移规划,以及在显式确认后执行写入 SQL。",
|
|
10610
12179
|
inputSchema: {
|
|
10611
12180
|
action: zod_1.z
|
|
10612
|
-
.enum(
|
|
10613
|
-
.describe("
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
functionRootPath: zod_1.z.string().optional().describe("创建或更新函数代码时默认推荐的本地目录方式。" +
|
|
10617
|
-
"必须是直接包含函数文件夹的目录绝对路径(如 /abs/path/cloudfunctions 或 /abs/path/functions)," +
|
|
10618
|
-
"不要传项目根目录(如 /abs/path),也不要传到函数名子目录(如 /abs/path/cloudfunctions/hello)。" +
|
|
10619
|
-
"本地应按 cloudfunctions/<functionName>/index.js 或 functions/<functionName>/index.js 布局," +
|
|
10620
|
-
"此参数传 cloudfunctions 或 functions 目录的绝对路径。" +
|
|
10621
|
-
"SDK 会自动拼接函数名子目录,无需预先压缩 zip 或 base64 编码。"),
|
|
10622
|
-
force: zod_1.z.boolean().optional().describe("createFunction 时是否覆盖"),
|
|
10623
|
-
functionName: zod_1.z.string().optional().describe("函数名称。大多数 action 使用该字段作为统一目标"),
|
|
10624
|
-
zipFile: zod_1.z.string().optional().describe("仅兼容特殊场景:预先准备好的代码包 base64 编码。普通 createFunction/updateFunctionCode 默认不要先压缩 zip,优先使用 functionRootPath。"),
|
|
10625
|
-
handler: zod_1.z.string().optional().describe("函数入口"),
|
|
10626
|
-
timeout: zod_1.z.number().optional().describe("配置更新时的超时时间"),
|
|
10627
|
-
envVariables: zod_1.z.record(zod_1.z.string()).optional().describe("配置更新时要合并的环境变量"),
|
|
10628
|
-
vpc: VPC_SCHEMA.optional().describe("配置更新时的 VPC 信息"),
|
|
10629
|
-
params: zod_1.z.record(zod_1.z.any()).optional().describe("invokeFunction 的调用参数"),
|
|
10630
|
-
triggers: zod_1.z
|
|
10631
|
-
.array(TRIGGER_SCHEMA)
|
|
12181
|
+
.enum(MANAGE_ACTIONS)
|
|
12182
|
+
.describe("操作类型:init=初始化或绑定 PostgreSQL 上下文;execute=执行已确认的写入 SQL 或 DDL;dryRun=只分析 SQL 风险不执行;planMigration=通过 PreviewPGUserMigrations 预览迁移计划;applyMigration=通过 PushPGUserMigrations 应用迁移;listMigrations=查询已应用的 Migration 列表;migrationDetail=查看单条 Migration 详情;rollbackMigration=回滚指定 Migration"),
|
|
12183
|
+
sql: zod_1.z
|
|
12184
|
+
.string()
|
|
10632
12185
|
.optional()
|
|
10633
|
-
.describe("
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
layerName: zod_1.z.string().optional().describe("层名称"),
|
|
10637
|
-
layerVersion: zod_1.z.number().optional().describe("层版本号"),
|
|
10638
|
-
contentPath: zod_1.z.string().optional().describe("层内容路径,可为目录或 ZIP 文件"),
|
|
10639
|
-
base64Content: zod_1.z.string().optional().describe("层内容的 base64 编码"),
|
|
10640
|
-
runtimes: zod_1.z.array(zod_1.z.string()).optional().describe("层适用的运行时列表"),
|
|
10641
|
-
description: zod_1.z.string().optional().describe("层版本描述"),
|
|
10642
|
-
licenseInfo: zod_1.z.string().optional().describe("层许可证信息"),
|
|
10643
|
-
layers: zod_1.z
|
|
10644
|
-
.array(MANAGE_LAYER_SCHEMA)
|
|
12186
|
+
.describe("action=execute、dryRun、planMigration 或 applyMigration 使用的 SQL 语句"),
|
|
12187
|
+
confirm: zod_1.z
|
|
12188
|
+
.boolean()
|
|
10645
12189
|
.optional()
|
|
10646
|
-
.describe("
|
|
10647
|
-
|
|
10648
|
-
|
|
12190
|
+
.describe("执行任何写入 SQL 前都需要显式设置为 true。"),
|
|
12191
|
+
envId: zod_1.z
|
|
12192
|
+
.string()
|
|
12193
|
+
.optional()
|
|
12194
|
+
.describe("可选的 CloudBase 环境 ID,不传时使用当前 MCP 环境。"),
|
|
12195
|
+
instanceId: zod_1.z
|
|
12196
|
+
.string()
|
|
12197
|
+
.optional()
|
|
12198
|
+
.describe("可选的 PostgreSQL 逻辑实例标识。"),
|
|
12199
|
+
defaultSchema: zod_1.z
|
|
12200
|
+
.string()
|
|
12201
|
+
.optional()
|
|
12202
|
+
.describe("默认 schema,会保存到 PG 上下文中,默认 public。"),
|
|
12203
|
+
role: zod_1.z
|
|
12204
|
+
.string()
|
|
12205
|
+
.optional()
|
|
12206
|
+
.describe("可选的 PostgreSQL role,会传给 Manager SDK executePGSql;例如需要管理策略时可传 postgres。"),
|
|
12207
|
+
objectName: zod_1.z
|
|
12208
|
+
.string()
|
|
12209
|
+
.optional()
|
|
12210
|
+
.describe("action=migrationDetail 或 rollbackMigration 时使用的 migration ID。"),
|
|
10649
12211
|
},
|
|
10650
12212
|
annotations: {
|
|
10651
12213
|
readOnlyHint: false,
|
|
10652
12214
|
destructiveHint: true,
|
|
10653
12215
|
idempotentHint: false,
|
|
10654
12216
|
openWorldHint: true,
|
|
10655
|
-
category:
|
|
12217
|
+
category: CATEGORY,
|
|
10656
12218
|
},
|
|
10657
|
-
}, async (
|
|
12219
|
+
}, async (args) => {
|
|
12220
|
+
switch (args.action) {
|
|
12221
|
+
case "init":
|
|
12222
|
+
return handleInit(args, server, deps);
|
|
12223
|
+
case "execute": {
|
|
12224
|
+
const context = await getPgContext(server);
|
|
12225
|
+
if (!context) {
|
|
12226
|
+
return buildMissingContextResult();
|
|
12227
|
+
}
|
|
12228
|
+
return handleExecuteSql(args, context, deps);
|
|
12229
|
+
}
|
|
12230
|
+
case "dryRun":
|
|
12231
|
+
return handleDryRun(args);
|
|
12232
|
+
case "planMigration":
|
|
12233
|
+
case "applyMigration":
|
|
12234
|
+
case "listMigrations":
|
|
12235
|
+
case "migrationDetail":
|
|
12236
|
+
case "rollbackMigration": {
|
|
12237
|
+
const migrationCtx = await getPgContext(server);
|
|
12238
|
+
if (!migrationCtx) {
|
|
12239
|
+
return buildMissingContextResult();
|
|
12240
|
+
}
|
|
12241
|
+
const cbOpts = server.cloudBaseOptions;
|
|
12242
|
+
switch (args.action) {
|
|
12243
|
+
case "planMigration": return handlePlanMigration(args, migrationCtx, deps, cbOpts);
|
|
12244
|
+
case "applyMigration": return handleApplyMigration(args, migrationCtx, deps, cbOpts);
|
|
12245
|
+
case "listMigrations": return handleListMigrations(args, migrationCtx, deps, cbOpts);
|
|
12246
|
+
case "migrationDetail": return handleMigrationDetail(args, migrationCtx, deps, cbOpts);
|
|
12247
|
+
case "rollbackMigration": return handleRollbackMigration(args, migrationCtx, deps, cbOpts);
|
|
12248
|
+
default: return buildPgToolResult({ success: false, errorCode: "UNSUPPORTED_ACTION", message: `Unsupported action: ${args.action}` });
|
|
12249
|
+
}
|
|
12250
|
+
}
|
|
12251
|
+
default:
|
|
12252
|
+
return buildPgToolResult({
|
|
12253
|
+
success: false,
|
|
12254
|
+
errorCode: "UNSUPPORTED_ACTION",
|
|
12255
|
+
message: `Unsupported PostgreSQL manage action: ${args.action}`,
|
|
12256
|
+
});
|
|
12257
|
+
}
|
|
12258
|
+
});
|
|
10658
12259
|
}
|
|
10659
12260
|
|
|
10660
12261
|
|
|
10661
|
-
/***/ }),
|
|
10662
|
-
|
|
10663
|
-
/***/ 6220:
|
|
10664
|
-
/***/ ((module) => {
|
|
10665
|
-
|
|
10666
|
-
module.exports = __WEBPACK_EXTERNAL_MODULE_ws__;
|
|
10667
|
-
|
|
10668
12262
|
/***/ }),
|
|
10669
12263
|
|
|
10670
12264
|
/***/ 6485:
|
|
@@ -10901,7 +12495,7 @@ function registerPermissionTools(server) {
|
|
|
10901
12495
|
};
|
|
10902
12496
|
server.registerTool?.("queryPermissions", {
|
|
10903
12497
|
title: "查询 CloudBase 权限与用户配置",
|
|
10904
|
-
description: "查询 CloudBase 权限与用户配置,支持查询资源权限(数据库/云函数/存储桶等)、角色列表/详情、应用用户列表/详情。\n\n示例:\n- 查询存储桶权限:`action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\"`",
|
|
12498
|
+
description: "查询 CloudBase 权限与用户配置,支持查询资源权限(数据库/云函数/存储桶等)、角色列表/详情、应用用户列表/详情。\n\n示例:\n- 查询存储桶权限:`action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\"`\n\n📌 跨后端边界提示:调用前先用 `envQuery(action=\"info\", envId=...)` 看 `EnvInfo.RuntimeBackends`。`resourceType=\"noSqlDatabase\"` 查询的是 CloudBase NoSQL 集合规则,与 CloudBase PostgreSQL(PG)表的行级安全(RLS)是两套独立机制——同一个 PG 环境里 NoSQL 集合若仍在使用,对那些集合查询本工具结果**仍然有效**。要查 PG 表 RLS,请改用 `queryPgDatabase(action=\"sql\", sql=\"SELECT * FROM pg_policies WHERE tablename=...\")`。本工具不涉及 MySQL 权限。",
|
|
10905
12499
|
inputSchema: {
|
|
10906
12500
|
action: zod_1.z.enum(QUERY_PERMISSION_ACTIONS),
|
|
10907
12501
|
resourceType: zod_1.z
|
|
@@ -11062,7 +12656,7 @@ function registerPermissionTools(server) {
|
|
|
11062
12656
|
}));
|
|
11063
12657
|
server.registerTool?.("managePermissions", {
|
|
11064
12658
|
title: "管理 CloudBase 权限与用户配置",
|
|
11065
|
-
description: "管理 CloudBase 权限与用户配置,支持修改资源权限(数据库/云函数/存储桶等)、角色管理、成员与策略增删、应用用户 CRUD。\n\n示例:\n- 设置存储桶为私有:`action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"PRIVATE\"`\n- 创建角色:`action=\"createRole\", roleName=\"admin\", roleIdentity=\"admin\"`\n\n注意:`createUser` / `updateUser` 是环境侧应用用户管理能力,适合测试账号、管理员或预置用户,不应替代浏览器里的 Web SDK
|
|
12659
|
+
description: "管理 CloudBase 权限与用户配置,支持修改资源权限(数据库/云函数/存储桶等)、角色管理、成员与策略增删、应用用户 CRUD。\n\n示例:\n- 设置存储桶为私有:`action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"PRIVATE\"`\n- 创建角色:`action=\"createRole\", roleName=\"admin\", roleIdentity=\"admin\"`\n\n注意:`createUser` / `updateUser` 是环境侧应用用户管理能力,适合测试账号、管理员或预置用户,不应替代浏览器里的 Web SDK 注册表单;前端用户名密码注册应使用 `auth.signUp({ username, password })`,登录应使用 `auth.signInWithPassword({ username, password })`。直接在浏览器里用 `auth.signUp` 创建用户名密码用户取决于 SDK/provider 支持,使用前必须验证;不支持时应走后端或管理端边界,不能在浏览器暴露密钥。`securityRule` 的详细语义取决于 `resourceType`:`doc._openid`、`auth.openid`、查询条件子集校验,以及 `create` / `update` / `delete` JSON 模板仅适用于 `resourceType=\"noSqlDatabase\"` 的文档数据库安全规则;配置 `function` 或 `storage` 时,请参考各自官方安全规则文档,而不是复用 NoSQL 模板。\n\n📌 跨后端边界提示:调用前先用 `envQuery(action=\"info\", envId=...)` 看 `EnvInfo.RuntimeBackends`:\n- `resourceType=\"noSqlDatabase\"` 仅作用于 CloudBase NoSQL 文档数据库的集合;CloudBase PostgreSQL(PG)表的行级权限**不**受它控制——PG 表请改用 RLS:`managePgDatabase(action=\"execute\", confirm=true)` 跑 `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` 与 `CREATE POLICY ...`。同一个 PG 环境里如果还有 NoSQL 集合在用,对那些**集合**继续使用 `noSqlDatabase` 规则是正确的——不是\"PG 环境就禁用本工具\"。\n- `resourceType=\"storage\"` 控制的是 NoSQL/COS 存储桶 ACL;PG 的 `pgstore` bucket 不在此 `resourceType` 覆盖范围内。\n- 本工具不涉及 MySQL;MySQL 数据库权限请走 MySQL 自身的 GRANT/REVOKE 语句(通过 `manageSqlDatabase`)。",
|
|
11066
12660
|
inputSchema: {
|
|
11067
12661
|
action: zod_1.z.enum(MANAGE_PERMISSION_ACTIONS),
|
|
11068
12662
|
resourceType: zod_1.z
|
|
@@ -11752,7 +13346,7 @@ function registerSetupTools(server) {
|
|
|
11752
13346
|
server.registerTool("downloadTemplate", {
|
|
11753
13347
|
title: "下载项目模板",
|
|
11754
13348
|
description: `自动下载并部署CloudBase项目模板。
|
|
11755
|
-
**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.
|
|
13349
|
+
**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.22.0-beta.0" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
|
|
11756
13350
|
inputSchema: {
|
|
11757
13351
|
template: zod_1.z
|
|
11758
13352
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|
|
@@ -14488,6 +16082,201 @@ function registerDownloadTools(server) {
|
|
|
14488
16082
|
}
|
|
14489
16083
|
|
|
14490
16084
|
|
|
16085
|
+
/***/ }),
|
|
16086
|
+
|
|
16087
|
+
/***/ 9189:
|
|
16088
|
+
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
16089
|
+
|
|
16090
|
+
|
|
16091
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
16092
|
+
exports.registerPGStorageTools = registerPGStorageTools;
|
|
16093
|
+
const zod_1 = __webpack_require__(2971);
|
|
16094
|
+
const tool_result_js_1 = __webpack_require__(9835);
|
|
16095
|
+
const CATEGORY = "PostgreSQL storage";
|
|
16096
|
+
const QUERY_PG_STORAGE = "queryPgStorage";
|
|
16097
|
+
const STORAGE_ACTIONS = [
|
|
16098
|
+
"buckets",
|
|
16099
|
+
"config",
|
|
16100
|
+
"uploadPlan",
|
|
16101
|
+
"objectInfo",
|
|
16102
|
+
"signUpload",
|
|
16103
|
+
"signDownload",
|
|
16104
|
+
];
|
|
16105
|
+
function buildPgStorageResult(payload) {
|
|
16106
|
+
return (0, tool_result_js_1.buildJsonToolResult)(payload);
|
|
16107
|
+
}
|
|
16108
|
+
function requireBucket(args) {
|
|
16109
|
+
if (!args.bucket?.trim()) {
|
|
16110
|
+
return buildPgStorageResult({
|
|
16111
|
+
success: false,
|
|
16112
|
+
errorCode: "BUCKET_REQUIRED",
|
|
16113
|
+
message: "Provide bucket when querying PostgreSQL storage.",
|
|
16114
|
+
});
|
|
16115
|
+
}
|
|
16116
|
+
return undefined;
|
|
16117
|
+
}
|
|
16118
|
+
function normalizeObjects(args) {
|
|
16119
|
+
if (args.objects?.length) {
|
|
16120
|
+
return args.objects.map((object) => ({
|
|
16121
|
+
objectKey: object.objectKey,
|
|
16122
|
+
contentType: object.contentType ?? "application/octet-stream",
|
|
16123
|
+
sizeBytes: object.sizeBytes ?? null,
|
|
16124
|
+
}));
|
|
16125
|
+
}
|
|
16126
|
+
const objectKeys = args.objectKeys?.length
|
|
16127
|
+
? args.objectKeys
|
|
16128
|
+
: args.objectKey
|
|
16129
|
+
? [args.objectKey]
|
|
16130
|
+
: [];
|
|
16131
|
+
return objectKeys.map((objectKey) => ({
|
|
16132
|
+
objectKey,
|
|
16133
|
+
contentType: "application/octet-stream",
|
|
16134
|
+
sizeBytes: null,
|
|
16135
|
+
}));
|
|
16136
|
+
}
|
|
16137
|
+
function buildHttpApiPlan(server, bucket) {
|
|
16138
|
+
const envId = server.cloudBaseOptions?.envId ?? "${envId}";
|
|
16139
|
+
const region = server.cloudBaseOptions?.region ?? "${region}";
|
|
16140
|
+
return {
|
|
16141
|
+
envId,
|
|
16142
|
+
region,
|
|
16143
|
+
bucket,
|
|
16144
|
+
auth: "accessToken",
|
|
16145
|
+
transport: "CloudBase HTTP API",
|
|
16146
|
+
endpointTemplate: "https://api.weixin.qq.com/tcb/{storageAction}?access_token={accessToken}",
|
|
16147
|
+
headersTemplate: {
|
|
16148
|
+
"content-type": "application/json",
|
|
16149
|
+
},
|
|
16150
|
+
bodyTemplate: {
|
|
16151
|
+
env: envId,
|
|
16152
|
+
bucket,
|
|
16153
|
+
},
|
|
16154
|
+
};
|
|
16155
|
+
}
|
|
16156
|
+
async function handleBuckets(server) {
|
|
16157
|
+
return buildPgStorageResult({
|
|
16158
|
+
success: true,
|
|
16159
|
+
data: {
|
|
16160
|
+
capability: "storageManagerSdkOrHttpApi",
|
|
16161
|
+
envId: server.cloudBaseOptions?.envId ?? null,
|
|
16162
|
+
supportedActions: ["buckets", "config", "uploadPlan", "objectInfo"],
|
|
16163
|
+
note: "PG environment storage is exposed as metadata and implementation plans in MCP. Application data-plane upload/download should use SDK or HTTP API code.",
|
|
16164
|
+
},
|
|
16165
|
+
message: "Resolved PostgreSQL storage capability summary. Use uploadPlan for application-side upload implementation.",
|
|
16166
|
+
});
|
|
16167
|
+
}
|
|
16168
|
+
async function handleUploadPlan(args, server) {
|
|
16169
|
+
const missingBucket = requireBucket(args);
|
|
16170
|
+
if (missingBucket)
|
|
16171
|
+
return missingBucket;
|
|
16172
|
+
const objects = normalizeObjects(args);
|
|
16173
|
+
const bucket = args.bucket;
|
|
16174
|
+
return buildPgStorageResult({
|
|
16175
|
+
success: true,
|
|
16176
|
+
data: {
|
|
16177
|
+
action: "uploadPlan",
|
|
16178
|
+
uploadMode: "directHttpApi",
|
|
16179
|
+
implementation: "use_sdk_or_http_api_in_app_code",
|
|
16180
|
+
...buildHttpApiPlan(server, bucket),
|
|
16181
|
+
objectCount: objects.length,
|
|
16182
|
+
objects,
|
|
16183
|
+
constraints: {
|
|
16184
|
+
mcpDoesNotReadLocalFiles: true,
|
|
16185
|
+
mcpDoesNotStreamFileContent: true,
|
|
16186
|
+
noSignedUrlByDefault: true,
|
|
16187
|
+
multiFileOutputIsMetadataOnly: true,
|
|
16188
|
+
},
|
|
16189
|
+
steps: [
|
|
16190
|
+
"Get an application-side or service-side access token.",
|
|
16191
|
+
"Call the CloudBase storage HTTP API or Manager SDK from application code.",
|
|
16192
|
+
"Upload file bytes outside MCP, then call objectInfo if metadata verification is needed.",
|
|
16193
|
+
],
|
|
16194
|
+
},
|
|
16195
|
+
message: "Generated an HTTP API upload plan without signed URLs or file content. This keeps MCP token usage stable for multi-file uploads.",
|
|
16196
|
+
});
|
|
16197
|
+
}
|
|
16198
|
+
async function handleObjectInfo(args, server) {
|
|
16199
|
+
const missingBucket = requireBucket(args);
|
|
16200
|
+
if (missingBucket)
|
|
16201
|
+
return missingBucket;
|
|
16202
|
+
const objectKeys = normalizeObjects(args).map((object) => object.objectKey);
|
|
16203
|
+
return buildPgStorageResult({
|
|
16204
|
+
success: true,
|
|
16205
|
+
data: {
|
|
16206
|
+
action: "objectInfo",
|
|
16207
|
+
...buildHttpApiPlan(server, args.bucket),
|
|
16208
|
+
objectKeys,
|
|
16209
|
+
implementation: "use_sdk_or_http_api_in_app_code",
|
|
16210
|
+
mcpFileIo: false,
|
|
16211
|
+
},
|
|
16212
|
+
message: "Generated an object metadata query plan. Use application code or service code for actual storage data-plane calls.",
|
|
16213
|
+
});
|
|
16214
|
+
}
|
|
16215
|
+
async function handleSignedUrl(args) {
|
|
16216
|
+
const missingBucket = requireBucket(args);
|
|
16217
|
+
if (missingBucket)
|
|
16218
|
+
return missingBucket;
|
|
16219
|
+
return buildPgStorageResult({
|
|
16220
|
+
success: false,
|
|
16221
|
+
errorCode: "SIGNED_URL_NOT_DEFAULT",
|
|
16222
|
+
data: {
|
|
16223
|
+
action: args.action,
|
|
16224
|
+
bucket: args.bucket,
|
|
16225
|
+
expiresIn: args.expiresIn ?? 900,
|
|
16226
|
+
recommendation: "Use queryPgStorage(action=uploadPlan) and HTTP API/SDK application code for multi-file flows.",
|
|
16227
|
+
},
|
|
16228
|
+
message: "Signed URL generation is intentionally not the default PG storage path because multi-file signed outputs waste tokens. Use uploadPlan unless a delegated one-off upload/download is explicitly required.",
|
|
16229
|
+
});
|
|
16230
|
+
}
|
|
16231
|
+
function registerPGStorageTools(server) {
|
|
16232
|
+
server.registerTool?.(QUERY_PG_STORAGE, {
|
|
16233
|
+
title: "查询 PostgreSQL 环境云存储能力和上传方案",
|
|
16234
|
+
description: "查询 CloudBase PostgreSQL 环境下的云存储能力。返回 bucket/config 能力摘要、对象信息查询方案,以及基于 HTTP API 或 SDK 的上传实现方案;不会读取本地文件,也不会默认输出大量签名 URL。",
|
|
16235
|
+
inputSchema: {
|
|
16236
|
+
action: zod_1.z
|
|
16237
|
+
.enum(STORAGE_ACTIONS)
|
|
16238
|
+
.describe("操作类型:buckets/config=查询存储能力摘要;uploadPlan=生成 HTTP API/SDK 上传方案;objectInfo=生成对象元信息查询方案;signUpload/signDownload=显式的一次性签名 URL 请求占位"),
|
|
16239
|
+
bucket: zod_1.z.string().optional().describe("云存储 bucket 名称"),
|
|
16240
|
+
objectKey: zod_1.z.string().optional().describe("单个对象 key"),
|
|
16241
|
+
objectKeys: zod_1.z.array(zod_1.z.string()).optional().describe("多个对象 key,用于对象元信息查询规划"),
|
|
16242
|
+
objects: zod_1.z
|
|
16243
|
+
.array(zod_1.z.object({
|
|
16244
|
+
objectKey: zod_1.z.string(),
|
|
16245
|
+
contentType: zod_1.z.string().optional(),
|
|
16246
|
+
sizeBytes: zod_1.z.number().int().nonnegative().optional(),
|
|
16247
|
+
}))
|
|
16248
|
+
.optional()
|
|
16249
|
+
.describe("待上传对象的元信息。文件字节内容不会通过 MCP 传递。"),
|
|
16250
|
+
expiresIn: zod_1.z.number().int().min(60).max(86400).optional().describe("签名 URL 有效期,单位秒,范围 60 到 86400。"),
|
|
16251
|
+
},
|
|
16252
|
+
annotations: {
|
|
16253
|
+
readOnlyHint: true,
|
|
16254
|
+
openWorldHint: true,
|
|
16255
|
+
category: CATEGORY,
|
|
16256
|
+
},
|
|
16257
|
+
}, async (args) => {
|
|
16258
|
+
switch (args.action) {
|
|
16259
|
+
case "buckets":
|
|
16260
|
+
case "config":
|
|
16261
|
+
return handleBuckets(server);
|
|
16262
|
+
case "uploadPlan":
|
|
16263
|
+
return handleUploadPlan(args, server);
|
|
16264
|
+
case "objectInfo":
|
|
16265
|
+
return handleObjectInfo(args, server);
|
|
16266
|
+
case "signUpload":
|
|
16267
|
+
case "signDownload":
|
|
16268
|
+
return handleSignedUrl(args);
|
|
16269
|
+
default:
|
|
16270
|
+
return buildPgStorageResult({
|
|
16271
|
+
success: false,
|
|
16272
|
+
errorCode: "UNSUPPORTED_ACTION",
|
|
16273
|
+
message: `Unsupported PostgreSQL storage action: ${args.action}`,
|
|
16274
|
+
});
|
|
16275
|
+
}
|
|
16276
|
+
});
|
|
16277
|
+
}
|
|
16278
|
+
|
|
16279
|
+
|
|
14491
16280
|
/***/ }),
|
|
14492
16281
|
|
|
14493
16282
|
/***/ 9192:
|
|
@@ -14902,6 +16691,13 @@ function registerAgentTools(server) {
|
|
|
14902
16691
|
}
|
|
14903
16692
|
|
|
14904
16693
|
|
|
16694
|
+
/***/ }),
|
|
16695
|
+
|
|
16696
|
+
/***/ 9627:
|
|
16697
|
+
/***/ ((module) => {
|
|
16698
|
+
|
|
16699
|
+
module.exports = __WEBPACK_EXTERNAL_MODULE_node_fs_75ed2103__;
|
|
16700
|
+
|
|
14905
16701
|
/***/ }),
|
|
14906
16702
|
|
|
14907
16703
|
/***/ 9684:
|
|
@@ -15009,6 +16805,44 @@ const zod_1 = __webpack_require__(2971);
|
|
|
15009
16805
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
15010
16806
|
const tool_result_js_1 = __webpack_require__(9835);
|
|
15011
16807
|
const CATEGORY = "SQL database";
|
|
16808
|
+
const MYSQL_GATE_CACHE = new Map();
|
|
16809
|
+
/**
|
|
16810
|
+
* Check whether MySQL tools should be blocked because the env is in PG-only mode.
|
|
16811
|
+
* Returns an error result if blocked, or null if MySQL is available/can't be determined.
|
|
16812
|
+
*/
|
|
16813
|
+
async function checkMysqlGate(server) {
|
|
16814
|
+
const { envId } = server.cloudBaseOptions ?? {};
|
|
16815
|
+
if (!envId)
|
|
16816
|
+
return null;
|
|
16817
|
+
if (MYSQL_GATE_CACHE.has(envId)) {
|
|
16818
|
+
if (MYSQL_GATE_CACHE.get(envId))
|
|
16819
|
+
return null; // cached as available
|
|
16820
|
+
// cached as blocked — return the gate error
|
|
16821
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
16822
|
+
success: false,
|
|
16823
|
+
errorCode: "MYSQL_NOT_AVAILABLE",
|
|
16824
|
+
message: `This environment (${envId}) does not have MySQL. Use managePgDatabase / queryPgDatabase instead.`,
|
|
16825
|
+
nextActions: [{ tool: "managePgDatabase", params: { action: "execute" }, description: "Use managePgDatabase" }],
|
|
16826
|
+
});
|
|
16827
|
+
}
|
|
16828
|
+
try {
|
|
16829
|
+
const manager = await (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions: { envId } });
|
|
16830
|
+
const envInfo = await manager.env.getEnvInfo?.();
|
|
16831
|
+
const backends = envInfo?.EnvInfo?.RuntimeBackends;
|
|
16832
|
+
if (backends?.postgresql === true && backends?.mysql !== true) {
|
|
16833
|
+
MYSQL_GATE_CACHE.set(envId, false);
|
|
16834
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
16835
|
+
success: false,
|
|
16836
|
+
errorCode: "MYSQL_NOT_AVAILABLE",
|
|
16837
|
+
message: `This environment (${envId}) has PostgreSQL enabled but MySQL is NOT available. Use managePgDatabase / queryPgDatabase instead of manageSqlDatabase / querySqlDatabase.`,
|
|
16838
|
+
nextActions: [{ tool: "managePgDatabase", params: { action: "execute" }, description: "Use managePgDatabase for PostgreSQL operations" }],
|
|
16839
|
+
});
|
|
16840
|
+
}
|
|
16841
|
+
}
|
|
16842
|
+
catch { /* probe failure — allow through */ }
|
|
16843
|
+
MYSQL_GATE_CACHE.set(envId, true);
|
|
16844
|
+
return null;
|
|
16845
|
+
}
|
|
15012
16846
|
const QUERY_SQL_DATABASE = "querySqlDatabase";
|
|
15013
16847
|
const MANAGE_SQL_DATABASE = "manageSqlDatabase";
|
|
15014
16848
|
const QUERY_PERMISSIONS = "queryPermissions";
|
|
@@ -15836,8 +17670,8 @@ function registerSQLDatabaseTools(server) {
|
|
|
15836
17670
|
server,
|
|
15837
17671
|
};
|
|
15838
17672
|
server.registerTool?.(QUERY_SQL_DATABASE, {
|
|
15839
|
-
title: "
|
|
15840
|
-
description: "
|
|
17673
|
+
title: "查询 CloudBase MySQL 数据库状态或执行只读 SQL",
|
|
17674
|
+
description: "查询 CloudBase MySQL 数据库信息。支持执行只读 SQL、查询 MySQL 开通结果、查询 MySQL 任务状态,以及获取当前实例上下文信息。",
|
|
15841
17675
|
inputSchema: {
|
|
15842
17676
|
action: zod_1.z
|
|
15843
17677
|
.enum(QUERY_ACTIONS)
|
|
@@ -15864,6 +17698,9 @@ function registerSQLDatabaseTools(server) {
|
|
|
15864
17698
|
category: CATEGORY,
|
|
15865
17699
|
},
|
|
15866
17700
|
}, async (args) => {
|
|
17701
|
+
const gate = await checkMysqlGate(server);
|
|
17702
|
+
if (gate)
|
|
17703
|
+
return gate;
|
|
15867
17704
|
switch (args.action) {
|
|
15868
17705
|
case "runQuery":
|
|
15869
17706
|
return handleRunQuery(args, context);
|
|
@@ -15879,8 +17716,8 @@ function registerSQLDatabaseTools(server) {
|
|
|
15879
17716
|
}
|
|
15880
17717
|
});
|
|
15881
17718
|
server.registerTool?.(MANAGE_SQL_DATABASE, {
|
|
15882
|
-
title: "
|
|
15883
|
-
description: "
|
|
17719
|
+
title: "管理 CloudBase MySQL 数据库生命周期或执行写入 SQL",
|
|
17720
|
+
description: "管理 CloudBase MySQL 数据库资源。支持开通 MySQL、销毁 MySQL、执行写入 SQL/DDL,以及初始化数据库 Schema。注意:必须先开通 MySQL(action=provisionMySQL,confirm=true)才能执行 runStatement 或 initializeSchema。若 MySQL 尚未开通,工具会返回 MYSQL_NOT_CREATED 并给出开通的 nextAction 提示。",
|
|
15884
17721
|
inputSchema: {
|
|
15885
17722
|
action: zod_1.z
|
|
15886
17723
|
.enum(MANAGE_ACTIONS)
|
|
@@ -15928,6 +17765,9 @@ function registerSQLDatabaseTools(server) {
|
|
|
15928
17765
|
category: CATEGORY,
|
|
15929
17766
|
},
|
|
15930
17767
|
}, async (args) => {
|
|
17768
|
+
const gate = await checkMysqlGate(server);
|
|
17769
|
+
if (gate)
|
|
17770
|
+
return gate;
|
|
15931
17771
|
switch (args.action) {
|
|
15932
17772
|
case "provisionMySQL":
|
|
15933
17773
|
return handleProvisionMySQL(args, context);
|