Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/mcp-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -2292,7 +2292,7 @@ Agent 域统一写入口。支持创建、更新和删除远端 Agent。
**云函数**: `DescribeFunctions`、`CreateFunction`、`UpdateFunctionCode`、`DeleteFunction`
**数据库**: `CreateMySQLInstance`、`DescribeMySQLInstances`、`DestroyMySQLInstance`

销毁环境时,常见做法是至少带上 `EnvId` 和 `BypassCheck: true`,如果环境已经处于隔离期再按文档补 `IsForce: true`。
销毁环境时,`EnvId` 必须传环境的 canonical full `EnvId`,不要传别名或其他简写;如果对话里只有别名,先用 `envQuery(action=list, alias=..., aliasExact=true)` 解析。按官方接口,普通销毁可先只传 `EnvId`;仅在需要绕过资源检查时再补 `BypassCheck: true`,环境已处于隔离期且要彻底删除时再补 `IsForce: true`。

#### 参数

Expand All @@ -2313,7 +2313,7 @@ Agent 域统一写入口。支持创建、更新和删除远端 Agent。
{
name: "params",
type: "object",
description: `Action 对应的参数对象,键名需与官方 API 定义一致。某些 Action 需要携带 EnvId 等信息;如不确定参数结构,请先查官方文档。tcb 示例:\`{ "service": "tcb", "action": "DestroyEnv", "params": { "EnvId": "env-xxx", "BypassCheck": true } }\`,如果环境已经处于隔离期,可再补 \`IsForce: true\`;更新环境别名则可用 \`{ "service": "tcb", "action": "ModifyEnv", "params": { "EnvId": "env-xxx", "Alias": "demo" } }\`。若你的场景是通过 HTTP 协议直接集成 auth/functions/cloudrun/storage/mysqldb 等 CloudBase 业务 API,请优先使用 OpenAPI / Swagger 或 searchKnowledgeBase(mode="openapi"),而不是优先使用 callCloudApi。`,
description: `Action 对应的参数对象,键名需与官方 API 定义一致。某些 Action 需要携带 EnvId 等信息;如不确定参数结构,请先查官方文档。tcb 示例:\`{ "service": "tcb", "action": "DestroyEnv", "params": { "EnvId": "env-xxx" } }\`。\`DestroyEnv\` 的 \`EnvId\` 必须是 canonical full \`EnvId\`,不要传环境别名或其他简写;如果对话里只有别名,先用 \`envQuery(action=list, alias=..., aliasExact=true)\` 解析。仅在需要绕过资源检查时再补 \`BypassCheck: true\`,仅在隔离期环境彻底删除时再补 \`IsForce: true\`;更新环境别名则可用 \`{ "service": "tcb", "action": "ModifyEnv", "params": { "EnvId": "env-xxx", "Alias": "demo" } }\`。若你的场景是通过 HTTP 协议直接集成 auth/functions/cloudrun/storage/mysqldb 等 CloudBase 业务 API,请优先使用 OpenAPI / Swagger 或 searchKnowledgeBase(mode="openapi"),而不是优先使用 callCloudApi。`,
}
]}
/>
Expand Down
40 changes: 20 additions & 20 deletions mcp/src/generated/tcb-action-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
],
"exampleParams": {
"Alias": "cloudbase",
"AutoVoucher": "true",
"AutoVoucher": true,
"Channel": "qc_console",
"EnvId": "my-env-i3jkguiejls",
"PackageId": "baas_personal",
"Period": "1",
"Period": 1,
"RenewFlag": "NOTIFY_AND_MANUAL_RENEW",
"Resources": "[\"flexdb\"]",
"Source": "qcloud",
Expand Down Expand Up @@ -220,7 +220,7 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
"requiredKeys": [],
"exampleParams": {
"EnvId": "env-xxyyzzaa",
"NeedOrderInfo": "true",
"NeedOrderInfo": true,
"ResourceId": "weda.xxxxdad",
"WxAppId": "wxasdkfjdkjfk"
},
Expand Down Expand Up @@ -299,7 +299,7 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
"EndTime": "2019-04-04 19:00:00",
"EnvId": "lotestapi100004",
"MetricName": "DbSizepkg",
"Period": "300",
"Period": 300,
"ResourceID": "ibot-agent1",
"StartTime": "2019-04-02 09:00:00",
"SubresourceID": "deepseek",
Expand Down Expand Up @@ -430,8 +430,8 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
],
"exampleParams": {
"EnvId": "lowcode-abc",
"MgoLimit": "10",
"MgoOffset": "0",
"MgoLimit": 10,
"MgoOffset": 0,
"MongoConnector": "无",
"TableNames": "[\"table_name\"]",
"Tag": "tnt-abc"
Expand Down Expand Up @@ -460,8 +460,8 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
"EnvId": "test_envId",
"Name": "zhang",
"NickName": "张",
"PageNo": "1",
"PageSize": "10",
"PageNo": 1,
"PageSize": 10,
"Phone": "13900139000"
},
"paramsType": "/**\n * 查询tcb用户列表\n */\ntype DescribeUserListParams = {\n /**\n * 邮箱,模糊查询\n */\n \"Email\"?: string;\n /**\n * 环境id\n */\n \"EnvId\": string;\n /**\n * 用户名,模糊查询\n */\n \"Name\"?: string;\n /**\n * 用户昵称,模糊查询\n */\n \"NickName\"?: string;\n /**\n * 页码,从1开始,默认1\n */\n \"PageNo\"?: number;\n /**\n * 每页数量,默认20,最大100\n */\n \"PageSize\"?: number;\n /**\n * 手机号,模糊查询\n */\n \"Phone\"?: string;\n};"
Expand All @@ -482,11 +482,11 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
"EnvId"
],
"exampleParams": {
"BypassCheck": "false",
"BypassLimit": "false",
"DeleteDirectly": "false",
"BypassCheck": false,
"BypassLimit": false,
"DeleteDirectly": false,
"EnvId": "yourenvid-2fb346",
"IsForce": "false"
"IsForce": false
},
"paramsType": "/**\n * 销毁环境\n */\ntype DestroyEnvParams = {\n /**\n * 是否绕过资源检查,资源包等额外资源,默认为false,如果为true,则不检查资源是否有数据,直接删除。\n */\n \"BypassCheck\"?: boolean;\n /**\n * 销毁消耗用户删除配额 默认为false-占用配额 true-不占用配额\n */\n \"BypassLimit\"?: boolean;\n /**\n * 是否自动删除环境。(仅在IsForce=false时,且仅对预付费环境有效)\n */\n \"DeleteDirectly\"?: boolean;\n /**\n * 环境Id\n */\n \"EnvId\": string;\n /**\n * 针对预付费 删除隔离中的环境时要传true 正常环境直接跳过隔离期删除\n */\n \"IsForce\"?: boolean;\n};"
},
Expand Down Expand Up @@ -557,11 +557,11 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
"exampleParams": {
"EnvId": "lowcode-abc",
"Filters": "[\"HIDDEN\"]",
"MgoLimit": "10",
"MgoOffset": "0",
"MgoLimit": 10,
"MgoOffset": 0,
"MongoConnector": "无",
"SearchValue": "prefix",
"ShowHidden": "false",
"ShowHidden": false,
"Tag": "tag-123"
},
"paramsType": "/**\n * 查询文档型数据库所有表\n */\ntype ListTablesParams = {\n /**\n * 云开发环境ID\n */\n \"EnvId\"?: string;\n /**\n * 过滤标签数组,用于过滤表名,可选值如:HIDDEN、WEDA、WEDA_SYSTEM\n */\n \"Filters\"?: (string)[];\n /**\n * 每页返回数量(0-1000)\n */\n \"MgoLimit\": number;\n /**\n * 分页偏移量\n */\n \"MgoOffset\"?: number;\n /**\n * MongoDB连接器配置\n */\n \"MongoConnector\"?: {\n /**\n * MongoDB数据库名\n */\n \"DatabaseName\"?: string;\n /**\n * 连接器实例ID\n */\n \"InstanceId\"?: string;\n };\n /**\n * 模糊搜索查询值\n */\n \"SearchValue\"?: string;\n /**\n * 是否展示隐藏表\n */\n \"ShowHidden\"?: boolean;\n /**\n * FlexDB实例ID\n */\n \"Tag\"?: string;\n};"
Expand All @@ -580,7 +580,7 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
],
"exampleParams": {
"EnvId": "env-xxxxxx",
"Period": "10"
"Period": 10
},
"paramsType": "/**\n * 修改日志主题\n */\ntype ModifyClsTopicParams = {\n /**\n * 环境ID\n */\n \"EnvId\": string;\n /**\n * 日志生命周期,单位天,可取值范围1~3600,取值为3640时代表永久保存\n */\n \"Period\"?: number;\n};"
},
Expand Down Expand Up @@ -651,7 +651,7 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
"PackageId"
],
"exampleParams": {
"AutoVoucher": "true",
"AutoVoucher": true,
"EnvId": "cloudbase-8grqda2hfc2f62bb",
"PackageId": "baas_pf_standard"
},
Expand Down Expand Up @@ -680,7 +680,7 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
"CollectionName": "my-table",
"EnvId": "myenv-c849jgkdldcmsd",
"Rule": "{ \"read\": true, \"write\": \"doc._openid == auth.openid\" }",
"SyncModel": "false",
"SyncModel": false,
"WxAppId": "wxg89jkjdkf034kgjlsdk"
},
"paramsType": "/**\n * 设置数据库安全规则\n */\ntype ModifySafeRuleParams = {\n /**\n * 权限标签。包含以下取值:\n * - READONLY:所有用户可读,仅创建者和管理员可写\n * - PRIVATE:仅创建者及管理员可读写\n * - ADMINWRITE:所有用户可读,仅管理员可写\n * - ADMINONLY:仅管理员可读写\n * - CUSTOM:自定义安全规则\n */\n \"AclTag\": string;\n /**\n * 集合名称\n */\n \"CollectionName\": string;\n /**\n * 环境ID\n */\n \"EnvId\": string;\n /**\n * 安全规则内容。\n * 当 AclTag=CUSTOM 时,此参数必填。\n * 详情参考:[文档型数据库安全规则](https://docs.cloudbase.net/database/security-rules)\n */\n \"Rule\"?: string;\n /**\n * 是否同步数据模型,默认同步\n */\n \"SyncModel\"?: boolean;\n /**\n * 微信 AppId,微信必传\n */\n \"WxAppId\"?: string;\n};"
Expand Down Expand Up @@ -760,9 +760,9 @@ export const TCB_ACTION_INDEX_MAP: Record<string, TcbActionIndexEntry> = {
"EnvId"
],
"exampleParams": {
"AutoVoucher": "true",
"AutoVoucher": true,
"EnvId": "cloudbase-8grqda2hfc2f62bb",
"Period": "1"
"Period": 1
},
"paramsType": "/**\n * 续费云开发环境\n */\ntype RenewEnvParams = {\n /**\n * 是否自动选择代金券支付。\n */\n \"AutoVoucher\"?: boolean;\n /**\n * 环境ID\n */\n \"EnvId\": string;\n /**\n * 续费周期,单位:月。\n * 默认值为 1,即续费1个月。\n */\n \"Period\"?: number;\n};"
},
Expand Down
13 changes: 13 additions & 0 deletions mcp/src/tools/capi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ describe("buildCapiErrorMessage", () => {
expect(message).toContain("/**");
});

