@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/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 分钟传播,请继续轮询 envQuery(action="domains"),直到目标域名状态为 ENABLE。',
188
+ message: '安全域名已提交添加请求。该变更通常需要约 10 分钟传播,请继续轮询 queryEnv(action="domains"),直到目标域名状态为 ENABLE。',
187
189
  propagation: {
188
190
  requiresPolling: true,
189
- pollTool: "envQuery",
191
+ pollTool: "queryEnv",
190
192
  pollAction: "domains",
191
193
  pollIntervalSuggestionSeconds: 10,
192
194
  timeoutSuggestionSeconds: 600,
193
- successCondition: '目标域名出现在 envQuery(action="domains") 返回中,且 Status 为 ENABLE。',
195
+ successCondition: '目标域名出现在 queryEnv(action="domains") 返回中,且 Status 为 ENABLE。',
194
196
  },
195
197
  next_step: {
196
- tool: "envQuery",
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: '安全域名已提交删除请求。该变更可能需要数分钟传播,请继续轮询 envQuery(action="domains"),直到目标域名不再出现。',
213
+ message: '安全域名已提交删除请求。该变更可能需要数分钟传播,请继续轮询 queryEnv(action="domains"),直到目标域名不再出现。',
212
214
  propagation: {
213
215
  requiresPolling: true,
214
- pollTool: "envQuery",
216
+ pollTool: "queryEnv",
215
217
  pollAction: "domains",
216
218
  pollIntervalSuggestionSeconds: 10,
217
219
  timeoutSuggestionSeconds: 600,
218
- successCondition: '目标域名不再出现在 envQuery(action="domains") 返回中。',
220
+ successCondition: '目标域名不再出现在 queryEnv(action="domains") 返回中。',
219
221
  },
220
222
  next_step: {
221
- tool: "envQuery",
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: "envQuery",
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 envQuery tool errors
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. 登录完成后再次调用 envQuery(action=\"list\")");
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("请使用 envQuery(action=\"list\") 查看可用的环境列表。");
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 `[envQuery/${action}] 调用失败: ${baseMessage}\n\n解决建议:\n${suggestions.join("\n")}`;
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
- server.registerTool?.("auth", {
636
- title: "CloudBase 开发阶段登录与环境",
637
- description: "CloudBase(腾讯云开发)开发阶段登录与环境绑定。登录后即可访问云资源;环境(env)是云函数、数据库、静态托管等资源的隔离单元,绑定环境后其他 MCP 工具才能操作该环境。支持:查询状态、发起登录、绑定环境(set_env)、退出登录。",
638
- inputSchema: {
639
- action: zod_1.z
640
- .enum(authActionEnum)
641
- .optional()
642
- .describe("动作:status=查询状态,start_auth=发起登录,set_env=绑定环境(传envId),logout=退出登录"),
643
- ...(supportedAuthActions.includes("start_auth")
644
- ? {
645
- authMode: zod_1.z
646
- .enum(["device", "web"])
647
- .optional()
648
- .describe("认证模式:device=设备码授权,web=浏览器回调授权"),
649
- oauthEndpoint: zod_1.z
650
- .string()
651
- .optional()
652
- .describe("高级可选:自定义 device-code 登录 endpoint。配置后 oauthCustom 默认按 true 处理"),
653
- clientId: zod_1.z
654
- .string()
655
- .optional()
656
- .describe("高级可选:自定义 device-code 登录 client_id,不传则使用默认值"),
657
- oauthCustom: zod_1.z
658
- .boolean()
659
- .optional()
660
- .describe("高级可选:自定义 endpoint 返回格式开关。未配置 endpoint 时默认 false;配置 endpoint 后默认 true,且不能设为 false"),
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
- catch (e) {
794
- // peekLoginState 内部异常,记录后 fall through
795
- (0, logger_js_1.debug)("start_auth: peekLoginState threw", { error: e instanceof Error ? e.message : String(e) });
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
- // API Key 换取失败:返回详细诊断信息
798
- // 尝试获取更详细的错误信息
799
- let diagMessage = "当前配置了 API Key 认证模式,但换取临时密钥失败。";
800
- const endpoint = process.env.CLOUDBASE_API_ENDPOINT || `https://${process.env.CLOUDBASE_ENV_ID}.ap-shanghai.tcb-api.tencentcloudapi.com`;
801
- diagMessage += `\n\n诊断信息:`;
802
- diagMessage += `\n- CLOUDBASE_ENV_ID: ${process.env.CLOUDBASE_ENV_ID}`;
803
- diagMessage += `\n- CLOUDBASE_API_KEY: ${process.env.CLOUDBASE_API_KEY.slice(0, 20)}...(已截断)`;
804
- diagMessage += `\n- Endpoint: ${endpoint}`;
805
- diagMessage += `\n\n可能原因:`;
806
- diagMessage += `\n1. API Key 已过期或被删除`;
807
- diagMessage += `\n2. Endpoint 不可达(网络/DNS 问题)`;
808
- diagMessage += `\n3. CLOUDBASE_ENV_ID 与 API Key 所属环境不匹配`;
809
- diagMessage += `\n\n建议:检查 MCP 配置中的 CLOUDBASE_API_KEY 和 CLOUDBASE_ENV_ID 环境变量是否正确。`;
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: "API_KEY_AUTH_FAILED",
813
- message: diagMessage,
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
- // 1. 如果已经有登录态,直接返回 AUTH_READY
834
- try {
835
- const existingLoginState = await (0, auth_js_1.peekLoginState)();
836
- if (existingLoginState) {
837
- const envPreparation = await prepareAuthEnvironment({
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: existingLoginState,
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: false,
859
- code: "INVALID_ARGS",
860
- message: validationError,
866
+ ok: true,
867
+ code: "STATUS",
868
+ auth_status: authStatus,
869
+ ...(isApiKeyMode ? { auth_mode: "api_key" } : {}),
861
870
  auth_config: authConfigSummary,
862
- next_step: (0, tool_result_js_1.buildAuthNextStep)("start_auth", {
863
- suggestedArgs: { action: "start_auth", authMode: "device" },
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
- // 2. 设备码模式:监听到 device code 即返回 AUTH_PENDING,后续由 toolbox 异步轮询并更新本地 credential
868
- const effectiveMode = resolvedAuthOptions.authMode;
869
- if (effectiveMode === "device") {
870
- let resolveCode;
871
- let rejectCode;
872
- const codeReady = new Promise((resolve, reject) => {
873
- resolveCode = resolve;
874
- rejectCode = reject;
875
- });
876
- const deviceOnCode = (info) => {
877
- onDeviceCode(info);
878
- if (resolveCode) {
879
- resolveCode();
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
- catch (err) {
910
- if (rejectCode) {
911
- rejectCode(err);
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 codeReady;
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
- catch (err) {
918
- const message = err instanceof Error ? err.message : String(err ?? "unknown error");
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: "AUTH_REQUIRED",
922
- message: `设备码登录初始化失败: ${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
- if (!deviceAuthInfo) {
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: "device" },
1077
+ suggestedArgs: { action: "start_auth", authMode: effectiveMode },
935
1078
  }),
936
1079
  });
937
1080
  }
938
- const envCandidates = await fetchAvailableEnvCandidates(cloudBaseOptions, server);
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: "AUTH_PENDING",
942
- message: "已发起设备码登录,请在浏览器中打开 verification_uri 并输入 user_code 完成授权。授权完成后请再次调用 auth(action=\"status\")。",
1088
+ code: "AUTH_READY",
1089
+ message: envPreparation.message,
943
1090
  auth_challenge: authChallenge(),
944
- ...buildEnvCandidatePayload(envCandidates),
945
- next_step: (0, tool_result_js_1.buildAuthNextStep)("status", {
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
- const envPreparation = await prepareAuthEnvironment({
969
- server,
970
- cloudBaseOptions,
971
- loginState,
972
- });
973
- return (0, tool_result_js_1.buildJsonToolResult)({
974
- ok: true,
975
- code: "AUTH_READY",
976
- message: envPreparation.message,
977
- auth_challenge: authChallenge(),
978
- ...buildAuthEnvSetupPayload(envPreparation),
979
- next_step: envPreparation.nextStep,
980
- });
981
- }
982
- if (action === "set_env") {
983
- const loginState = await (0, auth_js_1.peekLoginState)();
984
- if (!loginState) {
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: false,
987
- code: "AUTH_REQUIRED",
988
- message: isCodeBuddyIde(server)
989
- ? "当前未登录。CodeBuddy 暂不支持在 tool 内发起认证,请在外部完成认证后再次调用 auth(action=\"status\")。"
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
- const envCandidates = await fetchAvailableEnvCandidates(cloudBaseOptions, server);
995
- if (!envId) {
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: false,
998
- code: "INVALID_ARGS",
999
- message: "action=set_env 时必须提供 envId",
1000
- ...buildEnvCandidatePayload(envCandidates),
1001
- next_step: buildSetEnvNextStep(envCandidates),
1158
+ ok: true,
1159
+ code: "LOGGED_OUT",
1160
+ message: "✅ 已退出登录",
1002
1161
  });
1003
1162
  }
1004
- const target = envCandidates.find((item) => item.envId === envId);
1005
- if (envCandidates.length > 0 && !target) {
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: false,
1008
- code: "INVALID_ARGS",
1009
- message: `未找到环境: ${envId}`,
1010
- ...buildEnvCandidatePayload(envCandidates),
1011
- next_step: buildSetEnvNextStep(envCandidates),
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: true,
1017
- code: "ENV_READY",
1018
- message: `环境设置成功,当前环境: ${envId}`,
1019
- current_env_id: envId,
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
- if (action === "logout") {
1023
- // API Key 模式下不允许 logout
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: true,
1046
- code: "LOGGED_OUT",
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
- if (action === "get_temp_credentials") {
1051
- const loginState = (await (0, auth_js_1.peekLoginState)());
1052
- if (!loginState) {
1053
- return (0, tool_result_js_1.buildJsonToolResult)({
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
- const envIdFromEnv = process.env.CLOUDBASE_ENV_ID;
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
- // envDomainManagement - 环境域名管理(合并 createEnvDomain + deleteEnvDomain)
1330
- server.registerTool?.("envDomainManagement", {
1331
- title: "CloudBase 环境域名管理(安全域名 / CORS 白名单)",
1332
- description: "管理 CloudBase 环境的安全域名(安全域名 / CORS 白名单),支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 直接访问 CloudBase 资源时,应先用 envQuery(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。新增或删除后通常需要继续轮询 envQuery(action=domains) 确认状态收敛;安全域名一般约 10 分钟生效。⚠️ 重要:此工具仅用于 CORS/请求来源验证,不涉及 SSL 证书。如需绑定自定义域名供公网 HTTPS 访问,请使用 manageGateway(action=\"bindCustomDomain\")。",
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(["create", "delete"])
1336
- .describe("操作类型:create=添加安全域名,delete=删除安全域名"),
1337
- domains: zod_1.z.array(zod_1.z.string()).describe("安全域名数组(格式:host:port,例如 localhost:5173 或 127.0.0.1:4173)。注意:不是自定义域名,不需要证书。"),
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: false,
1341
- destructiveHint: false, // 注意:delete操作虽然是破坏性的,但这里采用较宽松的标注
1342
- idempotentHint: false,
1442
+ readOnlyHint: true,
1343
1443
  openWorldHint: true,
1344
1444
  category: "env",
1345
1445
  },
1346
- }, async ({ action, domains, }) => {
1347
- try {
1348
- const cloudbase = await getManager();
1349
- let result;
1350
- switch (action) {
1351
- case "create":
1352
- result = await cloudbase.env.createEnvDomain(domains);
1353
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
1354
- break;
1355
- case "delete":
1356
- result = await cloudbase.env.deleteEnvDomain(domains);
1357
- (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
1358
- break;
1359
- default:
1360
- throw new Error(`不支持的操作类型: ${action}`);
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
- return (0, tool_result_js_1.buildJsonToolResult)(buildEnvDomainManagementResult({
1363
- action,
1364
- domains,
1365
- result,
1366
- }));
1367
- }
1368
- catch (error) {
1369
- const toolPayloadResult = (0, tool_result_js_1.toolPayloadErrorToResult)(error);
1370
- if (toolPayloadResult) {
1371
- return toolPayloadResult;
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
- return {
1374
- content: [
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.21.1" || 0}
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.21.1" : 0;
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
- (0, databaseSQL_js_1.registerSQLDatabaseTools)(server);
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) => originalRegisterTool(name, meta, async (args) => {
2101
- try {
2102
- return await handler(args);
2103
- }
2104
- catch (error) {
2105
- if ((0, tool_result_js_1.isToolPayloadError)(error)) {
2106
- // 在结构化错误返回中注入 MCP 版本号
2107
- const payload = { ...error.payload, mcpVersion: version };
2108
- return (0, tool_result_js_1.buildJsonToolResult)(payload);
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
- throw error;
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
- // 如果传入了完整凭据,优先使用显式 CloudBase 配置
3707
- if (cloudBaseOptions && hasDirectCredentials) {
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
- // 直接用 commonService 调用 DescribeTable 检查集合是否存在,避免 SDK lazyInit → DescribeEnvs
4049
- const envId = await (0, cloudbase_manager_js_1.getEnvId)();
4050
- const result = await cloudbase.commonService("tcb", "2018-06-08").call({
4051
- Action: "DescribeTable",
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, result);
4058
- // DescribeTable 成功返回即表示集合存在
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
- collectionExists = true;
4347
- existsRequestId = descResult?.RequestId ?? "";
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: "查询并获取 NoSQL 数据库数据记录。⚠️ 本工具为服务端管理工具,用于管理端/运维端查询。当任务要求编写客户端应用代码时(例如「用 JS SDK 读取数据」),不应使用本工具,而应在项目代码中编写 @cloudbase/js-sdk 客户端代码(如 db.collection().where().get())。",
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 数据库数据记录。⚠️ 本工具为服务端管理工具,用于管理端/运维端操作(如后台脚本、数据迁移、批量修改)。当任务要求编写客户端应用代码时(例如「用 JS SDK 登录并插入数据」、「在前端读写数据库」),不应使用本工具,而应在项目代码中编写 @cloudbase/js-sdk 客户端代码(如 app.database().collection().add()、db.collection().where().get() 等)。可按 MongoDB updateOne/updateMany 的心智模型理解:部分更新必须使用 `$set`、`$inc`、`$push` 等更新操作符;如果直接传「字段到值的普通对象」这类内容,底层会把它当作替换内容,存在覆盖整条文档的风险。⚠️ 嵌套对象部分更新务必使用点号路径:要更新 `shipping.city`,应传 `$set: {\"shipping.city\": \"guangzhou\"}`,绝不能传 `$set: {\"shipping\": {\"city\": \"guangzhou\"}}`(后者会丢失 shipping 下的其他字段)。若集合中的角色/档案文档会在前端通过 `db.collection(...).doc(uid)` 读取,请确保文档 `_id` 就是该 `uid`;不要用按 `uid` 条件查询再配合 `upsert=true` 的方式去更新 `users` / `profiles`,否则经常会生成一个不同的 `_id`,导致后续 `doc(uid)` 读取命中不到。",
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) 查询当前支持 ${openapis.length} 个 API 文档,分别是:
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 时指定。CloudBase 文档操作类型:listModules=列出所有文档模块,listModuleDocs=获取指定模块的目录结构,findByName=按名称/路径/URL 智能查找,readDoc=读取指定文档 Markdown,searchDocs=全文搜索官方文档。"),
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 推导出 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\") 设置。",
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
- const result = await storageService.listDirectoryFiles(input.cloudPath);
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: result || [],
7286
- totalCount: result?.length || 0
7498
+ files,
7499
+ totalCount: files.length
7287
7500
  },
7288
- message: `Successfully listed ${result?.length || 0} files in directory '${input.cloudPath}'`
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
- const result = await storageService.getFileInfo(input.cloudPath);
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: result
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
- const result = await storageService.getTemporaryUrl([{
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: result[0]?.url || "",
7565
+ temporaryUrl,
7333
7566
  expireTime: `${input.maxAge || 3600}秒`,
7334
- fileId: result[0]?.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
- await storageService.downloadFile({
7350
- cloudPath: input.cloudPath,
7351
- localPath
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
- await storageService.uploadDirectory({
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
- onProgress: (progressData) => {
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
- await storageService.uploadFile({
7417
- localPath: input.localPath,
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
- onProgress: (progressData) => {
7420
- console.log("Upload file progress:", progressData);
7421
- }
7700
+ cloudBaseOptions,
7701
+ manager,
7422
7702
  });
7423
7703
  }
7424
- const fileUrls = await storageService.getTemporaryUrl([{
7425
- cloudPath: input.cloudPath,
7426
- maxAge: 3600
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: fileUrls[0]?.url || "",
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
- await storageService.downloadDirectory({
7459
- cloudPath: input.cloudPath,
7460
- localPath: input.localPath
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
- await storageService.downloadFile({
7465
- cloudPath: input.cloudPath,
7466
- localPath: input.localPath
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
- await storageService.deleteDirectory(input.cloudPath);
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
- await storageService.deleteFile([input.cloudPath]);
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: "auth.signUp({ username, password })",
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.21.1" || 0;
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(exports.QUERY_FUNCTION_ACTIONS)
10561
- .describe("只读操作类型:" +
10562
- "\n- `listFunctions`: 列出所有 CloudBase 云函数" +
10563
- "\n- `getFunctionDetail`: 获取 CloudBase 云函数详情(需要 functionName)" +
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("日志查询开始时间,格式必须为 YYYY-MM-DD HH:mm:ss(如 2024-01-01 00:00:00)。" +
10583
- "与 endTime 间隔不能超过一天。不传时默认查询最近一天"),
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("日志查询结束时间,格式必须为 YYYY-MM-DD HH:mm:ss(如 2024-01-01 23:59:59)。" +
10588
- "与 startTime 间隔不能超过一天。不传时默认为当前时间"),
10589
- requestId: zod_1.z
10590
- .string()
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("日志请求 ID。`getFunctionLogDetail` 操作必填,可从 `listFunctionLogs` 结果中获取"),
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: "functions",
12149
+ category: CATEGORY,
10603
12150
  },
10604
- }, async (input) => withEnvelope(() => handleQueryFunctions(input)));
10605
- server.registerTool?.("manageFunctions", {
10606
- title: "管理 CloudBase 云函数",
10607
- description: "CloudBase 云函数统一写入口。支持创建函数、更新代码、更新配置、调用函数、管理定时跑 / 定时任务 / scheduled job 的 timer 触发器和层绑定。" +
10608
- "如果要创建 cron 定时任务,先用 createFunction 创建函数,再用 createFunctionTrigger 创建 timer 触发器(支持7段cron表达式),deleteFunctionTrigger 删除触发器。" +
10609
- "危险操作需要显式 confirm=true。",
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(exports.MANAGE_FUNCTION_ACTIONS)
10613
- .describe("写操作类型,例如 createFunction、updateFunctionCode、invokeFunction、deleteFunction、" +
10614
- "createFunctionTrigger(定时任务 / cron / timer)、deleteFunctionTrigger、attachLayer、detachLayer"),
10615
- func: CREATE_FUNCTION_SCHEMA.optional().describe("createFunction 操作的函数配置"),
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("createFunctionTrigger 的触发器列表,用于定时跑 / 定时任务 / scheduled job。timer 触发器使用7段 cron 表达式(秒 分 时 日 月 星期 年)," +
10634
- '如 "0 */5 * * * * *" 表示每5分钟执行一次'),
10635
- triggerName: zod_1.z.string().optional().describe("deleteFunctionTrigger 的目标触发器名称"),
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("updateFunctionLayers 的目标层列表,顺序即最终顺序"),
10647
- codeSecret: zod_1.z.string().optional().describe("层绑定时的代码保护密钥"),
10648
- confirm: zod_1.z.boolean().optional().describe("危险操作确认开关。deleteFunction、deleteFunctionTrigger、deleteLayerVersion、detachLayer 等删除类操作需要显式传入 confirm=true"),
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: "functions",
12217
+ category: CATEGORY,
10656
12218
  },
10657
- }, async (input) => withEnvelope(() => handleManageFunctions(input)));
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 注册表单。前端用户名密码注册应使用 `auth.signUp({ username, password })`,登录应使用 `auth.signInWithPassword({ username, password })`。`securityRule` 的详细语义取决于 `resourceType`,请参考对应官方文档。",
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.21.1" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
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: "Query SQL database state or execute read-only SQL",
15840
- description: "Query SQL database information. Supports read-only SQL execution, MySQL provisioning result lookup, MySQL task status lookup, and current instance context discovery.",
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: "Manage SQL database lifecycle or execute write SQL",
15883
- description: "Manage SQL database resources. Supports MySQL provisioning, MySQL destruction, write SQL/DDL execution, and schema initialization. IMPORTANT: MySQL must be provisioned first (action=provisionMySQL with confirm=true) before any runStatement or initializeSchema call. If MySQL is not yet provisioned, the tool will return MYSQL_NOT_CREATED with a nextAction to provision first.",
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);