@cloudbase/cloudbase-mcp 2.34.2 → 2.34.4

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
@@ -2400,6 +2400,7 @@ exports.apps = (0, types_js_1.defineModule)({
2400
2400
  getSuccess: "CloudBase 应用详情查询成功",
2401
2401
  listVersionsSuccess: "CloudBase 应用版本列表查询成功",
2402
2402
  buildIdRequired: "action=getBuildLog 时必须提供 buildId",
2403
+ buildIdMustBeNumeric: "buildId 必须是数字(云 API DescribeCloudBaseRunBuildLog 的 BuildId 为整数)。收到的值:{buildId}。请使用部署回执或 getAppVersion 返回的 BuildId。",
2403
2404
  buildLogFound: "查询到 {count} 条构建日志",
2404
2405
  buildLogEmpty: "暂无构建日志",
2405
2406
  nextStepQueryBuildLog: "查询构建日志",
@@ -2471,6 +2472,7 @@ exports.apps = (0, types_js_1.defineModule)({
2471
2472
  getSuccess: "CloudBase app details retrieved successfully",
2472
2473
  listVersionsSuccess: "CloudBase app version list retrieved successfully",
2473
2474
  buildIdRequired: "buildId is required when action=getBuildLog",
2475
+ buildIdMustBeNumeric: "buildId must be numeric (the BuildId of the cloud API DescribeCloudBaseRunBuildLog is an integer). Received: {buildId}. Use the BuildId returned by the deploy response or getAppVersion.",
2474
2476
  buildLogFound: "Retrieved {count} build log entries",
2475
2477
  buildLogEmpty: "No build logs yet",
2476
2478
  nextStepQueryBuildLog: "Query build logs",
@@ -4605,6 +4607,9 @@ exports.gateway = (0, types_js_1.defineModule)({
4605
4607
  "create.backendError": "为目标资源配置访问路由失败(后端内部错误)。请确保:1) 目标云函数已成功创建并处于 Active 状态;2) 环境默认 HTTP 域名已完成初始化(IsDefault);3) 该访问路径未被占用。",
4606
4608
  "create.backendErrorScfHint": "此外注意:HTTP 云函数必须用 upstreamResourceType=WEB_SCF,Event 云函数必须用 SCF;互标会导致此错误。",
4607
4609
  "create.backendErrorSuffix": " 原始错误:{message}",
4610
+ "create.upstreamNotFound": "createRoute 未执行:上游目标不存在。upstreamResourceType={type}、targetName={name} 在环境中未找到,未创建任何路由。请先创建该资源(或核对名称/类型),再重新调用 createRoute。",
4611
+ "create.upstreamCandidates": "当前环境已有的 {type} 目标(最多 10 个,供核对名称):{candidates}",
4612
+ "create.upstreamNotFoundReason": "核对上游资源名称与 upstreamResourceType 后重试 createRoute",
4608
4613
  "create.privilegeOffHint": "⚠️ HTTP 网关总开关未开启,路由创建成功后访问仍将返回 HTTPSERVICE_NONACTIVATED(403);请先调用 manageGateway(action=\"enableService\", enable=true) 开启,再立刻探测 accessUrl(通常数秒到约 30 秒内生效,勿盲等 60 秒以上)。",
4609
4614
  "create.privilegeOffReason": "开启 HTTP 网关总开关(EnableService),否则访问路由会报 HTTPSERVICE_NONACTIVATED(403)",
4610
4615
  "create.privilegeUnknownHint": "(无法确认 HTTP 网关开关状态;若访问报 HTTPSERVICE_NONACTIVATED,请用 queryGateway(action=\"getPrivilege\") 查询后用 manageGateway(action=\"enableService\") 开启)",
@@ -4690,6 +4695,9 @@ exports.gateway = (0, types_js_1.defineModule)({
4690
4695
  "create.backendError": "Failed to configure the access route for the target resource (backend internal error). Ensure: 1) the target cloud function is created and Active; 2) the env default HTTP domain is initialized (IsDefault); 3) the access path is not taken.",
4691
4696
  "create.backendErrorScfHint": "Also note: HTTP cloud functions must use upstreamResourceType=WEB_SCF, Event cloud functions must use SCF; swapping them causes this error.",
4692
4697
  "create.backendErrorSuffix": " Original error: {message}",
4698
+ "create.upstreamNotFound": "createRoute skipped: the upstream target does not exist. upstreamResourceType={type}, targetName={name} was not found in this environment; no route was created. Create the resource (or fix the name/type), then call createRoute again.",
4699
+ "create.upstreamCandidates": "Existing {type} targets in this environment (up to 10, for name verification): {candidates}",
4700
+ "create.upstreamNotFoundReason": "Verify the upstream resource name and upstreamResourceType, then retry createRoute",
4693
4701
  "create.privilegeOffHint": "⚠️ The HTTP gateway master switch is off; after the route is created, access will still return HTTPSERVICE_NONACTIVATED (403). Call manageGateway(action=\"enableService\", enable=true) first, then probe accessUrl immediately (usually takes effect within a few seconds to ~30 seconds; do not wait 60+ seconds).",
4694
4702
  "create.privilegeOffReason": "Turn on the HTTP gateway master switch (EnableService), otherwise accessing the route returns HTTPSERVICE_NONACTIVATED (403)",
4695
4703
  "create.privilegeUnknownHint": " (Could not confirm the HTTP gateway switch status; if access returns HTTPSERVICE_NONACTIVATED, query via queryGateway(action=\"getPrivilege\") and enable via manageGateway(action=\"enableService\"))",
@@ -5177,9 +5185,9 @@ exports.permissions = void 0;
5177
5185
  const types_js_1 = __webpack_require__(9921);
5178
5186
  exports.permissions = (0, types_js_1.defineModule)({
5179
5187
  queryTitle: "查询 CloudBase 权限与用户配置",
5180
- queryDescription: "查询 CloudBase 权限与用户配置,支持查询资源权限(数据库/云函数/存储桶等)、角色列表/详情、应用用户列表/详情,以及网关 OPA 授权策略(对齐 CLI `tcb policy list/get`)。\n\n示例:\n- 查询存储桶权限:`action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\"`\n- 列出旧网关策略:`action=\"listPolicy\"`(PG / OPA 引擎环境返回空列表,与 CLI 一致)\n- 读取用户 Rego:`action=\"getPolicy\"`;平台扩展策略:`action=\"getPolicy\", extension=true`\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 权限。\n\n⚠️ PostgreSQL 环境:平台 `DescribeResourcePermission` 对 PG 环境会直接拒绝。当 `resourceType=\"function\"` 时,本工具会自动回退到 Manager SDK `describeEnvAuthzConfig`(与 CLI `tcb policy get` 一致,读取 `authz.user.rego`)。显式 OPA 策略请用 `listPolicy` / `getPolicy`。",
5188
+ queryDescription: "查询 CloudBase 权限与用户配置,支持查询资源权限(数据库/云函数/存储桶等)、角色列表/详情、应用用户列表/详情,以及网关 OPA 授权策略(对齐 CLI `tcb policy list/get`)。\n\n示例:\n- 查询存储桶权限:`action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\"`\n- 列出旧网关策略:`action=\"listPolicy\"`(PG / OPA 引擎环境返回空列表,与 CLI 一致)\n- 读取用户 Rego:`action=\"getPolicy\"`;平台扩展策略:`action=\"getPolicy\", extension=true`\n\n📌 跨后端边界提示:调用前先用 `queryEnv(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 权限。\n\n⚠️ PostgreSQL 环境:平台 `DescribeResourcePermission` 对 PG 环境会直接拒绝。当 `resourceType=\"function\"` 时,本工具会自动回退到 Manager SDK `describeEnvAuthzConfig`(与 CLI `tcb policy get` 一致,读取 `authz.user.rego`)。显式 OPA 策略请用 `listPolicy` / `getPolicy`。\n\n⚠️ 角色类 action(`listRoles` / `getRole`)在 **PostgreSQL 类型环境**下平台侧一律拒绝(`The current API does not support PostgreSQL type environments.`)——这是平台能力边界,不是配置问题,**不要在 PG 环境反复重试**;PG 环境请改用 RLS(`managePgDatabase(action=\"execute\")` 跑 `CREATE POLICY`)或控制台管理权限。",
5181
5189
  manageTitle: "管理 CloudBase 权限与用户配置",
5182
- manageDescription: "管理 CloudBase 权限与用户配置,支持修改资源权限(数据库/云函数/存储桶等)、角色管理、成员与策略增删、应用用户 CRUD,以及设置网关 OPA Rego 策略(对齐 CLI `tcb policy set`)。\n\n示例:\n- 设置存储桶为私有:`action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"PRIVATE\"`\n- 创建角色:`action=\"createRole\", roleName=\"admin\", roleIdentity=\"admin\"`\n- 放开云函数匿名/未登录访问(PG 会走 OPA,对齐 CLI `tcb policy set`):`action=\"updateResourcePermission\", resourceType=\"function\", resourceId=\"myFn\", permission=\"CUSTOM\", securityRule='{\"invoke\":true}'`\n- 直接设置用户 Rego:`action=\"setPolicy\", regoContent=\"package authz.user\\n\\ndefault allow := false\\n\", confirm=true`(⚠️ 立即禁用旧网关鉴权)\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 语句(通过 `manageMysqlDatabase`)。\n\n⚠️ PostgreSQL 环境:平台 `ModifyResourcePermission` 对 PG 环境会直接拒绝。当 `resourceType=\"function\"` 时,本工具会自动回退到 Manager SDK `modifyEnvAuthzConfig`(与 CLI `tcb policy set` 一致,写入 `authz.user.rego`)。`securityRule` 可传完整 Rego(`package authz.user`)或 `'{\"invoke\":true}'`(自动生成放通 anonymous/unauthenticated 调 functions 的策略)。设置 Rego 后旧网关鉴权会失效,行为与 CLI 相同。显式 OPA 策略请优先用 `action=\"setPolicy\"`。",
5190
+ manageDescription: "管理 CloudBase 权限与用户配置,支持修改资源权限(数据库/云函数/存储桶等)、角色管理、成员与策略增删、应用用户 CRUD,以及设置网关 OPA Rego 策略(对齐 CLI `tcb policy set`)。\n\n示例:\n- 设置存储桶为私有:`action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"PRIVATE\"`\n- 创建角色:`action=\"createRole\", roleName=\"admin\", roleIdentity=\"admin\"`\n- 放开云函数匿名/未登录访问(PG 会走 OPA,对齐 CLI `tcb policy set`):`action=\"updateResourcePermission\", resourceType=\"function\", resourceId=\"myFn\", permission=\"CUSTOM\", securityRule='{\"invoke\":true}'`\n- 直接设置用户 Rego:`action=\"setPolicy\", regoContent=\"package authz.user\\n\\ndefault allow := false\\n\", confirm=true`(⚠️ 立即禁用旧网关鉴权)\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📌 跨后端边界提示:调用前先用 `queryEnv(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 语句(通过 `manageMysqlDatabase`)。\n\n⚠️ PostgreSQL 环境:平台 `ModifyResourcePermission` 对 PG 环境会直接拒绝。当 `resourceType=\"function\"` 时,本工具会自动回退到 Manager SDK `modifyEnvAuthzConfig`(与 CLI `tcb policy set` 一致,写入 `authz.user.rego`)。`securityRule` 可传完整 Rego(`package authz.user`)或 `'{\"invoke\":true}'`(自动生成放通 anonymous/unauthenticated 调 functions 的策略)。设置 Rego 后旧网关鉴权会失效,行为与 CLI 相同。显式 OPA 策略请优先用 `action=\"setPolicy\"`。\n\n⚠️ 角色类 action(`createRole` / `deleteRoles` / `updateRole`)在 **PostgreSQL 类型环境**下平台侧一律拒绝(`The current API does not support PostgreSQL type environments.`)——平台能力边界,不是配置问题,**不要在 PG 环境反复重试**;PG 环境请改用 RLS(`managePgDatabase(action=\"execute\")` 跑 `CREATE POLICY`)或控制台管理权限。",
5183
5191
  writeVerificationHint: "对于 {resourceId} 这类有后端权限控制的集合,前端调用 .doc(id).update() / .doc(id).remove() 后,不能只看是否没有抛异常。请显式检查返回结果中的 updated / deleted 是否大于 0;如果 result.code、result.message 存在,或 updated / deleted 为 0,要把它当作真实失败并向上抛错。",
5184
5192
  permissionPropagationHint: "刚更新完 {resourceId} 的安全规则时,后端权限通常在数秒到约 30 秒内生效。若紧接着的真实写操作仍返回 DATABASE_PERMISSION_DENIED,请先间隔数秒用同一登录态重试同一条 .doc(id).update() / .doc(id).remove();不要盲等数分钟,也不要立刻连续重写规则,更不要在短暂传播窗口里把旧拒绝直接当成规则表达式仍然错误。",
5185
5193
  validateRegoEmpty: "action=setPolicy 需要非空 regoContent(对齐 CLI `tcb policy set <regoContent>`)。",
@@ -5246,9 +5254,9 @@ exports.permissions = (0, types_js_1.defineModule)({
5246
5254
  setPolicySuccess: "用户 OPA Rego 策略设置成功(旧网关鉴权已失效)",
5247
5255
  }, {
5248
5256
  queryTitle: "Query CloudBase permissions and user config",
5249
- queryDescription: "Query CloudBase permissions and user config: resource permissions (database/functions/buckets etc.), role list/detail, app user list/detail, and gateway OPA authorization policies (aligned with CLI `tcb policy list/get`).\n\nExamples:\n- Query bucket permission: `action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\"`\n- List legacy gateway policies: `action=\"listPolicy\"` (returns an empty list on PG / OPA engine environments, consistent with the CLI)\n- Read user Rego: `action=\"getPolicy\"`; platform extension policy: `action=\"getPolicy\", extension=true`\n\n📌 Cross-backend boundary note: before calling, check `EnvInfo.RuntimeBackends` via `envQuery(action=\"info\", envId=...)`. `resourceType=\"noSqlDatabase\"` queries CloudBase NoSQL collection rules, which are a separate mechanism from row-level security (RLS) on CloudBase PostgreSQL (PG) tables — in the same PG environment, if NoSQL collections are still in use, querying them with this tool **remains valid**. To query PG table RLS, use `queryPgDatabase(action=\"sql\", sql=\"SELECT * FROM pg_policies WHERE tablename=...\")` instead. This tool does not cover MySQL permissions.\n\n⚠️ PostgreSQL environments: the platform `DescribeResourcePermission` API rejects PG environments outright. When `resourceType=\"function\"`, this tool automatically falls back to the Manager SDK `describeEnvAuthzConfig` (aligned with CLI `tcb policy get`, reading `authz.user.rego`). For explicit OPA policies use `listPolicy` / `getPolicy`.",
5257
+ queryDescription: "Query CloudBase permissions and user config: resource permissions (database/functions/buckets etc.), role list/detail, app user list/detail, and gateway OPA authorization policies (aligned with CLI `tcb policy list/get`).\n\nExamples:\n- Query bucket permission: `action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\"`\n- List legacy gateway policies: `action=\"listPolicy\"` (returns an empty list on PG / OPA engine environments, consistent with the CLI)\n- Read user Rego: `action=\"getPolicy\"`; platform extension policy: `action=\"getPolicy\", extension=true`\n\n📌 Cross-backend boundary note: before calling, check `EnvInfo.RuntimeBackends` via `queryEnv(action=\"info\", envId=...)`. `resourceType=\"noSqlDatabase\"` queries CloudBase NoSQL collection rules, which are a separate mechanism from row-level security (RLS) on CloudBase PostgreSQL (PG) tables — in the same PG environment, if NoSQL collections are still in use, querying them with this tool **remains valid**. To query PG table RLS, use `queryPgDatabase(action=\"sql\", sql=\"SELECT * FROM pg_policies WHERE tablename=...\")` instead. This tool does not cover MySQL permissions.\n\n⚠️ PostgreSQL environments: the platform `DescribeResourcePermission` API rejects PG environments outright. When `resourceType=\"function\"`, this tool automatically falls back to the Manager SDK `describeEnvAuthzConfig` (aligned with CLI `tcb policy get`, reading `authz.user.rego`). For explicit OPA policies use `listPolicy` / `getPolicy`.\n\n⚠️ Role actions (`listRoles` / `getRole`) are rejected outright by the platform on **PostgreSQL environments** (`The current API does not support PostgreSQL type environments.`). This is a platform capability boundary, not a configuration problem — **do not keep retrying on PG**; use RLS (`managePgDatabase(action=\"execute\")` with `CREATE POLICY`) or the console to manage permissions instead.",
5250
5258
  manageTitle: "Manage CloudBase permissions and user config",
5251
- manageDescription: "Manage CloudBase permissions and user config: modify resource permissions (database/functions/buckets etc.), role management, member/policy add & remove, app user CRUD, and setting gateway OPA Rego policies (aligned with CLI `tcb policy set`).\n\nExamples:\n- Set a bucket to private: `action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"PRIVATE\"`\n- Create a role: `action=\"createRole\", roleName=\"admin\", roleIdentity=\"admin\"`\n- Open a cloud function to anonymous/unauthenticated access (PG goes through OPA, aligned with CLI `tcb policy set`): `action=\"updateResourcePermission\", resourceType=\"function\", resourceId=\"myFn\", permission=\"CUSTOM\", securityRule='{\"invoke\":true}'`\n- Set user Rego directly: `action=\"setPolicy\", regoContent=\"package authz.user\\n\\ndefault allow := false\\n\", confirm=true` (⚠️ immediately disables legacy gateway authorization)\n\nNote: `createUser` / `updateUser` are environment-side app user management capabilities, suitable for test accounts, administrators, or pre-provisioned users; they should not replace the Web SDK signup form in the browser. For frontend username/password signup use `auth.signUp({ username, password })` and for login `auth.signInWithPassword({ username, password })`. Creating username/password users directly in the browser with `auth.signUp` depends on SDK/provider support and must be verified before use; if unsupported, go through a backend or management API boundary and never expose keys in the browser. The detailed semantics of `securityRule` depend on `resourceType`: `doc._openid`, `auth.openid`, query-condition subset validation, and the `create` / `update` / `delete` JSON templates only apply to document database security rules with `resourceType=\"noSqlDatabase\"`; when configuring `function` or `storage`, refer to their official security rule docs instead of reusing NoSQL templates.\n\n📌 Cross-backend boundary note: before calling, check `EnvInfo.RuntimeBackends` via `envQuery(action=\"info\", envId=...)`:\n- `resourceType=\"noSqlDatabase\"` only affects CloudBase NoSQL document database collections; row-level permissions of CloudBase PostgreSQL (PG) tables are **not** controlled by it — for PG tables use RLS instead: run `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` and `CREATE POLICY ...` via `managePgDatabase(action=\"execute\", confirm=true)`. In the same PG environment, if NoSQL collections are still in use, continuing to use `noSqlDatabase` rules for those **collections** is correct — it does not mean \"this tool is disabled on PG environments\".\n- `resourceType=\"storage\"` controls NoSQL/COS bucket ACLs; PG `pgstore` buckets are not covered by this `resourceType`.\n- This tool does not cover MySQL; for MySQL database permissions use MySQL's own GRANT/REVOKE statements (via `manageMysqlDatabase`).\n\n⚠️ PostgreSQL environments: the platform `ModifyResourcePermission` API rejects PG environments outright. When `resourceType=\"function\"`, this tool automatically falls back to the Manager SDK `modifyEnvAuthzConfig` (aligned with CLI `tcb policy set`, writing `authz.user.rego`). `securityRule` accepts a full Rego document (`package authz.user`) or `'{\"invoke\":true}'` (auto-generates a policy allowing anonymous/unauthenticated calls to functions). Setting Rego disables legacy gateway authorization, same as the CLI. Prefer `action=\"setPolicy\"` for explicit OPA policies.",
5259
+ manageDescription: "Manage CloudBase permissions and user config: modify resource permissions (database/functions/buckets etc.), role management, member/policy add & remove, app user CRUD, and setting gateway OPA Rego policies (aligned with CLI `tcb policy set`).\n\nExamples:\n- Set a bucket to private: `action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"PRIVATE\"`\n- Create a role: `action=\"createRole\", roleName=\"admin\", roleIdentity=\"admin\"`\n- Open a cloud function to anonymous/unauthenticated access (PG goes through OPA, aligned with CLI `tcb policy set`): `action=\"updateResourcePermission\", resourceType=\"function\", resourceId=\"myFn\", permission=\"CUSTOM\", securityRule='{\"invoke\":true}'`\n- Set user Rego directly: `action=\"setPolicy\", regoContent=\"package authz.user\\n\\ndefault allow := false\\n\", confirm=true` (⚠️ immediately disables legacy gateway authorization)\n\nNote: `createUser` / `updateUser` are environment-side app user management capabilities, suitable for test accounts, administrators, or pre-provisioned users; they should not replace the Web SDK signup form in the browser. For frontend username/password signup use `auth.signUp({ username, password })` and for login `auth.signInWithPassword({ username, password })`. Creating username/password users directly in the browser with `auth.signUp` depends on SDK/provider support and must be verified before use; if unsupported, go through a backend or management API boundary and never expose keys in the browser. The detailed semantics of `securityRule` depend on `resourceType`: `doc._openid`, `auth.openid`, query-condition subset validation, and the `create` / `update` / `delete` JSON templates only apply to document database security rules with `resourceType=\"noSqlDatabase\"`; when configuring `function` or `storage`, refer to their official security rule docs instead of reusing NoSQL templates.\n\n📌 Cross-backend boundary note: before calling, check `EnvInfo.RuntimeBackends` via `queryEnv(action=\"info\", envId=...)`:\n- `resourceType=\"noSqlDatabase\"` only affects CloudBase NoSQL document database collections; row-level permissions of CloudBase PostgreSQL (PG) tables are **not** controlled by it — for PG tables use RLS instead: run `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` and `CREATE POLICY ...` via `managePgDatabase(action=\"execute\", confirm=true)`. In the same PG environment, if NoSQL collections are still in use, continuing to use `noSqlDatabase` rules for those **collections** is correct — it does not mean \"this tool is disabled on PG environments\".\n- `resourceType=\"storage\"` controls NoSQL/COS bucket ACLs; PG `pgstore` buckets are not covered by this `resourceType`.\n- This tool does not cover MySQL; for MySQL database permissions use MySQL's own GRANT/REVOKE statements (via `manageMysqlDatabase`).\n\n⚠️ PostgreSQL environments: the platform `ModifyResourcePermission` API rejects PG environments outright. When `resourceType=\"function\"`, this tool automatically falls back to the Manager SDK `modifyEnvAuthzConfig` (aligned with CLI `tcb policy set`, writing `authz.user.rego`). `securityRule` accepts a full Rego document (`package authz.user`) or `'{\"invoke\":true}'` (auto-generates a policy allowing anonymous/unauthenticated calls to functions). Setting Rego disables legacy gateway authorization, same as the CLI. Prefer `action=\"setPolicy\"` for explicit OPA policies.\n\n⚠️ Role actions (`createRole` / `deleteRoles` / `updateRole`) are rejected outright by the platform on **PostgreSQL environments** (`The current API does not support PostgreSQL type environments.`). This is a platform capability boundary, not a configuration problem — **do not keep retrying on PG**; use RLS (`managePgDatabase(action=\"execute\")` with `CREATE POLICY`) or the console to manage permissions instead.",
5252
5260
  writeVerificationHint: "For collections with backend permission control like {resourceId}, after the frontend calls .doc(id).update() / .doc(id).remove(), do not just check that no exception was thrown. Explicitly verify that updated / deleted in the result is greater than 0; if result.code or result.message exists, or updated / deleted is 0, treat it as a real failure and propagate the error.",
5253
5261
  permissionPropagationHint: "Right after updating security rules for {resourceId}, backend permissions usually take effect within a few seconds to about 30 seconds. If an immediate real write still returns DATABASE_PERMISSION_DENIED, wait a few seconds and retry the same .doc(id).update() / .doc(id).remove() with the same login state; do not wait blindly for minutes, do not immediately rewrite the rules repeatedly, and do not treat an old rejection during the short propagation window as evidence that the rule expression is still wrong.",
5254
5262
  validateRegoEmpty: "action=setPolicy requires non-empty regoContent (aligned with CLI `tcb policy set <regoContent>`).",
@@ -5346,11 +5354,11 @@ exports.rag = (0, types_js_1.defineModule)({
5346
5354
 
5347
5355
  返回内容包含该 skill 的 SKILL.md 全文,以及它在远端聚合仓(CNB raw)中的全部 .md 文件地址清单(SKILL.md 与 references/ 等,可直接 HTTP 抓取)。正文中代码栅栏之外的相对链接也会改写为绝对地址;若该 skill 在远端仓中不存在,则只返回内联内容并明确标注,不返回失效链接。
5348
5356
 
5349
- 固定技能文档 (skill) 查询当前支持 {skillCount} 个固定文档,分别是:
5350
- {skillList}
5357
+ 不确定该选哪个时:mode=skill 下不传 skillName、mode=openapi 下不传 apiName 直接调用,会返回当前可用清单及各自的适用场景 / 接口简介,再带上名称重新调用即可。可选名称也见本工具的 skillName / apiName 枚举(skill 共 {skillCount} 个,API 共 {openapiCount} 个)。
5351
5358
 
5352
- OpenAPI 文档 (openapi) 查询只需要传 mode="openapi" 和 apiName,不要传 action;action 仅用于 mode="docs"。当前支持 {openapiCount} 个 API 文档,分别是:
5353
- {openapiList}`,
5359
+ 注意:OpenAPI 文档 (openapi) 查询只需要传 mode="openapi" 和 apiName,不要传 action;action 仅用于 mode="docs"。`,
5360
+ skillCatalogHeader: "CloudBase 技能文档 (skill) 当前可用 {count} 个,用 mode=skill + skillName 读取其中一篇:",
5361
+ openapiCatalogHeader: "OpenAPI 文档 (openapi) 当前可用 {count} 个,用 mode=openapi + apiName 读取其中一篇:",
5354
5362
  skillListItem: "文档名:{name} 文档介绍:{description}",
5355
5363
  openapiListItem: "API名:{name} API介绍:{description}",
5356
5364
  paramRequired: "action={action} 时必须提供 {param}",
@@ -5394,11 +5402,11 @@ exports.rag = (0, types_js_1.defineModule)({
5394
5402
 
5395
5403
  The response contains the full SKILL.md of the skill plus the list of all its .md file URLs in the remote aggregate repo (CNB raw) — SKILL.md and references/ etc., fetchable over HTTP. Relative links outside code fences are rewritten to absolute URLs; when the skill is absent from the remote repo only the inline content is returned and that is stated explicitly, so no dead links are handed out.
5396
5404
 
5397
- Fixed skill doc (skill) queries currently support {skillCount} fixed docs:
5398
- {skillList}
5405
+ When unsure which one to pick: call with mode=skill and no skillName, or mode=openapi and no apiName, to get the current catalog with each entry's scenario / summary, then call again with the name. Available names are also listed in this tool's skillName / apiName enums (currently {skillCount} skills, {openapiCount} APIs).
5399
5406
 
5400
- OpenAPI doc (openapi) queries only need mode="openapi" and apiName; do not pass action — action is only for mode="docs". Currently {openapiCount} API docs are supported:
5401
- {openapiList}`,
5407
+ Note: OpenAPI doc (openapi) queries only need mode="openapi" and apiName; do not pass action — action is only for mode="docs".`,
5408
+ skillCatalogHeader: "CloudBase skill docs (skill), {count} available. Use mode=skill + skillName to read one:",
5409
+ openapiCatalogHeader: "OpenAPI docs (openapi), {count} available. Use mode=openapi + apiName to read one:",
5402
5410
  skillListItem: "Skill: {name} — Description: {description}",
5403
5411
  openapiListItem: "API: {name} — Description: {description}",
5404
5412
  paramRequired: "{param} is required when action={action}",
@@ -6177,7 +6185,8 @@ function registerAgentTools(server) {
6177
6185
  },
6178
6186
  annotations: {
6179
6187
  readOnlyHint: true,
6180
- openWorldHint: true,
6188
+ destructiveHint: false,
6189
+ openWorldHint: false,
6181
6190
  category: "agents",
6182
6191
  },
6183
6192
  }, async ({ action, agentId, pageNumber, pageSize, params, }) => {
@@ -6251,7 +6260,7 @@ function registerAgentTools(server) {
6251
6260
  readOnlyHint: false,
6252
6261
  destructiveHint: true,
6253
6262
  idempotentHint: false,
6254
- openWorldHint: true,
6263
+ openWorldHint: false,
6255
6264
  category: "agents",
6256
6265
  },
6257
6266
  }, async ({ action, agentId, name, description, runtime, timeout, memorySize, installDependency, zipFile, cosBucketRegion, tempCosObjectName, sessionConfig, cwd, params, }) => {
@@ -6706,7 +6715,8 @@ function registerAppAuthTools(server) {
6706
6715
  },
6707
6716
  annotations: {
6708
6717
  readOnlyHint: true,
6709
- openWorldHint: true,
6718
+ destructiveHint: false,
6719
+ openWorldHint: false,
6710
6720
  category: "auth",
6711
6721
  },
6712
6722
  }, async ({ action, providerId, clientId, keyType, pageNumber, pageSize, }) => withEnvelope(async () => {
@@ -6843,7 +6853,7 @@ function registerAppAuthTools(server) {
6843
6853
  readOnlyHint: false,
6844
6854
  destructiveHint: true,
6845
6855
  idempotentHint: false,
6846
- openWorldHint: true,
6856
+ openWorldHint: false,
6847
6857
  category: "auth",
6848
6858
  },
6849
6859
  }, async ({ action, patch, providerId, providerType, displayName, clientId, config, keyType, keyName, expireIn, keyId, }) => withEnvelope(async () => {
@@ -7139,9 +7149,15 @@ function registerAppTools(server) {
7139
7149
  .optional()
7140
7150
  .describe("版本名称。getAppVersion 时可与 buildId 二选一;已知版本号时优先传该值。"),
7141
7151
  buildId: zod_1.z
7142
- .string()
7152
+ // ⚠️ 同一字段驱动两条链路、类型要求不同(F13):
7153
+ // getAppVersion → SDK describeAppVersion,收 **string**
7154
+ // getBuildLog → 云 API DescribeCloudBaseRunBuildLog,BuildId 是 **Integer(int64)**
7155
+ // 收 string | number 并统一归一成 string(保证 getAppVersion 链路不回退),
7156
+ // getBuildLog 分支再转 number 传给云 API。
7157
+ .union([zod_1.z.string(), zod_1.z.number()])
7158
+ .transform((value) => String(value).trim())
7143
7159
  .optional()
7144
- .describe("构建 ID。getAppVersion 时可与 versionName 二选一;部署返回 BuildId 后可直接用它轮询状态。getBuildLog 时必填。"),
7160
+ .describe("构建 ID(数字或数字字符串均可)。getAppVersion 时可与 versionName 二选一;部署返回 BuildId 后可直接用它轮询状态。getBuildLog 时必填。"),
7145
7161
  start: zod_1.z
7146
7162
  .number()
7147
7163
  .optional()
@@ -7149,7 +7165,8 @@ function registerAppTools(server) {
7149
7165
  },
7150
7166
  annotations: {
7151
7167
  readOnlyHint: true,
7152
- openWorldHint: true,
7168
+ destructiveHint: false,
7169
+ openWorldHint: false,
7153
7170
  category: "apps",
7154
7171
  },
7155
7172
  }, async ({ action, serviceName, searchKey, pageNo, pageSize, versionName, buildId, start, }) => {
@@ -7243,15 +7260,22 @@ function registerAppTools(server) {
7243
7260
  }, (0, index_js_1.t)("apps.listVersionsSuccess")));
7244
7261
  }
7245
7262
  if (action === "getBuildLog") {
7246
- if (!buildId) {
7263
+ if (buildId === undefined || buildId === null || String(buildId).trim() === "") {
7247
7264
  throw new Error((0, index_js_1.t)("apps.buildIdRequired"));
7248
7265
  }
7266
+ // DescribeCloudBaseRunBuildLog 的 BuildId 是 Integer(int64)(官方文档 876/135707),
7267
+ // 传 string 会被后端拒:The value type of parameter BuildId is not valid, input type
7268
+ // should be int64。schema 侧已归一成 string(getAppVersion 需要 string),此处转 number。
7269
+ const numericBuildId = Number(String(buildId).trim());
7270
+ if (!Number.isInteger(numericBuildId) || numericBuildId <= 0) {
7271
+ throw new Error((0, index_js_1.t)("apps.buildIdMustBeNumeric", { buildId: String(buildId) }));
7272
+ }
7249
7273
  const result = await cloudbase.commonService("tcb", "2018-06-08").call({
7250
7274
  Action: "DescribeCloudBaseRunBuildLog",
7251
7275
  Param: {
7252
7276
  EnvId: cloudBaseOptions?.envId || process.env.CLOUDBASE_ENV_ID,
7253
7277
  ServiceName: serviceName,
7254
- BuildId: buildId,
7278
+ BuildId: numericBuildId,
7255
7279
  Start: start ?? 0,
7256
7280
  },
7257
7281
  });
@@ -7260,7 +7284,7 @@ function registerAppTools(server) {
7260
7284
  return (0, json_content_js_1.jsonContent)(buildEnvelope({
7261
7285
  action,
7262
7286
  serviceName,
7263
- buildId,
7287
+ buildId: String(numericBuildId),
7264
7288
  logs,
7265
7289
  total: result.Response?.Total || logs.length,
7266
7290
  nextStart: result.Response?.NextStart,
@@ -7273,7 +7297,8 @@ function registerAppTools(server) {
7273
7297
  deployType: "static-hosting",
7274
7298
  serviceName,
7275
7299
  versionName,
7276
- buildId,
7300
+ // 该链路要 string(SDK 类型 IDescribeCloudAppVersionParams.BuildId: string)
7301
+ buildId: buildId === undefined ? undefined : String(buildId),
7277
7302
  });
7278
7303
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
7279
7304
  // Platform may return Failed/failed; normalize before matching.
@@ -7325,12 +7350,21 @@ function registerAppTools(server) {
7325
7350
  .optional()
7326
7351
  .describe("要上传并部署的本地项目根目录绝对路径。本地模式下 deployApp 时必填;通常传源码所在目录(含 package.json 和源码),不是 dist 目录。构建产物目录请用 buildPath 指定。cloud mode 下无需传此参数,改用 cosTimestamp。"),
7327
7352
  cosTimestamp: zod_1.z
7328
- .coerce
7329
- .number()
7330
- .int()
7331
- .positive()
7353
+ // ⚠️ 不能用 z.coerce.number():SDK 的 StaticConfig.CosTimestamp(manager-node
7354
+ // types/cloudApp/types.d.ts `CosTimestamp?: string | null`)与后端 CreateCloudApp
7355
+ // 都要求 **string**。coerce 会把外部传入的值(含 getUploadUrl 返回的 unixTimestamp)
7356
+ // 强制转成 number,后端直接拒绝:
7357
+ // The value type of parameter `StaticConfig.CosTimestamp` is not valid,
7358
+ // input type should be `string`
7359
+ // 本地路径不受影响,是因为 cosTs 来自 SDK uploadCode() 的 string 返回值、不经 zod。
7360
+ // 因此这里收 string | number,统一归一成 string 后再透传。
7361
+ .union([zod_1.z.string(), zod_1.z.number()])
7362
+ .transform((value) => String(value).trim())
7363
+ .refine((value) => /^[1-9]\d*$/.test(value), {
7364
+ message: "cosTimestamp 必须是由数字组成的正整数时间戳,字符串或数字均可;直接使用 getUploadUrl 返回的 unixTimestamp 即可。",
7365
+ })
7332
7366
  .optional()
7333
- .describe("COS 时间戳(正整数 number,来自 getUploadUrl 返回的 unixTimestamp)。传入此值则直接使用已上传的代码创建应用,跳过本地文件上传。需先调用 getUploadUrl 获取预签名 URL,上传 ZIP 包后再传此时间戳。cloud mode 下为必填;本地模式也可传此值代替 filePath。两个路径严格二选一:filePath(本地打包上传)或 cosTimestamp(预签名 URL 上传),同时提供或都不提供都会报错。"),
7367
+ .describe("COS 时间戳(getUploadUrl 返回的 unixTimestamp,字符串或数字均可)。传入则直接用已上传的代码创建应用,跳过本地打包上传;需先 getUploadUrl 拿预签名 URL 并 PUT ZIP。cloud mode 必填。与 filePath 严格二选一,同时提供或都不提供都会报错。"),
7334
7368
  appPath: zod_1.z
7335
7369
  .string()
7336
7370
  .optional()
@@ -7374,7 +7408,7 @@ function registerAppTools(server) {
7374
7408
  readOnlyHint: false,
7375
7409
  destructiveHint: true,
7376
7410
  idempotentHint: false,
7377
- openWorldHint: true,
7411
+ openWorldHint: false,
7378
7412
  category: "apps",
7379
7413
  },
7380
7414
  }, async ({ action, serviceName, filePath, cosTimestamp, appPath, buildPath, framework, nodeJsVersion, installCmd, buildCmd, deployCmd, ignore, versionName, }) => {
@@ -7464,7 +7498,10 @@ function registerAppTools(server) {
7464
7498
  }
7465
7499
  }
7466
7500
  // Local stdio only: pack directory and upload. Cloud mode must never reach uploadCode.
7467
- let cosTs = cosTimestamp;
7501
+ // ⚠️ 类型契约(F12):SDK `StaticConfig.CosTimestamp` 与后端 CreateCloudApp 都要求
7502
+ // **string**。本地路径拿到的 `uploadResult.cosTimestamp` 本就是 SDK 给的 string;
7503
+ // 外部传入的可能是 number(MCP 客户端按 JSON Schema 传数字),这里统一归一。
7504
+ let cosTs = cosTimestamp === undefined ? undefined : String(cosTimestamp);
7468
7505
  if (!(0, cloud_mode_js_1.isCloudMode)() && filePath) {
7469
7506
  // Default excludes large build dirs (empirically target/ can be tens of GB).
7470
7507
  // Merge caller ignore with defaults so explicit ignore does not drop safety excludes.
@@ -7479,7 +7516,9 @@ function registerAppTools(server) {
7479
7516
  ignore: mergedIgnore,
7480
7517
  });
7481
7518
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, uploadResult);
7482
- cosTs = uploadResult.cosTimestamp;
7519
+ cosTs = uploadResult.cosTimestamp === undefined
7520
+ ? undefined
7521
+ : String(uploadResult.cosTimestamp);
7483
7522
  }
7484
7523
  // 构建命令智能默认值
7485
7524
  const resolvedInstallCmd = installCmd ?? "npm install";
@@ -8008,7 +8047,15 @@ function registerCapiTools(server) {
8008
8047
  const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions, requireEnvId: false });
8009
8048
  server.registerTool?.("callCloudApi", {
8010
8049
  title: "capi.title",
8011
- description: "capi.description",
8050
+ // ⚠️ 不能写成 `description: "capi.description"`(纯词典 key):注册包装层解析
8051
+ // key 时**不带插值参数**,而 {controlPlaneUrl}/{dependencyUrl} 只在错误路径
8052
+ // (下方 t("capi.errorBuild", …))传入 → 描述里的占位符永远替换不了,
8053
+ // tools/list 在 zh 与 en 下都会吐字面 `{controlPlaneUrl}`。
8054
+ // 因此这里按实例语言先解析成最终文案(与 env.ts 的 envQuery 别名同款处理)。
8055
+ description: (0, index_js_1.t)("capi.description", {
8056
+ controlPlaneUrl: CLOUDBASE_CONTROL_PLANE_DOC_URL,
8057
+ dependencyUrl: CLOUDBASE_DEPENDENCY_API_DOC_URL,
8058
+ }, server.lang),
8012
8059
  inputSchema: {
8013
8060
  service: zod_1.z
8014
8061
  .enum(exports.AV)
@@ -8037,7 +8084,7 @@ function registerCapiTools(server) {
8037
8084
  readOnlyHint: false,
8038
8085
  destructiveHint: true,
8039
8086
  idempotentHint: false,
8040
- openWorldHint: true,
8087
+ openWorldHint: false,
8041
8088
  category: CATEGORY,
8042
8089
  },
8043
8090
  }, async ({ service, action, params, version, region, }) => {
@@ -8335,6 +8382,7 @@ exports.buildCloudRunDeployNextStep = buildCloudRunDeployNextStep;
8335
8382
  exports.buildCloudRunDeployProgressHint = buildCloudRunDeployProgressHint;
8336
8383
  exports.queryCloudRunEnvStatus = queryCloudRunEnvStatus;
8337
8384
  exports.describeCloudRunEnvStatus = describeCloudRunEnvStatus;
8385
+ exports.isCloudRunEnvNotOpenedError = isCloudRunEnvNotOpenedError;
8338
8386
  exports.extractEnvBaseInfoVpc = extractEnvBaseInfoVpc;
8339
8387
  exports.resolveCloudRunDeployVpcInfo = resolveCloudRunDeployVpcInfo;
8340
8388
  exports.buildCreateCloudRunEnvParam = buildCreateCloudRunEnvParam;
@@ -8438,7 +8486,7 @@ const ManageCloudRunInputSchema = {
8438
8486
  PolicyThreshold: zod_1.z.number().min(1).max(100).describe('扩缩容阈值,单位为百分比。如60表示当资源使用率达到60%时触发扩缩容')
8439
8487
  })).optional().describe('扩缩容配置数组,用于配置服务的自动扩缩容策略。可配置多个扩缩容策略'),
8440
8488
  CustomLogs: zod_1.z.string().optional().describe('自定义日志配置,用于配置服务的日志收集和存储策略'),
8441
- Port: zod_1.z.number().min(1).max(65535).optional().describe('服务监听端口配置。函数型服务固定为3000,容器型服务可自定义。服务代码必须监听此端口才能正常接收请求'),
8489
+ Port: zod_1.z.number().min(1).max(65535).optional().describe('服务监听端口配置。函数型服务固定为3000(函数框架自身监听该端口,业务代码不要自行 app.listen);容器型服务可自定义,业务代码必须监听此端口'),
8442
8490
  EnvParams: zod_1.z.string().optional().describe('环境变量配置,JSON字符串格式。用于传递配置信息给服务代码,如\'{"DATABASE_URL":"postgres://user:pass@10.x.x.x:5432/db","NODE_ENV":"production"}\'。SDK v5.6.1+ 会自动对传入的环境变量进行 AES-256-CBC 加密传输。⚠️ 若 EnvParams 含 DATABASE_URL / MYSQL_* / POSTGRES_* / REDIS_* 等传统 TCP 连库变量,必须同时配置 VpcConf,否则实例通常无法访问 VPC 内数据库'),
8443
8491
  Dockerfile: zod_1.z.string().optional().describe('Dockerfile文件名配置,仅容器型服务需要。指定用于构建容器镜像的Dockerfile文件路径,默认为项目根目录下的Dockerfile'),
8444
8492
  BuildDir: zod_1.z.string().optional().describe('构建目录配置,指定代码构建的目录路径。当代码结构与标准不同时使用,默认为项目根目录'),
@@ -8915,6 +8963,36 @@ async function describeCloudRunEnvStatus(manager, envId) {
8915
8963
  : "unknown";
8916
8964
  return { isExist: true, status, baseInfo };
8917
8965
  }
8966
+ /**
8967
+ * 判断「云托管环境未开通 / 不存在」类错误,用于 `describeCloudRunEnvStatus` 失败的归一化分支。
8968
+ *
8969
+ * ⚠️ 必须**同时**看 `error.code` 与 `error.message`:
8970
+ * 报错文案有三套来源且会不一致 —— 英文错误码(`ResourceNotFound.CloudRunEnv`)、
8971
+ * 中文兜底(`[DescribeEnvBaseInfo] 资源不存在`)、旧式 `InvalidParameter.*Env`。
8972
+ * 只匹配 message 时,中文「资源不存在」不含 `ResourceNotFound`/`未开通`/`未初始化`,
8973
+ * 守卫会失效并把裸错误抛给模型(国际站未开通云托管的环境实测命中,见 F2)。
8974
+ *
8975
+ * 调用方只有 `DescribeEnvBaseInfo`(唯一资源就是环境),因此把「资源不存在」纳入
8976
+ * 未开通判定不会误伤其他语义。
8977
+ */
8978
+ function isCloudRunEnvNotOpenedError(error) {
8979
+ if (!error) {
8980
+ return false;
8981
+ }
8982
+ const codeCandidates = [
8983
+ error.code,
8984
+ error.Code,
8985
+ error.original?.code,
8986
+ error.original?.Code,
8987
+ ];
8988
+ const codes = codeCandidates
8989
+ .filter((value) => typeof value === "string")
8990
+ .join(" ");
8991
+ const message = error instanceof Error ? error.message : String(error);
8992
+ const haystack = `${codes} ${message}`;
8993
+ return (/ResourceNotFound|not.?initialized|未开通|未初始化|资源不存在|环境不存在/i.test(haystack) ||
8994
+ /InvalidParameter.*(?:Env|CloudRun)/i.test(haystack));
8995
+ }
8918
8996
  /** CloudRun EnvType for CreateCloudRunEnv (baas DescribeEnvBaseInfo enum). */
8919
8997
  exports.CLOUDRUN_ENV_TYPE = "baas";
8920
8998
  /**
@@ -9015,12 +9093,11 @@ async function ensureCloudRunEnvInitialized(options) {
9015
9093
  return true;
9016
9094
  }
9017
9095
  catch (error) {
9018
- const msg = error instanceof Error ? error.message : String(error);
9019
- // 错误兜底:未初始化/未开通类错误码。
9096
+ // 错误兜底:未初始化/未开通类错误码。判据统一收敛到 isCloudRunEnvNotOpenedError,
9097
+ // 避免「只匹配 message」被本地化文案(中文「资源不存在」)绕过。
9020
9098
  // 注意:裸 InvalidParameter 可能是普通参数错误,不拦截;仅当其带 Env/CloudRun
9021
9099
  // 上下文(疑似 "EnvironmentId not found / CloudRun Env 未开通")时才按未初始化处理。
9022
- if (/ResourceNotFound|not.?initialized|未开通|未初始化/i.test(msg) ||
9023
- /InvalidParameter.*(?:Env|CloudRun)/i.test(msg)) {
9100
+ if (isCloudRunEnvNotOpenedError(error)) {
9024
9101
  throwCloudRunEnvNotInitialized(options.envId);
9025
9102
  }
9026
9103
  // 其他错误(网络/权限等)不拦截,让上层按原逻辑处理。
@@ -9190,7 +9267,8 @@ function registerCloudRunTools(server) {
9190
9267
  inputSchema: queryCloudRunInputSchema,
9191
9268
  annotations: {
9192
9269
  readOnlyHint: true,
9193
- openWorldHint: true,
9270
+ destructiveHint: false,
9271
+ openWorldHint: false,
9194
9272
  category: "cloudrun"
9195
9273
  }
9196
9274
  }, async (args) => {
@@ -9584,8 +9662,7 @@ function registerCloudRunTools(server) {
9584
9662
  }
9585
9663
  catch (error) {
9586
9664
  const baseMessage = error instanceof Error ? error.message : String(error);
9587
- if (/ResourceNotFound|not.?initialized|未开通|未初始化/i.test(baseMessage) ||
9588
- /InvalidParameter.*(?:Env|CloudRun)/i.test(baseMessage)) {
9665
+ if (isCloudRunEnvNotOpenedError(error)) {
9589
9666
  status = { isExist: false, status: "unopened", baseInfo: {} };
9590
9667
  }
9591
9668
  else {
@@ -9650,7 +9727,7 @@ function registerCloudRunTools(server) {
9650
9727
  readOnlyHint: false,
9651
9728
  destructiveHint: true,
9652
9729
  idempotentHint: false,
9653
- openWorldHint: true,
9730
+ openWorldHint: false,
9654
9731
  category: "cloudrun"
9655
9732
  }
9656
9733
  }, async (args) => {
@@ -9678,8 +9755,7 @@ function registerCloudRunTools(server) {
9678
9755
  }
9679
9756
  catch (error) {
9680
9757
  const baseMessage = error instanceof Error ? error.message : String(error);
9681
- if (/ResourceNotFound|not.?initialized|未开通|未初始化/i.test(baseMessage) ||
9682
- /InvalidParameter.*(?:Env|CloudRun)/i.test(baseMessage)) {
9758
+ if (isCloudRunEnvNotOpenedError(error)) {
9683
9759
  current = { isExist: false, status: "unopened", baseInfo: {} };
9684
9760
  }
9685
9761
  else {
@@ -11269,7 +11345,8 @@ function registerDataModelTools(server) {
11269
11345
  },
11270
11346
  annotations: {
11271
11347
  readOnlyHint: true,
11272
- openWorldHint: true,
11348
+ destructiveHint: false,
11349
+ openWorldHint: false,
11273
11350
  category: "database",
11274
11351
  },
11275
11352
  }, async ({ action, name, names, }) => {
@@ -11608,7 +11685,7 @@ classDiagram
11608
11685
  readOnlyHint: false,
11609
11686
  destructiveHint: false,
11610
11687
  idempotentHint: false,
11611
- openWorldHint: true,
11688
+ openWorldHint: false,
11612
11689
  category: "database",
11613
11690
  },
11614
11691
  }, async ({ mermaidDiagram, action = "create", publish = false, dbInstanceType = "MYSQL", }) => {
@@ -12144,7 +12221,8 @@ checkIndex: 检查指定索引是否存在`),
12144
12221
  },
12145
12222
  annotations: {
12146
12223
  readOnlyHint: true,
12147
- openWorldHint: true,
12224
+ destructiveHint: false,
12225
+ openWorldHint: false,
12148
12226
  category: CATEGORY,
12149
12227
  },
12150
12228
  }, async ({ action, limit, offset, collectionName, indexName }) => {
@@ -12347,7 +12425,7 @@ deleteCollection: 删除集合`),
12347
12425
  readOnlyHint: false,
12348
12426
  destructiveHint: true,
12349
12427
  idempotentHint: false,
12350
- openWorldHint: true,
12428
+ openWorldHint: false,
12351
12429
  category: CATEGORY,
12352
12430
  },
12353
12431
  }, async ({ action, collectionName, updateOptions }) => {
@@ -12529,7 +12607,8 @@ deleteCollection: 删除集合`),
12529
12607
  },
12530
12608
  annotations: {
12531
12609
  readOnlyHint: true,
12532
- openWorldHint: true,
12610
+ destructiveHint: false,
12611
+ openWorldHint: false,
12533
12612
  category: CATEGORY,
12534
12613
  },
12535
12614
  }, async ({ collectionName, instanceId, query, projection, sort, limit, offset }) => {
@@ -12630,7 +12709,7 @@ deleteCollection: 删除集合`),
12630
12709
  readOnlyHint: false,
12631
12710
  destructiveHint: true,
12632
12711
  idempotentHint: false,
12633
- openWorldHint: true,
12712
+ openWorldHint: false,
12634
12713
  category: CATEGORY,
12635
12714
  },
12636
12715
  }, async ({ action, collectionName, instanceId, documents, query, update, isMulti, upsert, }) => {
@@ -15248,7 +15327,8 @@ function registerPGDatabaseTools(server, providedDeps) {
15248
15327
  },
15249
15328
  annotations: {
15250
15329
  readOnlyHint: true,
15251
- openWorldHint: true,
15330
+ destructiveHint: false,
15331
+ openWorldHint: false,
15252
15332
  category: CATEGORY,
15253
15333
  },
15254
15334
  }, async (args) => {
@@ -15407,7 +15487,7 @@ function registerPGDatabaseTools(server, providedDeps) {
15407
15487
  readOnlyHint: false,
15408
15488
  destructiveHint: true,
15409
15489
  idempotentHint: false,
15410
- openWorldHint: true,
15490
+ openWorldHint: false,
15411
15491
  category: CATEGORY,
15412
15492
  },
15413
15493
  }, async (args) => {
@@ -16417,7 +16497,8 @@ function registerSQLDatabaseTools(server) {
16417
16497
  },
16418
16498
  annotations: {
16419
16499
  readOnlyHint: true,
16420
- openWorldHint: true,
16500
+ destructiveHint: false,
16501
+ openWorldHint: false,
16421
16502
  category: CATEGORY,
16422
16503
  },
16423
16504
  }, async (args) => {
@@ -16486,7 +16567,7 @@ function registerSQLDatabaseTools(server) {
16486
16567
  readOnlyHint: false,
16487
16568
  destructiveHint: true,
16488
16569
  idempotentHint: false,
16489
- openWorldHint: true,
16570
+ openWorldHint: false,
16490
16571
  category: CATEGORY,
16491
16572
  },
16492
16573
  }, async (args) => {
@@ -16790,7 +16871,8 @@ function registerDeployTools(server) {
16790
16871
  annotations: {
16791
16872
  // 本地构建会执行 buildCommand 并写产物文件,不是 read-only;但不变更云端资源,非 destructive。
16792
16873
  readOnlyHint: false,
16793
- openWorldHint: true,
16874
+ destructiveHint: false,
16875
+ openWorldHint: false,
16794
16876
  category: "deploy",
16795
16877
  },
16796
16878
  }, async ({ cwd, mode }) => {
@@ -16862,7 +16944,8 @@ function registerDeployTools(server) {
16862
16944
  },
16863
16945
  annotations: {
16864
16946
  readOnlyHint: true,
16865
- openWorldHint: true,
16947
+ destructiveHint: false,
16948
+ openWorldHint: false,
16866
16949
  category: "deploy",
16867
16950
  },
16868
16951
  }, async ({ cwd, mode, envId: envIdInput, only, skip, yes, }) => {
@@ -16944,7 +17027,7 @@ function registerDeployTools(server) {
16944
17027
  annotations: {
16945
17028
  readOnlyHint: false,
16946
17029
  destructiveHint: true,
16947
- openWorldHint: true,
17030
+ openWorldHint: false,
16948
17031
  category: "deploy",
16949
17032
  },
16950
17033
  }, async ({ confirm, confirmDestructive, cwd, mode, envId: envIdInput, only, skip, yes, concurrency, continueOnError, }) => {
@@ -18708,7 +18791,7 @@ async function enrichEnvInfoWithMissingFields(manager, result, envId) {
18708
18791
  }
18709
18792
  }
18710
18793
  /**
18711
- * Project gateway Route.Enable onto envQuery(info) without mutating StaticDomain.
18794
+ * Project gateway Route.Enable onto queryEnv(info) without mutating StaticDomain.
18712
18795
  *
18713
18796
  * StaticStorages[].StaticDomain stays the cloud-API nominal hostname.
18714
18797
  * Sibling field staticDomainRouteEnabled (and EnvInfo-level mirrors for the
@@ -19267,7 +19350,7 @@ function registerEnvTools(server) {
19267
19350
  readOnlyHint: false,
19268
19351
  destructiveHint: false,
19269
19352
  idempotentHint: false,
19270
- openWorldHint: true,
19353
+ openWorldHint: false,
19271
19354
  category: "env",
19272
19355
  },
19273
19356
  }, async (rawArgs) => {
@@ -19982,14 +20065,7 @@ function registerEnvTools(server) {
19982
20065
  }
19983
20066
  (0, logger_js_1.debug)("降级到 listEnvs() 也失败:", fallbackError instanceof Error ? fallbackError : new Error(String(fallbackError)));
19984
20067
  const enhancedMessage = buildEnvQueryErrorMessage(fallbackError, "list");
19985
- return {
19986
- content: [
19987
- {
19988
- type: "text",
19989
- text: enhancedMessage,
19990
- },
19991
- ],
19992
- };
20068
+ return (0, tool_result_js_1.buildTextErrorResult)(enhancedMessage);
19993
20069
  }
19994
20070
  }
19995
20071
  result = buildEnvQueryListResult({
@@ -20172,14 +20248,7 @@ function registerEnvTools(server) {
20172
20248
  return toolPayloadResult;
20173
20249
  }
20174
20250
  const enhancedMessage = buildEnvQueryErrorMessage(error, action);
20175
- return {
20176
- content: [
20177
- {
20178
- type: "text",
20179
- text: enhancedMessage,
20180
- },
20181
- ],
20182
- };
20251
+ return (0, tool_result_js_1.buildTextErrorResult)(enhancedMessage);
20183
20252
  }
20184
20253
  };
20185
20254
  // Register primary tool name (queryEnv)
@@ -20249,7 +20318,8 @@ function registerEnvTools(server) {
20249
20318
  },
20250
20319
  annotations: {
20251
20320
  readOnlyHint: true,
20252
- openWorldHint: true,
20321
+ destructiveHint: false,
20322
+ openWorldHint: false,
20253
20323
  category: "env",
20254
20324
  },
20255
20325
  };
@@ -20281,9 +20351,9 @@ function registerEnvTools(server) {
20281
20351
  },
20282
20352
  annotations: {
20283
20353
  readOnlyHint: false,
20284
- destructiveHint: false, // 注意:delete操作虽然是破坏性的,但这里采用较宽松的标注
20354
+ destructiveHint: true,
20285
20355
  idempotentHint: false,
20286
- openWorldHint: true,
20356
+ openWorldHint: false,
20287
20357
  category: "env",
20288
20358
  },
20289
20359
  }, async ({ action, domains, }) => {
@@ -20313,16 +20383,9 @@ function registerEnvTools(server) {
20313
20383
  if (toolPayloadResult) {
20314
20384
  return toolPayloadResult;
20315
20385
  }
20316
- return {
20317
- content: [
20318
- {
20319
- type: "text",
20320
- text: (0, index_js_1.t)("env.domain.operationFailed", {
20321
- message: error instanceof Error ? error.message : String(error),
20322
- }),
20323
- },
20324
- ],
20325
- };
20386
+ return (0, tool_result_js_1.buildTextErrorResult)((0, index_js_1.t)("env.domain.operationFailed", {
20387
+ message: error instanceof Error ? error.message : String(error),
20388
+ }));
20326
20389
  }
20327
20390
  });
20328
20391
  } // end: wxide guard for envDomainManagement
@@ -20374,7 +20437,7 @@ function registerEnvTools(server) {
20374
20437
  readOnlyHint: false,
20375
20438
  destructiveHint: true,
20376
20439
  idempotentHint: false,
20377
- openWorldHint: true,
20440
+ openWorldHint: false,
20378
20441
  category: "env",
20379
20442
  },
20380
20443
  }, async (rawArgs) => {
@@ -20848,14 +20911,7 @@ function registerEnvTools(server) {
20848
20911
  message: (0, index_js_1.t)("env.manage.listPackagesFailed", { message: errorMessage }),
20849
20912
  });
20850
20913
  }
20851
- return {
20852
- content: [
20853
- {
20854
- type: "text",
20855
- text: (0, index_js_1.t)("env.manage.operationFailed", { message: errorMessage }),
20856
- },
20857
- ],
20858
- };
20914
+ return (0, tool_result_js_1.buildTextErrorResult)((0, index_js_1.t)("env.manage.operationFailed", { message: errorMessage }));
20859
20915
  }
20860
20916
  });
20861
20917
  }
@@ -24013,7 +24069,8 @@ function registerFunctionTools(server) {
24013
24069
  },
24014
24070
  annotations: {
24015
24071
  readOnlyHint: true,
24016
- openWorldHint: true,
24072
+ destructiveHint: false,
24073
+ openWorldHint: false,
24017
24074
  category: "functions",
24018
24075
  },
24019
24076
  }, async (input) => withEnvelope(() => handleQueryFunctions(input)));
@@ -24098,7 +24155,7 @@ function registerFunctionTools(server) {
24098
24155
  readOnlyHint: false,
24099
24156
  destructiveHint: true,
24100
24157
  idempotentHint: false,
24101
- openWorldHint: true,
24158
+ openWorldHint: false,
24102
24159
  category: "functions",
24103
24160
  },
24104
24161
  }, async (input) => withEnvelope(() => handleManageFunctions(input)));
@@ -24202,6 +24259,123 @@ function registerGatewayTools(server) {
24202
24259
  data: {},
24203
24260
  message: error instanceof Error ? error.message : String(error),
24204
24261
  });
24262
+ /**
24263
+ * 上游目标存在性校验(F1)。
24264
+ *
24265
+ * 背景:`createHttpServiceRoute` 不校验 `UpstreamResourceName` 是否存在,预检
24266
+ * (VerifyHTTPServiceRoute)也只查域名归属/证书/配额,因此**不存在的上游也能真实落库并
24267
+ * 返回 success**,形成悬空路由(path 被占用、指向不存在的目标)。实测两站复现。
24268
+ *
24269
+ * 设计原则:**只在能证明「不存在」时拒绝**。
24270
+ * - 列表接口报错、分页未扫全、类型不支持 → `unknown`,放行并记日志(fail-open)。
24271
+ * 校验失败绝不该阻塞用户创建合法路由。
24272
+ * - 只有扫完全量且确实没有该名称时,才返回 `missing`。
24273
+ *
24274
+ * 覆盖范围:SCF / WEB_SCF(函数列表)、CBR(云托管服务列表)。
24275
+ * NOT covered:STATIC_STORE 的 UpstreamResourceName 是静态托管**实例名/固定别名
24276
+ * staticstore**(不是 CloudApp 应用名),可靠列举要额外走 DescribeStaticStore,且该资源
24277
+ * 随托管启用必然存在、悬空风险低;LH(轻量应用服务器)无列举接口。两者一律 fail-open。
24278
+ */
24279
+ const UPSTREAM_PROBE_PAGE_SIZE = 100;
24280
+ const UPSTREAM_PROBE_MAX_PAGES = 5;
24281
+ const UPSTREAM_PROBE_CANDIDATE_LIMIT = 10;
24282
+ const probeUpstreamResource = async (cloudbase, params) => {
24283
+ const wanted = params.upstreamResourceName.trim();
24284
+ const upstreamResourceType = params.upstreamResourceType;
24285
+ try {
24286
+ if (upstreamResourceType === "SCF" || upstreamResourceType === "WEB_SCF") {
24287
+ // fail-open:老 SDK / mock 没有该 API 时不校验,绝不让「拿不到列表」变成「判定不存在」。
24288
+ if (typeof cloudbase.functions?.getFunctionList !== "function") {
24289
+ return { status: "unknown", reason: "function-list-api-unavailable" };
24290
+ }
24291
+ const names = new Set();
24292
+ let scanned = 0;
24293
+ let total = Number.POSITIVE_INFINITY;
24294
+ for (let page = 0; page < UPSTREAM_PROBE_MAX_PAGES; page += 1) {
24295
+ const res = await cloudbase.functions.getFunctionList(UPSTREAM_PROBE_PAGE_SIZE, scanned);
24296
+ const list = Array.isArray(res?.Functions) ? res.Functions : [];
24297
+ for (const item of list) {
24298
+ const name = item?.FunctionName;
24299
+ if (typeof name === "string" && name) {
24300
+ names.add(name);
24301
+ }
24302
+ }
24303
+ scanned += list.length;
24304
+ total = typeof res?.TotalCount === "number" ? res.TotalCount : scanned;
24305
+ if (names.has(wanted) || list.length === 0 || scanned >= total) {
24306
+ break;
24307
+ }
24308
+ }
24309
+ if (names.has(wanted)) {
24310
+ return { status: "exists" };
24311
+ }
24312
+ if (scanned >= total) {
24313
+ return {
24314
+ status: "missing",
24315
+ candidates: [...names].slice(0, UPSTREAM_PROBE_CANDIDATE_LIMIT),
24316
+ };
24317
+ }
24318
+ return { status: "unknown", reason: "function-list-partially-scanned" };
24319
+ }
24320
+ if (upstreamResourceType === "CBR") {
24321
+ // fail-open:同上,`cloudrun?.list` 缺失时会短路成 undefined,若不显式拦截会被
24322
+ // 误判成「列表为空 = 目标不存在」,从而错误拒绝合法路由。
24323
+ if (typeof cloudbase.cloudrun?.list !== "function") {
24324
+ return { status: "unknown", reason: "cloudrun-list-api-unavailable" };
24325
+ }
24326
+ const names = new Set();
24327
+ for (let page = 1; page <= UPSTREAM_PROBE_MAX_PAGES; page += 1) {
24328
+ const res = await cloudbase.cloudrun.list({
24329
+ pageSize: UPSTREAM_PROBE_PAGE_SIZE,
24330
+ pageNum: page,
24331
+ });
24332
+ const list = Array.isArray(res?.ServerList) ? res.ServerList : [];
24333
+ for (const item of list) {
24334
+ const record = item;
24335
+ const name = record?.ServerName ?? record?.Name;
24336
+ if (typeof name === "string" && name) {
24337
+ names.add(name);
24338
+ }
24339
+ }
24340
+ const total = typeof res?.Total === "number" ? res.Total : names.size;
24341
+ if (names.has(wanted) || list.length === 0 || names.size >= total) {
24342
+ break;
24343
+ }
24344
+ }
24345
+ if (names.has(wanted)) {
24346
+ return { status: "exists" };
24347
+ }
24348
+ return {
24349
+ status: "missing",
24350
+ candidates: [...names].slice(0, UPSTREAM_PROBE_CANDIDATE_LIMIT),
24351
+ };
24352
+ }
24353
+ // STATIC_STORE / LH:无可靠的实例列举通路,不校验(见上方说明)。
24354
+ return { status: "unknown", reason: `unsupported-upstream-type:${upstreamResourceType}` };
24355
+ }
24356
+ catch (error) {
24357
+ const message = error instanceof Error ? error.message : String(error);
24358
+ return { status: "unknown", reason: message };
24359
+ }
24360
+ };
24361
+ /** 上游缺失时的候选列举工具,按类型给出下一步可用的只读入口。 */
24362
+ const upstreamProbeNextAction = (upstreamResourceType) => {
24363
+ if (upstreamResourceType === "SCF" || upstreamResourceType === "WEB_SCF") {
24364
+ return {
24365
+ tool: "queryFunctions",
24366
+ action: "listFunctions",
24367
+ reason: (0, index_js_1.t)("gateway.create.upstreamNotFoundReason"),
24368
+ };
24369
+ }
24370
+ if (upstreamResourceType === "CBR") {
24371
+ return {
24372
+ tool: "queryCloudRun",
24373
+ action: "list",
24374
+ reason: (0, index_js_1.t)("gateway.create.upstreamNotFoundReason"),
24375
+ };
24376
+ }
24377
+ return null;
24378
+ };
24205
24379
  const verifyHttpServiceRouteOrFail = async (params) => {
24206
24380
  const cloudbase = await getManager();
24207
24381
  let result;
@@ -24688,6 +24862,46 @@ function registerGatewayTools(server) {
24688
24862
  case "createRoute": {
24689
24863
  const cloudbase = await getManager();
24690
24864
  const payload = await normalizeRoutePayload(input);
24865
+ // 上游目标存在性校验(F1):平台侧不校验 UpstreamResourceName,不存在的目标也会
24866
+ // 真实落库形成悬空路由并返回 success。能证明不存在就拒绝,校验不了就放行(fail-open)。
24867
+ const upstreamProbe = await probeUpstreamResource(cloudbase, {
24868
+ upstreamResourceType: payload.resolved.upstreamResourceType,
24869
+ upstreamResourceName: payload.resolved.upstreamResourceName,
24870
+ });
24871
+ if (upstreamProbe.status === "missing") {
24872
+ const nextAction = upstreamProbeNextAction(payload.resolved.upstreamResourceType);
24873
+ return {
24874
+ success: false,
24875
+ data: {
24876
+ action: "createRoute",
24877
+ upstreamResourceType: payload.resolved.upstreamResourceType,
24878
+ targetName: payload.resolved.upstreamResourceName,
24879
+ path: payload.resolved.path,
24880
+ candidates: upstreamProbe.candidates,
24881
+ ...(upstreamProbe.candidates.length
24882
+ ? {
24883
+ candidateHint: (0, index_js_1.t)("gateway.create.upstreamCandidates", {
24884
+ type: payload.resolved.upstreamResourceType,
24885
+ candidates: upstreamProbe.candidates.join("、"),
24886
+ }),
24887
+ }
24888
+ : {}),
24889
+ },
24890
+ message: (0, index_js_1.t)("gateway.create.upstreamNotFound", {
24891
+ type: payload.resolved.upstreamResourceType,
24892
+ name: payload.resolved.upstreamResourceName,
24893
+ }),
24894
+ ...(nextAction ? { nextActions: [nextAction] } : {}),
24895
+ };
24896
+ }
24897
+ if (upstreamProbe.status === "unknown") {
24898
+ // 校验不了不阻塞创建,但要留痕,便于排查「为什么这次没拦住」。
24899
+ server.logger?.({
24900
+ type: "errorToolCall",
24901
+ toolName: "manageGateway",
24902
+ message: `createRoute upstream probe skipped (${payload.resolved.upstreamResourceType}/${payload.resolved.upstreamResourceName}): ${upstreamProbe.reason}`,
24903
+ });
24904
+ }
24691
24905
  // Probe → create: ownership / cert / quota / conflict checks before create.
24692
24906
  const verifyFailure = await verifyHttpServiceRouteOrFail({
24693
24907
  envId: payload.EnvId,
@@ -25147,7 +25361,8 @@ function registerGatewayTools(server) {
25147
25361
  },
25148
25362
  annotations: {
25149
25363
  readOnlyHint: true,
25150
- openWorldHint: true,
25364
+ destructiveHint: false,
25365
+ openWorldHint: false,
25151
25366
  category: "gateway",
25152
25367
  },
25153
25368
  }, async (input) => withEnvelope(() => handleQueryGateway(input)));
@@ -25265,7 +25480,7 @@ function registerGatewayTools(server) {
25265
25480
  readOnlyHint: false,
25266
25481
  destructiveHint: true,
25267
25482
  idempotentHint: false,
25268
- openWorldHint: true,
25483
+ openWorldHint: false,
25269
25484
  category: "gateway",
25270
25485
  },
25271
25486
  }, async (input) => withEnvelope(() => handleManageGateway(input)));
@@ -25707,6 +25922,15 @@ function buildWebsiteConfigNextStep() {
25707
25922
  },
25708
25923
  };
25709
25924
  }
25925
+ /**
25926
+ * 静态托管的对象 key 一律不带前导斜杠(`dir/f.txt` 而非 `/dir/f.txt`),但 agent
25927
+ * 从 URL 形态(`/index.html`)推路径时很自然带上斜杠。底层 SDK 的 `getCloudKey`
25928
+ * 只补尾部 `/`、不剥离前导 `/`,随后 `getBucket({ Prefix })` 做的是严格字节前缀
25929
+ * 匹配 ⇒ `/dir/` 匹配不到 `dir/f.txt`。因此所有对外传入的路径/前缀都先在此归一化。
25930
+ */
25931
+ function stripLeadingSlashes(value) {
25932
+ return value.replace(/^\/+/, '');
25933
+ }
25710
25934
  function buildFindFilesNextStep(prefix) {
25711
25935
  return {
25712
25936
  tool: 'queryHosting',
@@ -25935,7 +26159,8 @@ function registerHostingTools(server) {
25935
26159
  inputSchema: queryHostingInputSchema,
25936
26160
  annotations: {
25937
26161
  readOnlyHint: true,
25938
- openWorldHint: true,
26162
+ destructiveHint: false,
26163
+ openWorldHint: false,
25939
26164
  category: 'hosting',
25940
26165
  },
25941
26166
  }, async (args) => {
@@ -25978,8 +26203,11 @@ function registerHostingTools(server) {
25978
26203
  if (!input.prefix) {
25979
26204
  throw new Error((0, index_js_1.t)('hosting.findFilesPrefixRequired'));
25980
26205
  }
26206
+ // 读侧与 delete 侧同源:prefix 原样透传时,agent 按 URL 形态传的
26207
+ // `/assets/` 在 COS 侧严格前缀匹配命中 0 条,会被误读成「目录是空的」。
26208
+ const normalizedPrefix = stripLeadingSlashes(input.prefix);
25981
26209
  const result = await cloudbase.hosting.findFiles({
25982
- prefix: input.prefix,
26210
+ prefix: normalizedPrefix,
25983
26211
  marker: input.marker,
25984
26212
  maxKeys: input.maxKeys,
25985
26213
  });
@@ -25991,7 +26219,7 @@ function registerHostingTools(server) {
25991
26219
  success: true,
25992
26220
  data: {
25993
26221
  action: 'findFiles',
25994
- prefix: input.prefix,
26222
+ prefix: normalizedPrefix,
25995
26223
  marker: input.marker,
25996
26224
  maxKeys: input.maxKeys,
25997
26225
  files: normalizedFiles,
@@ -26000,7 +26228,7 @@ function registerHostingTools(server) {
26000
26228
  result,
26001
26229
  },
26002
26230
  message: (0, index_js_1.t)('hosting.findFilesSuccess', {
26003
- prefix: input.prefix,
26231
+ prefix: normalizedPrefix,
26004
26232
  count: normalizedFiles.length,
26005
26233
  more: nextMarker ? (0, index_js_1.t)('hosting.findFilesMore') : '',
26006
26234
  }),
@@ -26089,7 +26317,7 @@ function registerHostingTools(server) {
26089
26317
  readOnlyHint: false,
26090
26318
  destructiveHint: true,
26091
26319
  idempotentHint: false,
26092
- openWorldHint: true,
26320
+ openWorldHint: false,
26093
26321
  category: 'hosting',
26094
26322
  },
26095
26323
  }, async (args) => {
@@ -26251,10 +26479,18 @@ function registerHostingTools(server) {
26251
26479
  if (!input.confirm) {
26252
26480
  throw new Error((0, index_js_1.t)('hosting.deleteConfirmRequired'));
26253
26481
  }
26482
+ // 静态托管对象 key 不带前导斜杠。若不规范化,isDir=true 会走
26483
+ // storage.deleteDirectoryCustom,其前缀由 getCloudKey(cloudPath)
26484
+ // 生成且不做前导斜杠剥离,做严格字节前缀匹配时 `/dir/` 匹配不到
26485
+ // `dir/f.txt`,导致目录删除静默 no-op(Deleted:0,文件全在)。
26486
+ // 同时下面的回查 findFiles(prefix) 也是严格前缀匹配,带斜杠时
26487
+ // 同样命中 0 条,会把「没删到」误判成「删干净了 verified:true」。
26488
+ // 这里统一剥离前导斜杠,两种形态(/dir 与 dir)都按正确 key 处理。
26489
+ const normalizedCloudPath = stripLeadingSlashes(input.cloudPath);
26254
26490
  let result;
26255
26491
  try {
26256
26492
  result = await cloudbase.hosting.deleteFiles({
26257
- cloudPath: input.cloudPath,
26493
+ cloudPath: normalizedCloudPath,
26258
26494
  isDir: input.isDir ?? false,
26259
26495
  });
26260
26496
  }
@@ -26276,10 +26512,28 @@ function registerHostingTools(server) {
26276
26512
  }
26277
26513
  try {
26278
26514
  const checkResult = await cloudbase.hosting.findFiles({
26279
- prefix: input.cloudPath,
26280
- maxKeys: 1,
26515
+ prefix: normalizedCloudPath,
26516
+ maxKeys: 100,
26281
26517
  });
26282
- if (Array.isArray(checkResult) && checkResult.length > 0) {
26518
+ // ⚠️ 回查有两个坑,都会让 verified 失真:
26519
+ // 1) findFiles 返回 **COS 风格对象**(列表在 Contents 内),直接用
26520
+ // `Array.isArray(checkResult)` 判断会静默失效、恒判 verified=true。
26521
+ // 统一走 extractFileList 归一化。
26522
+ // 2) findFiles 是 **prefix** 语义:删单文件 /a/b.txt 时,若同前缀的
26523
+ // /a/b.txt.bak 还在也会命中 → 把「已删成功」误判成未验证。
26524
+ // 单文件场景必须精确比对 Key(前缀场景见 normalizeFileFields 的 key 字段)。
26525
+ const remaining = normalizeFileFields(checkResult);
26526
+ const targetPath = normalizedCloudPath;
26527
+ const stillExists = input.isDir
26528
+ ? remaining.length > 0
26529
+ : remaining.some((file) => {
26530
+ const key = typeof file.key === 'string' ? file.key : '';
26531
+ // 拿不到 Key 时保守按「仍存在」处理,避免误报已验证
26532
+ if (!key)
26533
+ return true;
26534
+ return stripLeadingSlashes(key) === targetPath;
26535
+ });
26536
+ if (stillExists) {
26283
26537
  deleteVerified = false;
26284
26538
  verificationError = verificationError ?? (0, index_js_1.t)('hosting.deleteVerifyFailed');
26285
26539
  }
@@ -26303,7 +26557,11 @@ function registerHostingTools(server) {
26303
26557
  type: input.isDir ? (0, index_js_1.t)('hosting.typeDirectory') : (0, index_js_1.t)('hosting.typeFile'),
26304
26558
  cloudPath: input.cloudPath,
26305
26559
  })
26306
- : (0, index_js_1.t)('hosting.deleteUnverified', { cloudPath: input.cloudPath }),
26560
+ // 这里刻意插入规范化后的路径:该文案的 {cloudPath} 会被拼进
26561
+ // `queryHosting(action="findFiles", prefix="{cloudPath}")` 自查命令里,
26562
+ // 带前导斜杠的前缀在 COS 侧严格匹配不到任何对象,agent 照抄会得到
26563
+ // 「0 命中 ⇒ 看起来删干净了」的错误结论。
26564
+ : (0, index_js_1.t)('hosting.deleteUnverified', { cloudPath: normalizedCloudPath }),
26307
26565
  });
26308
26566
  }
26309
26567
  case 'setWebsiteDocument': {
@@ -26541,7 +26799,8 @@ function registerLogTools(server) {
26541
26799
  },
26542
26800
  annotations: {
26543
26801
  readOnlyHint: true,
26544
- openWorldHint: true,
26802
+ destructiveHint: false,
26803
+ openWorldHint: false,
26545
26804
  category: "logs",
26546
26805
  },
26547
26806
  }, async ({ action, queryString, service, startTime, endTime, limit, context, sort, }) => {
@@ -27090,7 +27349,8 @@ function registerMsgPushTools(server) {
27090
27349
  },
27091
27350
  annotations: {
27092
27351
  readOnlyHint: true,
27093
- openWorldHint: true,
27352
+ destructiveHint: false,
27353
+ openWorldHint: false,
27094
27354
  category: CATEGORY,
27095
27355
  },
27096
27356
  }, async ({ appid, env, action }) => {
@@ -27235,7 +27495,7 @@ function registerMsgPushTools(server) {
27235
27495
  readOnlyHint: false,
27236
27496
  destructiveHint: true,
27237
27497
  idempotentHint: true,
27238
- openWorldHint: true,
27498
+ openWorldHint: false,
27239
27499
  category: CATEGORY,
27240
27500
  },
27241
27501
  }, async ({ appid, env_id, function_name, action, msg_type, event_types, enable, qbase_container_path, qbase_env, text_mode, confirm, }) => {
@@ -28104,7 +28364,8 @@ function registerPermissionTools(server) {
28104
28364
  },
28105
28365
  annotations: {
28106
28366
  readOnlyHint: true,
28107
- openWorldHint: true,
28367
+ destructiveHint: false,
28368
+ openWorldHint: false,
28108
28369
  category: "permissions",
28109
28370
  },
28110
28371
  }, async ({ action, resourceType, resourceId, resourceIds, roleId, roleIdentity, roleName, uid, username, pageNo, pageSize, extension, policyResourceType, }) => withEnvelope(async () => {
@@ -28333,7 +28594,7 @@ function registerPermissionTools(server) {
28333
28594
  readOnlyHint: false,
28334
28595
  destructiveHint: true,
28335
28596
  idempotentHint: false,
28336
- openWorldHint: true,
28597
+ openWorldHint: false,
28337
28598
  category: "permissions",
28338
28599
  },
28339
28600
  }, async ({ action, resourceType, resourceId, permission, securityRule, roleId, roleIds, roleName, roleIdentity, description, memberUids, policies, policyIds, uid, uids, username, password, userStatus, regoContent, confirm, }) => withEnvelope(async () => {
@@ -29079,16 +29340,7 @@ async function registerRagTools(server) {
29079
29340
  title: "rag.title",
29080
29341
  description: (0, index_js_1.t)("rag.description", {
29081
29342
  skillCount: skills.length,
29082
- skillList: skills
29083
- .map((skill) => (0, index_js_1.t)("rag.skillListItem", {
29084
- name: path.basename(path.dirname(skill.absolutePath)),
29085
- description: skill.description,
29086
- }))
29087
- .join("\n"),
29088
29343
  openapiCount: openapis.length,
29089
- openapiList: openapis
29090
- .map((api) => (0, index_js_1.t)("rag.openapiListItem", { name: api.name, description: api.description }))
29091
- .join("\n"),
29092
29344
  }),
29093
29345
  inputSchema: {
29094
29346
  mode: SearchKnowledgeModeEnum,
@@ -29114,6 +29366,7 @@ async function registerRagTools(server) {
29114
29366
  },
29115
29367
  annotations: {
29116
29368
  readOnlyHint: true,
29369
+ destructiveHint: false,
29117
29370
  openWorldHint: true,
29118
29371
  category: "rag",
29119
29372
  },
@@ -29165,6 +29418,16 @@ async function registerRagTools(server) {
29165
29418
  }
29166
29419
  }
29167
29420
  if (mode === "skill") {
29421
+ // 不传 skillName:返回带适用场景的完整目录,让调用方按需发现后再取正文
29422
+ if (!skillName?.trim()) {
29423
+ return (0, json_content_js_1.jsonContent)([
29424
+ (0, index_js_1.t)("rag.skillCatalogHeader", { count: skillNames.length }),
29425
+ ...skills.map((item) => (0, index_js_1.t)("rag.skillListItem", {
29426
+ name: path.basename(path.dirname(item.absolutePath)),
29427
+ description: item.description,
29428
+ })),
29429
+ ].join("\n"));
29430
+ }
29168
29431
  const skill = skills.find((item) => item.absolutePath.includes(skillName));
29169
29432
  if (!skill) {
29170
29433
  const remoteHint = skillName?.trim()
@@ -29215,6 +29478,15 @@ async function registerRagTools(server) {
29215
29478
  return { content: [{ type: "text", text: sections.join("\n\n") }] };
29216
29479
  }
29217
29480
  if (mode === "openapi") {
29481
+ if (!apiName?.trim()) {
29482
+ return (0, json_content_js_1.jsonContent)([
29483
+ (0, index_js_1.t)("rag.openapiCatalogHeader", { count: openapiNames.length }),
29484
+ ...openapis.map((api) => (0, index_js_1.t)("rag.openapiListItem", {
29485
+ name: api.name,
29486
+ description: api.description,
29487
+ })),
29488
+ ].join("\n"));
29489
+ }
29218
29490
  const api = openapis.find((api) => api.name === apiName);
29219
29491
  if (!api) {
29220
29492
  return {
@@ -29271,7 +29543,7 @@ function extractDescriptionFromFrontMatter(content) {
29271
29543
  fm.push(lines[i]);
29272
29544
  const match = fm
29273
29545
  .join("\n")
29274
- .match(/^(?:decsription|description)\s*:\s*(.*)$/m);
29546
+ .match(/^description\s*:\s*(.*)$/m);
29275
29547
  return match ? match[1].trim() : null;
29276
29548
  }
29277
29549
  async function collectSkillDescriptions(rootDir) {
@@ -30237,7 +30509,8 @@ function registerStorageTools(server) {
30237
30509
  inputSchema: queryStorageInputSchema,
30238
30510
  annotations: {
30239
30511
  readOnlyHint: true,
30240
- openWorldHint: true,
30512
+ destructiveHint: false,
30513
+ openWorldHint: false,
30241
30514
  category: "storage"
30242
30515
  }
30243
30516
  }, async (args) => {
@@ -30417,7 +30690,7 @@ function registerStorageTools(server) {
30417
30690
  readOnlyHint: false,
30418
30691
  destructiveHint: true,
30419
30692
  idempotentHint: false,
30420
- openWorldHint: true,
30693
+ openWorldHint: false,
30421
30694
  category: "storage"
30422
30695
  }
30423
30696
  }, async (args) => {
@@ -30887,7 +31160,8 @@ function registerPGStorageTools(server) {
30887
31160
  },
30888
31161
  annotations: {
30889
31162
  readOnlyHint: true,
30890
- openWorldHint: true,
31163
+ destructiveHint: false,
31164
+ openWorldHint: false,
30891
31165
  category: CATEGORY,
30892
31166
  },
30893
31167
  }, async (args) => {
@@ -32134,7 +32408,7 @@ exports.SITE_REGION_MAP = {
32134
32408
  },
32135
32409
  };
32136
32410
  exports.dB = Object.keys(exports.SITE_REGION_MAP);
32137
- /** Regions accepted by envQuery(list) / cross-region DescribeEnvs probes. */
32411
+ /** Regions accepted by queryEnv(list) / cross-region DescribeEnvs probes. */
32138
32412
  exports.TCB_QUERY_REGIONS = [
32139
32413
  "ap-shanghai",
32140
32414
  "ap-guangzhou",
@@ -32504,7 +32778,7 @@ class TelemetryReporter {
32504
32778
  const arch = os_1.default.arch(); // 系统架构
32505
32779
  // Build-time injected version; guard for vitest / non-webpack runtimes.
32506
32780
  const mcpVersion = process.env.npm_package_version ||
32507
- ( true ? "2.34.2" : 0);
32781
+ ( true ? "2.34.4" : 0);
32508
32782
  return {
32509
32783
  userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
32510
32784
  deviceId: this.deviceId,
@@ -32908,6 +33182,7 @@ exports.ToolPayloadError = void 0;
32908
33182
  exports.buildJsonToolResult = buildJsonToolResult;
32909
33183
  __webpack_unused_export__ = isBusinessFailureToolResult;
32910
33184
  exports.withBusinessFailureIsError = withBusinessFailureIsError;
33185
+ exports.buildTextErrorResult = buildTextErrorResult;
32911
33186
  exports.isToolPayloadError = isToolPayloadError;
32912
33187
  exports.toolPayloadErrorToResult = toolPayloadErrorToResult;
32913
33188
  exports.buildAuthNextStep = buildAuthNextStep;
@@ -32974,6 +33249,28 @@ function withBusinessFailureIsError(result) {
32974
33249
  }
32975
33250
  return { ...record, isError: true };
32976
33251
  }
33252
+ /**
33253
+ * 纯文本失败回执:**必须显式带 `isError: true`**。
33254
+ *
33255
+ * 为什么需要它:`withBusinessFailureIsError` 只能识别结构化的 `{ success: false }`
33256
+ * 载荷(`isBusinessFailureToolResult` 会 JSON.parse 文本内容再嗅 `success`)。工具在自己
33257
+ * 的 catch 里返回 `{ content: [{ type: "text", text }] }` 这种**纯文本**回执时,文本不是
33258
+ * JSON,嗅探不到任何失败标记 → 客户端只看到 `isError: false`,把失败当成功处理。
33259
+ *
33260
+ * 实测(F7):`queryEnv(action="usage")` 不带 envId 时抛出的引导文案就是这样返回的,
33261
+ * 只看 `isError` 的客户端会漏判。MCP 协议里 `isError` 是唯一可靠的失败标记,别省。
33262
+ */
33263
+ function buildTextErrorResult(text) {
33264
+ return {
33265
+ content: [
33266
+ {
33267
+ type: "text",
33268
+ text,
33269
+ },
33270
+ ],
33271
+ isError: true,
33272
+ };
33273
+ }
32977
33274
  class ToolPayloadError extends Error {
32978
33275
  payload;
32979
33276
  constructor(payload) {
@@ -33085,6 +33382,9 @@ ${envId}
33085
33382
  // 构建标题
33086
33383
  const title = `MCP工具错误: ${toolName}`;
33087
33384
  // 构建问题描述
33385
+ // ⚠️ MCP 版本必须优先取构建期注入的 __MCP_VERSION__:hosted 场景下进程是 BFF 的进程,
33386
+ // process.env.npm_package_version 是**宿主应用**的版本(实测报出 0.0.1,与 MCP 自身
33387
+ // 版本 2.34.2 自相矛盾);仅在未注入(源码直跑)时才回退到它。
33088
33388
  const body = `## 错误描述
33089
33389
  工具 \`${toolName}\` 执行时发生错误
33090
33390
 
@@ -33096,7 +33396,7 @@ ${envIdSection}
33096
33396
  ## 环境信息
33097
33397
  - 操作系统: ${os_1.default.type()} ${os_1.default.release()}
33098
33398
  - Node.js版本: ${process.version}
33099
- - MCP 版本:${process.env.npm_package_version || ( true ? "2.34.2" : 0)}
33399
+ - MCP 版本:${( true ? "2.34.4" : 0) || 0 || 0}
33100
33400
  - 系统架构: ${os_1.default.arch()}
33101
33401
  - 时间: ${new Date().toISOString()}
33102
33402
  - 请求ID: ${requestId}
@@ -33185,7 +33485,7 @@ function createWrappedHandler(name, handler, server) {
33185
33485
  requestId,
33186
33486
  ide: server.ide || process.env.INTEGRATION_IDE || ''
33187
33487
  });
33188
- const mcpVersion = true ? "2.34.2" : 0;
33488
+ const mcpVersion = true ? "2.34.4" : 0;
33189
33489
  const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
33190
33490
  const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
33191
33491
  // 创建新的错误对象,保持原有的错误类型但更新消息