it("adds destroy env specific guidance for invalid parameter values", () => {
const message = buildCapiErrorMessage(
"tcb",
"DestroyEnv",
new Error("400 invalid parameter value"),
);

expect(message).toContain("canonical full `EnvId`");
expect(message).toContain("aliasExact=true");
expect(message).toContain("{ \"EnvId\": \"env-xxx\" }");
expect(message).toContain("`IsForce: true`");
});

it("does not inject tcb action suggestions for non-tcb services", () => {
const message = buildCapiErrorMessage(
"scf",
Expand Down
9 changes: 7 additions & 2 deletions mcp/src/tools/capi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ export function buildCapiErrorMessage(service: AllowedService, action: string, e
suggestions.push("2. 数值参数是否在允许范围内");
suggestions.push("3. 枚举值是否使用了正确的取值(区分大小写)");
suggestions.push("4. 必填参数是否有值");
if (service === "tcb" && tcbEntry?.action === "DestroyEnv") {
suggestions.push("`DestroyEnv` 的 `EnvId` 必须是环境的 canonical full `EnvId`,不要传环境别名、昵称、当前登录态标识,或带 `/` 的复合值。");
suggestions.push("如果对话里只有环境别名,先用 `envQuery(action=\"list\", alias=..., aliasExact=true)` 解析出准确 `EnvId`。");
suggestions.push("按官方接口,普通销毁可先只传 `{ \"EnvId\": \"env-xxx\" }`;仅在确实需要绕过资源检查时再补 `BypassCheck: true`,仅在隔离期环境彻底删除时再补 `IsForce: true`。");
}
if (service === "tcb" && tcbEntry) {
const paramsTypeHint = formatTcbParamsTypeHint(tcbEntry.action);
if (paramsTypeHint) {
Expand Down Expand Up @@ -205,7 +210,7 @@ export function registerCapiTools(server: ExtendedMcpServer) {
**云函数**: \`DescribeFunctions\`、\`CreateFunction\`、\`UpdateFunctionCode\`、\`DeleteFunction\`
**数据库**: \`CreateMySQLInstance\`、\`DescribeMySQLInstances\`、\`DestroyMySQLInstance\`

销毁环境时,常见做法是至少带上 \`EnvId\` 和 \`BypassCheck: true\`,如果环境已经处于隔离期再按文档补 \`IsForce: true\`。`,
销毁环境时,\`EnvId\` 必须传环境的 canonical full \`EnvId\`,不要传别名或其他简写;如果对话里只有别名,先用 \`envQuery(action=list, alias=..., aliasExact=true)\` 解析。按官方接口,普通销毁可先只传 \`EnvId\`;仅在需要绕过资源检查时再补 \`BypassCheck: true\`,环境已处于隔离期且要彻底删除时再补 \`IsForce: true\`。`,
inputSchema: {
service: z
.enum(ALLOWED_SERVICES)
Expand All @@ -220,7 +225,7 @@ export function registerCapiTools(server: ExtendedMcpServer) {
.record(z.any())
.optional()
.describe(
"Action 对应的参数对象,键名需与官方 API 定义一致。某些 Action 需要携带 EnvId 等信息;如不确定参数结构,请先查官方文档。tcb 示例:`{ \"service\": \"tcb\", \"action\": \"DestroyEnv\", \"params\": { \"EnvId\": \"env-xxx\", \"BypassCheck\": true } }`,如果环境已经处于隔离期,可再补 `IsForce: true`;更新环境别名则可用 `{ \"service\": \"tcb\", \"action\": \"ModifyEnv\", \"params\": { \"EnvId\": \"env-xxx\", \"Alias\": \"demo\" } }`。若你的场景是通过 HTTP 协议直接集成 auth/functions/cloudrun/storage/mysqldb 等 CloudBase 业务 API,请优先使用 OpenAPI / Swagger 或 searchKnowledgeBase(mode=\"openapi\"),而不是优先使用 callCloudApi。",
"Action 对应的参数对象,键名需与官方 API 定义一致。某些 Action 需要携带 EnvId 等信息;如不确定参数结构,请先查官方文档。tcb 示例:`{ \"service\": \"tcb\", \"action\": \"DestroyEnv\", \"params\": { \"EnvId\": \"env-xxx\" } }`。`DestroyEnv` 的 `EnvId` 必须是 canonical full `EnvId`,不要传环境别名或其他简写;如果对话里只有别名,先用 `envQuery(action=list, alias=..., aliasExact=true)` 解析。仅在需要绕过资源检查时再补 `BypassCheck: true`,仅在隔离期环境彻底删除时再补 `IsForce: true`;更新环境别名则可用 `{ \"service\": \"tcb\", \"action\": \"ModifyEnv\", \"params\": { \"EnvId\": \"env-xxx\", \"Alias\": \"demo\" } }`。若你的场景是通过 HTTP 协议直接集成 auth/functions/cloudrun/storage/mysqldb 等 CloudBase 业务 API,请优先使用 OpenAPI / Swagger 或 searchKnowledgeBase(mode=\"openapi\"),而不是优先使用 callCloudApi。",
),
},
annotations: {
Expand Down
50 changes: 49 additions & 1 deletion scripts/generate-tcb-action-index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,54 @@ function cloneExample(value) {
return JSON.parse(JSON.stringify(value));
}

/**
* Normalize example values to match their schema types.
* This fixes issues where the OpenAPI YAML has string values for boolean fields
* (e.g., 'false' instead of false), which can cause 400 errors when agents use them.
*/
function normalizeExampleValue(value, schema) {
if (value === null || value === undefined) {
return value;
}

// Handle boolean type normalization
if (schema?.type === "boolean" && typeof value === "string") {
const lowerValue = value.toLowerCase();
if (lowerValue === "true") {
return true;
}
if (lowerValue === "false") {
return false;
}
}

// Handle integer/number type normalization
if ((schema?.type === "integer" || schema?.type === "number") && typeof value === "string") {
const parsed = Number(value);
if (!Number.isNaN(parsed)) {
return schema.type === "integer" ? Math.floor(parsed) : parsed;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid flooring non-integer numeric strings

The new normalization path converts any numeric string for integer schemas with Math.floor, so an example like "1.9" (or "-1.2") is silently rewritten to a different value (1 / -2) instead of being left unchanged or rejected. This can produce misleading exampleParams that no longer reflect the OpenAPI source and may drive bad tool calls (especially for bounded fields) when agents copy examples. Restrict integer coercion to strings that parse as exact integers (and finite values) to avoid data corruption during generation.

Useful? React with 👍 / 👎.

}
}

// Handle array type normalization
if (schema?.type === "array" && Array.isArray(value)) {
return value.map((item) => normalizeExampleValue(item, schema.items));
}

// Handle object type normalization
if ((schema?.type === "object" || schema?.properties) && typeof value === "object" && value !== null) {
const properties = schema.properties ?? {};
const normalized = {};
for (const [key, val] of Object.entries(value)) {
const propSchema = properties[key];
normalized[key] = normalizeExampleValue(val, propSchema);
}
return normalized;
}

return value;
}

function mergeSchemaNodes(base, incoming) {
if (!incoming || typeof incoming !== "object") {
return base;
Expand Down Expand Up @@ -185,7 +233,7 @@ function buildSchemaResolver(components) {
required: Array.isArray(schema.required)
? [...schema.required].sort()
: undefined,
example: cloneExample(schema.example),
example: normalizeExampleValue(cloneExample(schema.example), schema),
};

if (schema.properties && typeof schema.properties === "object") {
Expand Down
Loading
Loading