-
Notifications
You must be signed in to change notification settings - Fork 125
Expand file tree
/
Copy pathcapi.ts
More file actions
292 lines (257 loc) · 13.9 KB
/
capi.ts
File metadata and controls
292 lines (257 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
import { z } from "zod";
import { getCloudBaseManager, logCloudBaseResult } from "../cloudbase-manager.js";
import { TCB_ACTION_INDEX_MAP } from "../generated/tcb-action-index.js";
import { ExtendedMcpServer } from "../server.js";
const CATEGORY = "cloud-api";
const CLOUDBASE_CONTROL_PLANE_DOC_URL = "https://cloud.tencent.com/document/product/876/34809";
const CLOUDBASE_DEPENDENCY_API_DOC_URL = "https://cloud.tencent.com/document/product/876/34808";
const ALLOWED_SERVICES = [
"tcb",
"scf",
"sts",
"cam",
"lowcode",
"cdn",
"vpc",
] as const;
type AllowedService = (typeof ALLOWED_SERVICES)[number];
function levenshteinDistance(left: string, right: string) {
const rows = left.length + 1;
const cols = right.length + 1;
const matrix = Array.from({ length: rows }, () => Array(cols).fill(0));
for (let row = 0; row < rows; row += 1) {
matrix[row][0] = row;
}
for (let col = 0; col < cols; col += 1) {
matrix[0][col] = col;
}
for (let row = 1; row < rows; row += 1) {
for (let col = 1; col < cols; col += 1) {
const substitutionCost = left[row - 1] === right[col - 1] ? 0 : 1;
matrix[row][col] = Math.min(
matrix[row - 1][col] + 1,
matrix[row][col - 1] + 1,
matrix[row - 1][col - 1] + substitutionCost,
);
}
}
return matrix[left.length][right.length];
}
function findTcbActionEntry(action: string) {
if (TCB_ACTION_INDEX_MAP[action]) {
return TCB_ACTION_INDEX_MAP[action];
}
const normalizedAction = action.toLowerCase();
return Object.values(TCB_ACTION_INDEX_MAP).find(
(entry) => entry.action.toLowerCase() === normalizedAction,
);
}
function suggestTcbActions(action: string, limit = 3) {
const normalizedAction = action.toLowerCase();
return Object.values(TCB_ACTION_INDEX_MAP)
.map((entry) => {
const normalizedCandidate = entry.action.toLowerCase();
let score = levenshteinDistance(normalizedAction, normalizedCandidate);
if (normalizedCandidate.startsWith(normalizedAction)) {
score -= 3;
}
if (normalizedCandidate.includes(normalizedAction)) {
score -= 2;
}
if (normalizedAction.startsWith(normalizedCandidate)) {
score -= 1;
}
return { entry, score };
})
.sort((left, right) => {
if (left.score !== right.score) {
return left.score - right.score;
}
return left.entry.action.localeCompare(right.entry.action);
})
.slice(0, limit)
.map(({ entry }) => entry.action);
}
function formatTcbParamKeys(keys: string[]) {
return keys.map((item: string) => `\`${item}\``).join("、");
}
function formatTcbParamsTypeHint(action: string) {
const entry = findTcbActionEntry(action);
if (!entry) {
return undefined;
}
return `参数类型参考:\n\`\`\`ts\n${entry.paramsType}\n\`\`\``;
}
function buildCapiDocGuidance(service: AllowedService) {
if (service === "tcb" || service === "lowcode" || service === "scf") {
return `优先查阅 CloudBase API 概览 ${CLOUDBASE_CONTROL_PLANE_DOC_URL} 与云开发依赖资源接口指引 ${CLOUDBASE_DEPENDENCY_API_DOC_URL}。`;
}
return `请优先核对对应官方云 API 文档;若你的场景其实是通过 HTTP 协议直接集成 auth/functions/cloudrun/storage/mysqldb 等 CloudBase 业务 API,请优先使用 OpenAPI / Swagger 或 searchKnowledgeBase(mode="openapi"),不要继续猜测管控面 Action。`;
}
export function buildCapiErrorMessage(service: AllowedService, action: string, error: unknown): string {
const baseMessage = error instanceof Error ? error.message : String(error);
const suggestions: string[] = [];
const tcbEntry = service === "tcb" ? findTcbActionEntry(action) : undefined;
const hasInvalidActionError = /invalid or not found|does not exist|not recognized/i.test(baseMessage);
const hasParameterError = /parameter\s+`?.+?`?\s+is not recognized|MissingParameter|missing parameter|missing required/i.test(baseMessage);
if (hasInvalidActionError) {
suggestions.push(
`Action \`${action}\` 可能不存在或不对外开放。请不要继续猜测 Action 名称,先确认 service=\`${service}\` 下该 Action 在当前 API 版本是否真实存在。`,
);
if (service === "tcb") {
const candidates = suggestTcbActions(action);
if (candidates.length > 0) {
suggestions.push(`可能的 tcb Action:${candidates.map((item) => `\`${item}\``).join("、")}。`);
}
}
suggestions.push(buildCapiDocGuidance(service));
}
if (hasParameterError) {
suggestions.push("请求参数名与 API 定义不一致,请核对参数字段(区分大小写)并移除未支持字段。");
if (service === "tcb" && tcbEntry) {
const paramHint = [
tcbEntry.paramKeys.length > 0
? `常见参数键:${formatTcbParamKeys(tcbEntry.paramKeys)}`
: "",
tcbEntry.requiredKeys.length > 0
? `必填参数:${formatTcbParamKeys(tcbEntry.requiredKeys)}`
: "",
].filter(Boolean);
if (paramHint.length > 0) {
suggestions.push(`\`${tcbEntry.action}\` ${paramHint.join(";")}。`);
}
const paramsTypeHint = formatTcbParamsTypeHint(tcbEntry.action);
if (paramsTypeHint) {
suggestions.push(paramsTypeHint);
}
}
}
if (/ECONNRESET|socket hang up|ETIMEDOUT|ENOTFOUND/i.test(baseMessage)) {
suggestions.push("网络请求异常,建议稍后重试,并检查本地网络/代理设置。");
}
if (suggestions.length === 0) {
suggestions.push(`请检查 service/action/params 是否与官方 API 文档一致后重试。${buildCapiDocGuidance(service)}`);
if (service === "tcb" && tcbEntry && tcbEntry.paramKeys.length > 0) {
suggestions.push(`\`${tcbEntry.action}\` 常见参数键:${formatTcbParamKeys(tcbEntry.paramKeys)}。`);
const paramsTypeHint = formatTcbParamsTypeHint(tcbEntry.action);
if (paramsTypeHint) {
suggestions.push(paramsTypeHint);
}
}
}
return `[${service}/${action}] 调用失败: ${baseMessage}\n建议:${suggestions.join(" ")}\n参考文档:CloudBase API 概览 ${CLOUDBASE_CONTROL_PLANE_DOC_URL}\n云开发依赖资源接口指引 ${CLOUDBASE_DEPENDENCY_API_DOC_URL}`;
}
/**
* Register Common Service based Cloud API tool.
* The tool is intentionally generic; callers must read project rules or
* skills to ensure correct API usage before invoking.
*/
export function registerCapiTools(server: ExtendedMcpServer) {
const cloudBaseOptions = server.cloudBaseOptions;
const logger = server.logger;
const getManager = () => getCloudBaseManager({ cloudBaseOptions });
server.registerTool?.(
"callCloudApi",
{
title: "调用云API",
description:
`通用的云 API 调用工具,主要用于 CloudBase / 腾讯云管控面与依赖资源相关 API 调用。调用前请先确认 service、Action 与 Param,避免猜测 Action 名称。如果你的目标是通过 HTTP 协议直接集成 auth/functions/cloudrun/storage/mysqldb 等 CloudBase 业务 API,请不要优先使用 callCloudApi,而应优先查看对应 OpenAPI / Swagger。现有 OpenAPI / Swagger 能力不是通用的管控面 Action 集合;管控面 API 请优先参考 CloudBase API 概览 ${CLOUDBASE_CONTROL_PLANE_DOC_URL} 与云开发依赖资源接口指引 ${CLOUDBASE_DEPENDENCY_API_DOC_URL}。对于 tcb service,常用 Action 分类如下:
**环境管理**: \`CreateEnv\`、\`ModifyEnv\`、\`DescribeEnvs\`、\`DestroyEnv\`
**用户管理**: \`CreateUser\`、\`ModifyUser\`、\`DescribeUserList\`、\`DeleteUsers\`
**认证配置**: \`EditAuthConfig\`、\`DescribeAuthDomains\`
**云函数**: \`DescribeFunctions\`、\`CreateFunction\`、\`UpdateFunctionCode\`、\`DeleteFunction\`
**数据库**: \`CreateMySQLInstance\`、\`DescribeMySQLInstances\`、\`DestroyMySQLInstance\`
销毁环境时,常见做法是至少带上 \`EnvId\` 和 \`BypassCheck: true\`,如果环境已经处于隔离期再按文档补 \`IsForce: true\`。
**重要:params 结构规则**
1. params 必须是**扁平键值对**,不要嵌套对象。例如 \`CreateUser\` 的参数应写成 \`{ "EnvId": "env-xxx", "Name": "zhangsan", "Type": "internalUser", "UserStatus": "ACTIVE" }\`,而不是 \`{ "User": { "Name": "..." } }\`。
2. 几乎所有 tcb Action 都**必须**在 params 顶层携带 \`EnvId\`(环境 ID)。如果已通过 auth 工具获取了 env_id,请直接填入 params.EnvId。
3. 参数键名**必须**与官方 API 定义完全一致(区分大小写),不要自创或使用近义词。例如 \`CreateUser\` 使用 \`Name\`(不是 UserName)、\`Type\`(不是 UserType)、\`UserStatus\`(不是 Status)。`,
inputSchema: {
service: z
.enum(ALLOWED_SERVICES)
.describe(
"选择要访问的服务。可选:tcb、scf、sts、cam、lowcode、cdn、vpc。对于 tcb / scf / lowcode 等 CloudBase 管控面 Action,请优先查官方文档,不要直接猜测 Action。",
),
action: z
.string()
.min(1)
.describe("具体 Action 名称,需符合对应服务的官方 API 定义。若不确定正确 Action,请先查官方文档;不要用近义词或历史命名进行猜测。tcb 常用 Action:环境管理 CreateEnv/ModifyEnv/DescribeEnvs/DestroyEnv、用户管理 CreateUser/ModifyUser/DescribeUserList/DeleteUsers、认证配置 EditAuthConfig、云函数 DescribeFunctions/CreateFunction、数据库 CreateMySQLInstance 等。"),
params: z
.record(z.any())
.optional()
.describe(
"Action 对应的参数对象,键名需与官方 API 定义一致(区分大小写)。params 必须是扁平键值对,不要嵌套子对象。几乎所有 tcb Action 都必须在顶层携带 EnvId。如不确定参数结构,请先查官方文档。示例:`{ \"service\": \"tcb\", \"action\": \"DestroyEnv\", \"params\": { \"EnvId\": \"env-xxx\", \"BypassCheck\": true } }`;`{ \"service\": \"tcb\", \"action\": \"CreateUser\", \"params\": { \"EnvId\": \"env-xxx\", \"Name\": \"zhangsan\", \"NickName\": \"张三\", \"Phone\": \"13800138000\", \"Email\": \"zhangsan@example.com\", \"Type\": \"internalUser\", \"UserStatus\": \"ACTIVE\" } }`。注意 CreateUser 的必填参数是 EnvId 和 Name,用户类型字段是 Type(非 UserType),状态字段是 UserStatus(非 Status)。若你的场景是通过 HTTP 协议直接集成 auth/functions/cloudrun/storage/mysqldb 等 CloudBase 业务 API,请优先使用 OpenAPI / Swagger 或 searchKnowledgeBase(mode=\"openapi\"),而不是优先使用 callCloudApi。",
),
},
annotations: {
readOnlyHint: false,
destructiveHint: true,
idempotentHint: false,
openWorldHint: true,
category: CATEGORY,
},
},
async ({
service,
action,
params,
}: {
service: AllowedService;
action: string;
params?: Record<string, any>;
}) => {
if (!ALLOWED_SERVICES.includes(service)) {
throw new Error(
`Service ${service} is not allowed. Allowed services: ${ALLOWED_SERVICES.join(", ")}`,
);
}
const cloudbase = await getManager();
if (['1', 'true'].includes(process.env.CLOUDBASE_EVALUATE_MODE ?? '')) {
if (service === 'lowcode') {
throw new Error(`${service}/${action} Cloud API is not exposed or does not exist. Please use another API.`);
}
if (service === 'tcb') {
const tcbCapiForbidList = [
// 未明确对外的云API
'DescribeStorageACL', 'ModifyStorageACL', 'DescribeSecurityRule',
// 要下线的云API
"ListTables",
"DescribeCloudBaseGWAPI",
"DescribeCloudBaseGWService",
"CreateCloudBaseGWAPI",
"DeleteCloudBaseGWAPI",
"ModifyCloudBaseGWAPI",
"DeleteCloudBaseGWDomain",
"BindCloudBaseGWDomain",
"BindCloudBaseAccessDomain"
];
if (tcbCapiForbidList.includes(action)) {
throw new Error(`${service}/${action} Cloud API is not exposed or does not exist. Please use another API.`);
}
}
}
let result: unknown;
try {
result = await cloudbase.commonService(service).call({
Action: action,
Param: params ?? {},
});
} catch (error) {
throw new Error(buildCapiErrorMessage(service, action, error));
}
logCloudBaseResult(logger, result);
return {
content: [
{
type: "text",
text: JSON.stringify(
result,
null,
2,
),
},
],
};
},
);
}