@cloudbase/cloudbase-mcp 2.16.1-beta.1 → 2.17.0-beta.2
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/README.md +1 -1
- package/dist/cli.cjs +3 -3
- package/dist/index.cjs +1471 -288
- package/dist/index.js +1316 -273
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -134,6 +134,40 @@ function buildEnvCandidatePayload(envCandidates, limit = DEFAULT_ENV_CANDIDATE_L
|
|
|
134
134
|
},
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
|
+
function buildLocalDevDomainHint() {
|
|
138
|
+
return {
|
|
139
|
+
format: "host:port",
|
|
140
|
+
useActualOrigin: true,
|
|
141
|
+
requiredValue: "当前浏览器实际访问 origin 对应的 host:port",
|
|
142
|
+
deriveFrom: ["浏览器地址栏中的当前 origin", "本地 dev server 实际启动输出"],
|
|
143
|
+
note: "如果你的前端运行在自定义域名或本地开发端口上,请把当前浏览器实际访问地址对应的 host:port 加入安全域名。不要依赖一组固定默认端口,也不要假设已有 localhost/127.0.0.1 条目已经覆盖当前运行端口。",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function summarizeConfiguredLocalDevEntries(domains) {
|
|
147
|
+
const localEntries = domains
|
|
148
|
+
.map((domain) => String(domain?.Domain ?? "").trim())
|
|
149
|
+
.filter((domain) => domain.startsWith("127.0.0.1:") || domain.startsWith("localhost:"));
|
|
150
|
+
return {
|
|
151
|
+
hasAnyConfiguredLocalEntry: localEntries.length > 0,
|
|
152
|
+
configuredEntries: localEntries,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function simplifyEnvDomains(domains) {
|
|
156
|
+
if (!Array.isArray(domains)) {
|
|
157
|
+
return domains;
|
|
158
|
+
}
|
|
159
|
+
return domains.map((domain) => {
|
|
160
|
+
if (!domain || typeof domain !== "object") {
|
|
161
|
+
return domain;
|
|
162
|
+
}
|
|
163
|
+
const source = domain;
|
|
164
|
+
return {
|
|
165
|
+
...(source.Domain !== undefined ? { Domain: source.Domain } : {}),
|
|
166
|
+
...(source.Status !== undefined ? { Status: source.Status } : {}),
|
|
167
|
+
...(source.Type !== undefined ? { Type: source.Type } : {}),
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
137
171
|
function formatDeviceAuthHint(deviceAuthInfo) {
|
|
138
172
|
if (!deviceAuthInfo) {
|
|
139
173
|
return "";
|
|
@@ -1076,6 +1110,35 @@ function registerEnvTools(server) {
|
|
|
1076
1110
|
const cloudbaseDomains = await getManager();
|
|
1077
1111
|
result = await cloudbaseDomains.env.getEnvAuthDomains();
|
|
1078
1112
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
1113
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
1114
|
+
const domainsResult = result;
|
|
1115
|
+
const localDevHint = buildLocalDevDomainHint();
|
|
1116
|
+
const simplifiedDomains = simplifyEnvDomains(domainsResult.Domains);
|
|
1117
|
+
const localDevSummary = summarizeConfiguredLocalDevEntries(Array.isArray(simplifiedDomains)
|
|
1118
|
+
? simplifiedDomains
|
|
1119
|
+
: []);
|
|
1120
|
+
result = {
|
|
1121
|
+
...domainsResult,
|
|
1122
|
+
Domains: simplifiedDomains,
|
|
1123
|
+
localDevHint,
|
|
1124
|
+
localDevStatus: {
|
|
1125
|
+
requiresExactCurrentOrigin: true,
|
|
1126
|
+
browserUploadReady: false,
|
|
1127
|
+
coverageConfirmed: false,
|
|
1128
|
+
doNotAssumeConfiguredEntriesAreSufficient: true,
|
|
1129
|
+
canAutoDetermineCurrentOrigin: false,
|
|
1130
|
+
hasAnyConfiguredLocalEntry: localDevSummary.hasAnyConfiguredLocalEntry,
|
|
1131
|
+
configuredEntries: localDevSummary.configuredEntries,
|
|
1132
|
+
note: "此查询不会自动知道你当前浏览器实际使用的自定义域名或本地端口。即使已经存在一些 localhost/127.0.0.1 条目,也不能据此认定浏览器上传已就绪。若浏览器 Web 应用需要直接上传文件到 CloudBase,请先确认并添加当前访问地址对应的 host:port,再依赖 app.uploadFile()。",
|
|
1133
|
+
},
|
|
1134
|
+
next_step_template: {
|
|
1135
|
+
tool: "envDomainManagement",
|
|
1136
|
+
action: "create",
|
|
1137
|
+
domains: ["<actual-browser-host>:<actual-browser-port>"],
|
|
1138
|
+
note: "请把占位符替换为当前浏览器实际访问 origin 对应的 host:port,再执行添加。",
|
|
1139
|
+
},
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1079
1142
|
break;
|
|
1080
1143
|
case "hosting": {
|
|
1081
1144
|
const cloudbaseHosting = await getManager();
|
|
@@ -1147,7 +1210,7 @@ function registerEnvTools(server) {
|
|
|
1147
1210
|
// envDomainManagement - 环境域名管理(合并 createEnvDomain + deleteEnvDomain)
|
|
1148
1211
|
server.registerTool?.("envDomainManagement", {
|
|
1149
1212
|
title: "环境域名管理",
|
|
1150
|
-
description: "管理云开发环境的安全域名,支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI
|
|
1213
|
+
description: "管理云开发环境的安全域名,支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 或自定义域名直接访问 CloudBase 资源时,应先用 envQuery(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。",
|
|
1151
1214
|
inputSchema: {
|
|
1152
1215
|
action: zod_1.z
|
|
1153
1216
|
.enum(["create", "delete"])
|
|
@@ -1509,7 +1572,7 @@ ${envIdSection}
|
|
|
1509
1572
|
## 环境信息
|
|
1510
1573
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
1511
1574
|
- Node.js版本: ${process.version}
|
|
1512
|
-
- MCP 版本:${process.env.npm_package_version || "2.
|
|
1575
|
+
- MCP 版本:${process.env.npm_package_version || "2.17.0-beta.2" || 0}
|
|
1513
1576
|
- 系统架构: ${os_1.default.arch()}
|
|
1514
1577
|
- 时间: ${new Date().toISOString()}
|
|
1515
1578
|
- 请求ID: ${requestId}
|
|
@@ -1555,6 +1618,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
1555
1618
|
// 执行原始处理函数
|
|
1556
1619
|
const result = await handler(args);
|
|
1557
1620
|
success = true;
|
|
1621
|
+
requestId = extractRequestIdFromToolResult(result);
|
|
1558
1622
|
const duration = Date.now() - startTime;
|
|
1559
1623
|
(0, logger_js_1.debug)(`工具执行成功: ${name}`, { duration });
|
|
1560
1624
|
if (!isTestEnvironment) {
|
|
@@ -1618,6 +1682,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
1618
1682
|
(0, telemetry_js_1.reportToolCall)({
|
|
1619
1683
|
toolName: name,
|
|
1620
1684
|
success,
|
|
1685
|
+
requestId,
|
|
1621
1686
|
duration,
|
|
1622
1687
|
error: errorMessage,
|
|
1623
1688
|
inputParams: sanitizeArgs(args), // 添加入参上报
|
|
@@ -1631,6 +1696,38 @@ function createWrappedHandler(name, handler, server) {
|
|
|
1631
1696
|
}
|
|
1632
1697
|
};
|
|
1633
1698
|
}
|
|
1699
|
+
function extractRequestIdFromToolResult(result) {
|
|
1700
|
+
if (!result || typeof result !== 'object') {
|
|
1701
|
+
return undefined;
|
|
1702
|
+
}
|
|
1703
|
+
if (typeof result.requestId === 'string' && result.requestId.length > 0) {
|
|
1704
|
+
return result.requestId;
|
|
1705
|
+
}
|
|
1706
|
+
if (typeof result.RequestId === 'string' && result.RequestId.length > 0) {
|
|
1707
|
+
return result.RequestId;
|
|
1708
|
+
}
|
|
1709
|
+
const content = Array.isArray(result.content) ? result.content : [];
|
|
1710
|
+
for (const item of content) {
|
|
1711
|
+
if (!item || item.type !== 'text' || typeof item.text !== 'string') {
|
|
1712
|
+
continue;
|
|
1713
|
+
}
|
|
1714
|
+
try {
|
|
1715
|
+
const parsed = JSON.parse(item.text);
|
|
1716
|
+
if (parsed && typeof parsed === 'object') {
|
|
1717
|
+
if (typeof parsed.requestId === 'string' && parsed.requestId.length > 0) {
|
|
1718
|
+
return parsed.requestId;
|
|
1719
|
+
}
|
|
1720
|
+
if (typeof parsed.RequestId === 'string' && parsed.RequestId.length > 0) {
|
|
1721
|
+
return parsed.RequestId;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
catch {
|
|
1726
|
+
// Ignore non-JSON text payloads.
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return undefined;
|
|
1730
|
+
}
|
|
1634
1731
|
/**
|
|
1635
1732
|
* 包装 MCP Server 的 registerTool 方法,添加数据上报功能和条件注册
|
|
1636
1733
|
* @param server MCP Server 实例
|
|
@@ -3084,6 +3181,9 @@ exports.listAvailableEnvCandidates = listAvailableEnvCandidates;
|
|
|
3084
3181
|
exports.getEnvId = getEnvId;
|
|
3085
3182
|
exports.resetCloudBaseManagerCache = resetCloudBaseManagerCache;
|
|
3086
3183
|
exports.getCachedEnvId = getCachedEnvId;
|
|
3184
|
+
exports.resetDatabaseInstanceIdCache = resetDatabaseInstanceIdCache;
|
|
3185
|
+
exports.invalidateDatabaseInstanceIdCache = invalidateDatabaseInstanceIdCache;
|
|
3186
|
+
exports.getDatabaseInstanceId = getDatabaseInstanceId;
|
|
3087
3187
|
exports.getCloudBaseManager = getCloudBaseManager;
|
|
3088
3188
|
exports.createCloudBaseManagerWithOptions = createCloudBaseManagerWithOptions;
|
|
3089
3189
|
exports.extractRequestId = extractRequestId;
|
|
@@ -3116,7 +3216,7 @@ function createManagerFromLoginState(loginState, region) {
|
|
|
3116
3216
|
envId: loginState.envId,
|
|
3117
3217
|
token: loginState.token,
|
|
3118
3218
|
proxy: process.env.http_proxy,
|
|
3119
|
-
region,
|
|
3219
|
+
region: region ?? process.env.TCB_REGION ?? 'ap-shanghai',
|
|
3120
3220
|
});
|
|
3121
3221
|
}
|
|
3122
3222
|
async function listAvailableEnvCandidates(options) {
|
|
@@ -3135,7 +3235,7 @@ async function listAvailableEnvCandidates(options) {
|
|
|
3135
3235
|
if (!loginState?.secretId || !loginState?.secretKey) {
|
|
3136
3236
|
return [];
|
|
3137
3237
|
}
|
|
3138
|
-
const region = cloudBaseOptions?.region ?? process.env.TCB_REGION ??
|
|
3238
|
+
const region = cloudBaseOptions?.region ?? process.env.TCB_REGION ?? 'ap-shanghai';
|
|
3139
3239
|
cloudbase = createManagerFromLoginState(loginState, region);
|
|
3140
3240
|
}
|
|
3141
3241
|
try {
|
|
@@ -3339,6 +3439,74 @@ function resetCloudBaseManagerCache() {
|
|
|
3339
3439
|
function getCachedEnvId() {
|
|
3340
3440
|
return envManager.getCachedEnvId();
|
|
3341
3441
|
}
|
|
3442
|
+
const databaseInstanceIdCache = new Map();
|
|
3443
|
+
function buildDatabaseInstanceIdCacheKey(options) {
|
|
3444
|
+
const envId = options?.envId ?? options?.cloudBaseOptions?.envId ?? process.env.CLOUDBASE_ENV_ID ?? 'unknown';
|
|
3445
|
+
const region = options?.region ?? options?.cloudBaseOptions?.region ?? process.env.TCB_REGION ?? 'ap-shanghai';
|
|
3446
|
+
return `${region}:${envId}`;
|
|
3447
|
+
}
|
|
3448
|
+
function resetDatabaseInstanceIdCache() {
|
|
3449
|
+
databaseInstanceIdCache.clear();
|
|
3450
|
+
}
|
|
3451
|
+
function invalidateDatabaseInstanceIdCache(options) {
|
|
3452
|
+
const cacheKey = options?.cacheKey ?? buildDatabaseInstanceIdCacheKey(options);
|
|
3453
|
+
databaseInstanceIdCache.delete(cacheKey);
|
|
3454
|
+
}
|
|
3455
|
+
async function getDatabaseInstanceId(options) {
|
|
3456
|
+
if (options?.instanceId) {
|
|
3457
|
+
return {
|
|
3458
|
+
instanceId: options.instanceId,
|
|
3459
|
+
source: 'input',
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
const envId = await getEnvId(options?.cloudBaseOptions);
|
|
3463
|
+
const cacheKey = buildDatabaseInstanceIdCacheKey({
|
|
3464
|
+
envId,
|
|
3465
|
+
cloudBaseOptions: options?.cloudBaseOptions,
|
|
3466
|
+
});
|
|
3467
|
+
const cachedEntry = databaseInstanceIdCache.get(cacheKey);
|
|
3468
|
+
if (cachedEntry?.instanceId) {
|
|
3469
|
+
return {
|
|
3470
|
+
instanceId: cachedEntry.instanceId,
|
|
3471
|
+
source: 'cache',
|
|
3472
|
+
cacheKey,
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
if (cachedEntry?.inflightPromise) {
|
|
3476
|
+
const instanceId = await cachedEntry.inflightPromise;
|
|
3477
|
+
return {
|
|
3478
|
+
instanceId,
|
|
3479
|
+
source: 'cache',
|
|
3480
|
+
cacheKey,
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
const inflightPromise = (async () => {
|
|
3484
|
+
const cloudbase = options?.cloudbase ?? (await getCloudBaseManager({ cloudBaseOptions: options?.cloudBaseOptions }));
|
|
3485
|
+
const { EnvInfo } = await cloudbase.env.getEnvInfo();
|
|
3486
|
+
if (!EnvInfo?.Databases?.[0]?.InstanceId) {
|
|
3487
|
+
throw new Error("无法获取数据库实例ID");
|
|
3488
|
+
}
|
|
3489
|
+
return EnvInfo.Databases[0].InstanceId;
|
|
3490
|
+
})();
|
|
3491
|
+
databaseInstanceIdCache.set(cacheKey, {
|
|
3492
|
+
inflightPromise,
|
|
3493
|
+
});
|
|
3494
|
+
try {
|
|
3495
|
+
const instanceId = await inflightPromise;
|
|
3496
|
+
databaseInstanceIdCache.set(cacheKey, {
|
|
3497
|
+
instanceId,
|
|
3498
|
+
});
|
|
3499
|
+
return {
|
|
3500
|
+
instanceId,
|
|
3501
|
+
source: 'envInfo',
|
|
3502
|
+
cacheKey,
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
catch (error) {
|
|
3506
|
+
databaseInstanceIdCache.delete(cacheKey);
|
|
3507
|
+
throw error;
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3342
3510
|
/**
|
|
3343
3511
|
* 每次都实时获取最新的 token/secretId/secretKey
|
|
3344
3512
|
*/
|
|
@@ -3379,8 +3547,8 @@ async function getCloudBaseManager(options = {}) {
|
|
|
3379
3547
|
});
|
|
3380
3548
|
}
|
|
3381
3549
|
try {
|
|
3382
|
-
//
|
|
3383
|
-
const fallbackRegion = cloudBaseOptions?.region ?? process.env.TCB_REGION;
|
|
3550
|
+
// Region priority: explicit option > env var > ap-shanghai default
|
|
3551
|
+
const fallbackRegion = cloudBaseOptions?.region ?? process.env.TCB_REGION ?? 'ap-shanghai';
|
|
3384
3552
|
const loginState = authStrategy === 'ensure'
|
|
3385
3553
|
? await (0, auth_js_1.getLoginState)({ region: fallbackRegion })
|
|
3386
3554
|
: await (0, auth_js_1.peekLoginState)();
|
|
@@ -3428,24 +3596,19 @@ async function getCloudBaseManager(options = {}) {
|
|
|
3428
3596
|
}
|
|
3429
3597
|
}
|
|
3430
3598
|
}
|
|
3431
|
-
// envId priority:
|
|
3432
|
-
// Note: envManager.cachedEnvId has highest priority as it reflects user's latest environment switch
|
|
3433
|
-
// Region priority: envCandidates[].region (actual env region) > process.env.TCB_REGION > undefined
|
|
3434
|
-
// Resolving the environment's own region avoids INVALID_REGION errors when TCB_REGION
|
|
3435
|
-
// is set to a different region than the target environment.
|
|
3599
|
+
// envId priority: explicit option > envManager cache > loginState.envId
|
|
3436
3600
|
const resolvedEnvId = finalEnvId || loginEnvId;
|
|
3437
3601
|
let region = fallbackRegion;
|
|
3438
3602
|
if (resolvedEnvId && !cloudBaseOptions?.region) {
|
|
3439
3603
|
try {
|
|
3440
3604
|
const envCandidates = await listAvailableEnvCandidates({ loginState });
|
|
3441
|
-
const matchedEnv = envCandidates.find(
|
|
3605
|
+
const matchedEnv = envCandidates.find((candidate) => candidate.envId === resolvedEnvId);
|
|
3442
3606
|
if (matchedEnv?.region) {
|
|
3443
3607
|
region = matchedEnv.region;
|
|
3444
3608
|
(0, logger_js_1.debug)('使用环境实际 region:', { envId: resolvedEnvId, region });
|
|
3445
3609
|
}
|
|
3446
3610
|
}
|
|
3447
3611
|
catch {
|
|
3448
|
-
// If env listing fails, fall back to TCB_REGION / undefined
|
|
3449
3612
|
(0, logger_js_1.debug)('无法获取环境列表,使用 fallback region');
|
|
3450
3613
|
}
|
|
3451
3614
|
}
|
|
@@ -3478,8 +3641,8 @@ function createCloudBaseManagerWithOptions(cloudBaseOptions) {
|
|
|
3478
3641
|
hasSecretKey: !!cloudBaseOptions.secretKey,
|
|
3479
3642
|
hasToken: !!cloudBaseOptions.token,
|
|
3480
3643
|
});
|
|
3481
|
-
// Region priority:
|
|
3482
|
-
const region = cloudBaseOptions.region ?? process.env.TCB_REGION ??
|
|
3644
|
+
// Region priority: explicit option > env var > ap-shanghai default
|
|
3645
|
+
const region = cloudBaseOptions.region ?? process.env.TCB_REGION ?? 'ap-shanghai';
|
|
3483
3646
|
const manager = new manager_node_1.default({
|
|
3484
3647
|
...cloudBaseOptions,
|
|
3485
3648
|
proxy: cloudBaseOptions.proxy || process.env.http_proxy,
|
|
@@ -3546,20 +3709,12 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
3546
3709
|
exports.registerDatabaseTools = registerDatabaseTools;
|
|
3547
3710
|
const zod_1 = __webpack_require__(2971);
|
|
3548
3711
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
3712
|
+
const logger_js_1 = __webpack_require__(3039);
|
|
3549
3713
|
const CATEGORY = "NoSQL database";
|
|
3550
3714
|
const COLLECTION_READY_TIMEOUT_MS = 10000;
|
|
3551
3715
|
const COLLECTION_READY_POLL_INTERVAL_MS = 500;
|
|
3552
3716
|
/** Convert object values to JSON strings for API calls */
|
|
3553
3717
|
const toJSONString = (v) => typeof v === "object" && v !== null ? JSON.stringify(v) : v;
|
|
3554
|
-
// 获取数据库实例ID
|
|
3555
|
-
async function getDatabaseInstanceId(getManager) {
|
|
3556
|
-
const cloudbase = await getManager();
|
|
3557
|
-
const { EnvInfo } = await cloudbase.env.getEnvInfo();
|
|
3558
|
-
if (!EnvInfo?.Databases?.[0]?.InstanceId) {
|
|
3559
|
-
throw new Error("无法获取数据库实例ID");
|
|
3560
|
-
}
|
|
3561
|
-
return EnvInfo.Databases[0].InstanceId;
|
|
3562
|
-
}
|
|
3563
3718
|
function delay(ms) {
|
|
3564
3719
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3565
3720
|
}
|
|
@@ -3635,6 +3790,83 @@ function withCollectionName(collectionName, payload) {
|
|
|
3635
3790
|
collectionName,
|
|
3636
3791
|
};
|
|
3637
3792
|
}
|
|
3793
|
+
function logNoSqlLatency(toolName, phase, details) {
|
|
3794
|
+
(0, logger_js_1.debug)("[nosql-latency]", {
|
|
3795
|
+
toolName,
|
|
3796
|
+
phase,
|
|
3797
|
+
...details,
|
|
3798
|
+
});
|
|
3799
|
+
}
|
|
3800
|
+
function isInvalidInstanceIdError(error) {
|
|
3801
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
3802
|
+
return /(instance.?id|tag).*(invalid|错误|不存在|not exist|not found)|invalid.*(instance.?id|tag)/i.test(message);
|
|
3803
|
+
}
|
|
3804
|
+
async function resolveNoSqlInstanceId(options) {
|
|
3805
|
+
const startedAt = Date.now();
|
|
3806
|
+
const resolved = await (0, cloudbase_manager_js_1.getDatabaseInstanceId)({
|
|
3807
|
+
instanceId: options.instanceIdOverride,
|
|
3808
|
+
cloudBaseOptions: options.cloudBaseOptions,
|
|
3809
|
+
cloudbase: options.cloudbase,
|
|
3810
|
+
});
|
|
3811
|
+
logNoSqlLatency(options.toolName, "resolveInstanceId", {
|
|
3812
|
+
collectionName: options.collectionName,
|
|
3813
|
+
durationMs: Date.now() - startedAt,
|
|
3814
|
+
instanceIdSource: resolved.source,
|
|
3815
|
+
cacheKey: resolved.cacheKey,
|
|
3816
|
+
});
|
|
3817
|
+
return resolved;
|
|
3818
|
+
}
|
|
3819
|
+
async function callNoSqlContentApi(options) {
|
|
3820
|
+
const resolvedInstance = await resolveNoSqlInstanceId({
|
|
3821
|
+
toolName: options.toolName,
|
|
3822
|
+
cloudbase: options.cloudbase,
|
|
3823
|
+
cloudBaseOptions: options.cloudBaseOptions,
|
|
3824
|
+
instanceIdOverride: options.instanceIdOverride,
|
|
3825
|
+
collectionName: options.collectionName,
|
|
3826
|
+
});
|
|
3827
|
+
const startedAt = Date.now();
|
|
3828
|
+
try {
|
|
3829
|
+
const result = await options.cloudbase
|
|
3830
|
+
.commonService("tcb", "2018-06-08")
|
|
3831
|
+
.call({
|
|
3832
|
+
Action: options.action,
|
|
3833
|
+
Param: {
|
|
3834
|
+
...options.param,
|
|
3835
|
+
TableName: options.collectionName,
|
|
3836
|
+
Tag: resolvedInstance.instanceId,
|
|
3837
|
+
},
|
|
3838
|
+
});
|
|
3839
|
+
logNoSqlLatency(options.toolName, "cloudApiCall", {
|
|
3840
|
+
collectionName: options.collectionName,
|
|
3841
|
+
action: options.action,
|
|
3842
|
+
durationMs: Date.now() - startedAt,
|
|
3843
|
+
instanceIdSource: resolvedInstance.source,
|
|
3844
|
+
requestId: result?.RequestId,
|
|
3845
|
+
});
|
|
3846
|
+
return result;
|
|
3847
|
+
}
|
|
3848
|
+
catch (error) {
|
|
3849
|
+
logNoSqlLatency(options.toolName, "cloudApiError", {
|
|
3850
|
+
collectionName: options.collectionName,
|
|
3851
|
+
action: options.action,
|
|
3852
|
+
durationMs: Date.now() - startedAt,
|
|
3853
|
+
instanceIdSource: resolvedInstance.source,
|
|
3854
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3855
|
+
});
|
|
3856
|
+
if (!options.instanceIdOverride &&
|
|
3857
|
+
resolvedInstance.cacheKey &&
|
|
3858
|
+
isInvalidInstanceIdError(error)) {
|
|
3859
|
+
(0, cloudbase_manager_js_1.invalidateDatabaseInstanceIdCache)({
|
|
3860
|
+
cacheKey: resolvedInstance.cacheKey,
|
|
3861
|
+
});
|
|
3862
|
+
logNoSqlLatency(options.toolName, "invalidateInstanceIdCache", {
|
|
3863
|
+
collectionName: options.collectionName,
|
|
3864
|
+
cacheKey: resolvedInstance.cacheKey,
|
|
3865
|
+
});
|
|
3866
|
+
}
|
|
3867
|
+
throw error;
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3638
3870
|
async function waitForCollectionReady({ cloudbase, collectionName, logger, timeoutMs = COLLECTION_READY_TIMEOUT_MS, pollIntervalMs = COLLECTION_READY_POLL_INTERVAL_MS, }) {
|
|
3639
3871
|
const startedAt = Date.now();
|
|
3640
3872
|
const deadline = startedAt + timeoutMs;
|
|
@@ -3692,7 +3924,7 @@ function registerDatabaseTools(server) {
|
|
|
3692
3924
|
// readNoSqlDatabaseStructure
|
|
3693
3925
|
server.registerTool?.("readNoSqlDatabaseStructure", {
|
|
3694
3926
|
title: "读取 NoSQL 数据库结构",
|
|
3695
|
-
description: "读取 NoSQL
|
|
3927
|
+
description: "读取 NoSQL 数据库集合与索引结构,支持列出集合、查看集合详情、列出索引以及检查索引是否存在。",
|
|
3696
3928
|
inputSchema: {
|
|
3697
3929
|
action: zod_1.z.enum([
|
|
3698
3930
|
"listCollections",
|
|
@@ -3701,10 +3933,10 @@ function registerDatabaseTools(server) {
|
|
|
3701
3933
|
"listIndexes",
|
|
3702
3934
|
"checkIndex",
|
|
3703
3935
|
]).describe(`listCollections: 列出集合列表
|
|
3704
|
-
describeCollection:
|
|
3936
|
+
describeCollection: 描述集合详情(会返回索引摘要)
|
|
3705
3937
|
checkCollection: 检查集合是否存在
|
|
3706
|
-
listIndexes:
|
|
3707
|
-
checkIndex:
|
|
3938
|
+
listIndexes: 列出指定集合的索引列表
|
|
3939
|
+
checkIndex: 检查指定索引是否存在`),
|
|
3708
3940
|
limit: zod_1.z
|
|
3709
3941
|
.number()
|
|
3710
3942
|
.optional()
|
|
@@ -3840,38 +4072,42 @@ checkIndex: 检查索引是否存在`),
|
|
|
3840
4072
|
// writeNoSqlDatabaseStructure
|
|
3841
4073
|
server.registerTool?.("writeNoSqlDatabaseStructure", {
|
|
3842
4074
|
title: "修改 NoSQL 数据库结构",
|
|
3843
|
-
description: "修改 NoSQL
|
|
4075
|
+
description: "修改 NoSQL 数据库结构,支持创建/删除集合,以及通过 updateCollection 的 updateOptions.CreateIndexes / updateOptions.DropIndexes 添加索引和删除索引。",
|
|
3844
4076
|
inputSchema: {
|
|
3845
4077
|
action: zod_1.z.enum([
|
|
3846
4078
|
"createCollection",
|
|
3847
4079
|
"updateCollection",
|
|
3848
4080
|
"deleteCollection",
|
|
3849
4081
|
]).describe(`createCollection: 创建集合
|
|
3850
|
-
updateCollection:
|
|
4082
|
+
updateCollection: 更新集合配置;添加索引请传 updateOptions.CreateIndexes,删除索引请传 updateOptions.DropIndexes
|
|
3851
4083
|
deleteCollection: 删除集合`),
|
|
3852
4084
|
collectionName: zod_1.z.string().describe("集合名称"),
|
|
3853
4085
|
updateOptions: zod_1.z
|
|
3854
4086
|
.object({
|
|
3855
4087
|
CreateIndexes: zod_1.z
|
|
3856
4088
|
.array(zod_1.z.object({
|
|
3857
|
-
IndexName: zod_1.z.string(),
|
|
4089
|
+
IndexName: zod_1.z.string().describe("要创建的索引名称"),
|
|
3858
4090
|
MgoKeySchema: zod_1.z.object({
|
|
3859
|
-
MgoIsUnique: zod_1.z.boolean(),
|
|
4091
|
+
MgoIsUnique: zod_1.z.boolean().describe("是否唯一索引"),
|
|
3860
4092
|
MgoIndexKeys: zod_1.z.array(zod_1.z.object({
|
|
3861
|
-
Name: zod_1.z.string(),
|
|
3862
|
-
Direction: zod_1.z
|
|
3863
|
-
|
|
3864
|
-
|
|
4093
|
+
Name: zod_1.z.string().describe("索引字段名"),
|
|
4094
|
+
Direction: zod_1.z
|
|
4095
|
+
.string()
|
|
4096
|
+
.describe("索引方向,通常 1 表示升序,-1 表示降序"),
|
|
4097
|
+
})).describe("索引字段列表,支持单字段或复合索引"),
|
|
4098
|
+
}).describe("待创建索引的字段与约束配置"),
|
|
3865
4099
|
}))
|
|
3866
|
-
.optional()
|
|
4100
|
+
.optional()
|
|
4101
|
+
.describe("要添加的索引列表"),
|
|
3867
4102
|
DropIndexes: zod_1.z
|
|
3868
4103
|
.array(zod_1.z.object({
|
|
3869
|
-
IndexName: zod_1.z.string(),
|
|
4104
|
+
IndexName: zod_1.z.string().describe("要删除的索引名称"),
|
|
3870
4105
|
}))
|
|
3871
|
-
.optional()
|
|
4106
|
+
.optional()
|
|
4107
|
+
.describe("要删除的索引列表"),
|
|
3872
4108
|
})
|
|
3873
4109
|
.optional()
|
|
3874
|
-
.describe("更新选项(updateCollection 时使用)"),
|
|
4110
|
+
.describe("更新选项(updateCollection 时使用)。CreateIndexes 用于添加索引,DropIndexes 用于删除索引。"),
|
|
3875
4111
|
},
|
|
3876
4112
|
annotations: {
|
|
3877
4113
|
readOnlyHint: false,
|
|
@@ -3883,6 +4119,24 @@ deleteCollection: 删除集合`),
|
|
|
3883
4119
|
}, async ({ action, collectionName, updateOptions }) => {
|
|
3884
4120
|
const cloudbase = await getManager();
|
|
3885
4121
|
if (action === "createCollection") {
|
|
4122
|
+
const existsResult = await cloudbase.database.checkCollectionExists(collectionName);
|
|
4123
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, existsResult);
|
|
4124
|
+
if (existsResult.Exists) {
|
|
4125
|
+
return {
|
|
4126
|
+
content: [
|
|
4127
|
+
{
|
|
4128
|
+
type: "text",
|
|
4129
|
+
text: JSON.stringify(withCollectionName(collectionName, {
|
|
4130
|
+
success: true,
|
|
4131
|
+
action,
|
|
4132
|
+
requestId: existsResult.RequestId,
|
|
4133
|
+
message: "集合已存在,无需重复创建",
|
|
4134
|
+
exists: true,
|
|
4135
|
+
}), null, 2),
|
|
4136
|
+
},
|
|
4137
|
+
],
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
3886
4140
|
const result = await cloudbase.database.createCollection(collectionName);
|
|
3887
4141
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
3888
4142
|
await waitForCollectionReady({
|
|
@@ -3953,6 +4207,10 @@ deleteCollection: 删除集合`),
|
|
|
3953
4207
|
description: "查询并获取 NoSQL 数据库数据记录",
|
|
3954
4208
|
inputSchema: {
|
|
3955
4209
|
collectionName: zod_1.z.string().describe("集合名称"),
|
|
4210
|
+
instanceId: zod_1.z
|
|
4211
|
+
.string()
|
|
4212
|
+
.optional()
|
|
4213
|
+
.describe("可选:显式指定数据库实例ID;未传时会自动解析并缓存"),
|
|
3956
4214
|
query: zod_1.z
|
|
3957
4215
|
.union([zod_1.z.object({}).passthrough(), zod_1.z.string()])
|
|
3958
4216
|
.optional()
|
|
@@ -3981,24 +4239,37 @@ deleteCollection: 删除集合`),
|
|
|
3981
4239
|
openWorldHint: true,
|
|
3982
4240
|
category: CATEGORY,
|
|
3983
4241
|
},
|
|
3984
|
-
}, async ({ collectionName, query, projection, sort, limit, offset }) => {
|
|
4242
|
+
}, async ({ collectionName, instanceId, query, projection, sort, limit, offset }) => {
|
|
4243
|
+
const managerStartedAt = Date.now();
|
|
3985
4244
|
const cloudbase = await getManager();
|
|
3986
|
-
|
|
4245
|
+
logNoSqlLatency("readNoSqlDatabaseContent", "getManager", {
|
|
4246
|
+
collectionName,
|
|
4247
|
+
durationMs: Date.now() - managerStartedAt,
|
|
4248
|
+
});
|
|
3987
4249
|
const normalizedSort = normalizeSortInput(sort);
|
|
3988
|
-
const result = await
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4250
|
+
const result = await callNoSqlContentApi({
|
|
4251
|
+
toolName: "readNoSqlDatabaseContent",
|
|
4252
|
+
action: "QueryRecords",
|
|
4253
|
+
collectionName,
|
|
4254
|
+
cloudbase,
|
|
4255
|
+
cloudBaseOptions,
|
|
4256
|
+
instanceIdOverride: instanceId,
|
|
4257
|
+
param: {
|
|
3992
4258
|
MgoQuery: toJSONString(query),
|
|
3993
4259
|
MgoProjection: toJSONString(projection),
|
|
3994
4260
|
MgoSort: normalizedSort,
|
|
3995
4261
|
MgoLimit: limit ?? 100,
|
|
3996
4262
|
MgoOffset: offset,
|
|
3997
|
-
Tag: instanceId,
|
|
3998
4263
|
},
|
|
3999
4264
|
});
|
|
4000
4265
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
4266
|
+
const parseStartedAt = Date.now();
|
|
4001
4267
|
const documents = normalizeNoSqlDocuments(result.Data);
|
|
4268
|
+
logNoSqlLatency("readNoSqlDatabaseContent", "parseResult", {
|
|
4269
|
+
collectionName,
|
|
4270
|
+
durationMs: Date.now() - parseStartedAt,
|
|
4271
|
+
documentCount: documents.length,
|
|
4272
|
+
});
|
|
4002
4273
|
return {
|
|
4003
4274
|
content: [
|
|
4004
4275
|
{
|
|
@@ -4020,12 +4291,16 @@ deleteCollection: 删除集合`),
|
|
|
4020
4291
|
// writeNoSqlDatabaseContent
|
|
4021
4292
|
server.registerTool?.("writeNoSqlDatabaseContent", {
|
|
4022
4293
|
title: "修改 NoSQL 数据库数据记录",
|
|
4023
|
-
description: "修改 NoSQL 数据库数据记录。可按 MongoDB updateOne/updateMany 的心智模型理解:部分更新必须使用 $set/$inc/$push 等更新操作符;如果直接传 { field: value } 这类普通对象,底层会把它当作替换内容,存在覆盖整条文档的风险。更新嵌套对象中的某个字段时必须使用点号路径(如 { \"$set\": { \"address.city\": \"shenzhen\" } }),若写成 { \"$set\": { \"address\": { \"city\": \"shenzhen\" } } } 则整个 address
|
|
4294
|
+
description: "修改 NoSQL 数据库数据记录。可按 MongoDB updateOne/updateMany 的心智模型理解:部分更新必须使用 $set/$inc/$push 等更新操作符;如果直接传 { field: value } 这类普通对象,底层会把它当作替换内容,存在覆盖整条文档的风险。更新嵌套对象中的某个字段时必须使用点号路径(如 { \"$set\": { \"address.city\": \"shenzhen\" } }),若写成 { \"$set\": { \"address\": { \"city\": \"shenzhen\" } } } 则整个 address 对象会被替换,同级其他字段将丢失。若集合中的角色/档案文档会在前端通过 `db.collection(...).doc(uid)` 读取,请确保文档 `_id` 就是该 `uid`;不要用 `query={\"uid\":\"...\"}` + `upsert=true` 去更新 `users` / `profiles`,否则经常会生成一个不同的 `_id`,导致后续 `doc(uid)` 读取命中不到。",
|
|
4024
4295
|
inputSchema: {
|
|
4025
4296
|
action: zod_1.z
|
|
4026
4297
|
.enum(["insert", "update", "delete"])
|
|
4027
4298
|
.describe(`insert: 插入数据(新增文档)\nupdate: 更新数据\ndelete: 删除数据`),
|
|
4028
4299
|
collectionName: zod_1.z.string().describe("集合名称"),
|
|
4300
|
+
instanceId: zod_1.z
|
|
4301
|
+
.string()
|
|
4302
|
+
.optional()
|
|
4303
|
+
.describe("可选:显式指定数据库实例ID;未传时会自动解析并缓存"),
|
|
4029
4304
|
documents: zod_1.z
|
|
4030
4305
|
.array(zod_1.z.object({}).passthrough())
|
|
4031
4306
|
.optional()
|
|
@@ -4054,16 +4329,18 @@ deleteCollection: 删除集合`),
|
|
|
4054
4329
|
openWorldHint: true,
|
|
4055
4330
|
category: CATEGORY,
|
|
4056
4331
|
},
|
|
4057
|
-
}, async ({ action, collectionName, documents, query, update, isMulti, upsert, }) => {
|
|
4332
|
+
}, async ({ action, collectionName, instanceId, documents, query, update, isMulti, upsert, }) => {
|
|
4058
4333
|
if (action === "insert") {
|
|
4059
4334
|
if (!documents) {
|
|
4060
4335
|
throw new Error("insert 操作时必须提供 documents");
|
|
4061
4336
|
}
|
|
4062
4337
|
const text = await insertDocuments({
|
|
4063
4338
|
collectionName,
|
|
4339
|
+
instanceId,
|
|
4064
4340
|
documents,
|
|
4065
4341
|
getManager,
|
|
4066
4342
|
logger,
|
|
4343
|
+
cloudBaseOptions,
|
|
4067
4344
|
});
|
|
4068
4345
|
return {
|
|
4069
4346
|
content: [
|
|
@@ -4083,12 +4360,14 @@ deleteCollection: 删除集合`),
|
|
|
4083
4360
|
}
|
|
4084
4361
|
const text = await updateDocuments({
|
|
4085
4362
|
collectionName,
|
|
4363
|
+
instanceId,
|
|
4086
4364
|
query,
|
|
4087
4365
|
update,
|
|
4088
4366
|
isMulti,
|
|
4089
4367
|
upsert,
|
|
4090
4368
|
getManager,
|
|
4091
4369
|
logger,
|
|
4370
|
+
cloudBaseOptions,
|
|
4092
4371
|
});
|
|
4093
4372
|
return {
|
|
4094
4373
|
content: [
|
|
@@ -4105,10 +4384,12 @@ deleteCollection: 删除集合`),
|
|
|
4105
4384
|
}
|
|
4106
4385
|
const text = await deleteDocuments({
|
|
4107
4386
|
collectionName,
|
|
4387
|
+
instanceId,
|
|
4108
4388
|
query,
|
|
4109
4389
|
isMulti,
|
|
4110
4390
|
getManager,
|
|
4111
4391
|
logger,
|
|
4392
|
+
cloudBaseOptions,
|
|
4112
4393
|
});
|
|
4113
4394
|
return {
|
|
4114
4395
|
content: [
|
|
@@ -4122,16 +4403,24 @@ deleteCollection: 删除集合`),
|
|
|
4122
4403
|
throw new Error(`不支持的操作类型: ${action}`);
|
|
4123
4404
|
});
|
|
4124
4405
|
}
|
|
4125
|
-
async function insertDocuments({ collectionName, documents, getManager, logger, }) {
|
|
4406
|
+
async function insertDocuments({ collectionName, instanceId, documents, getManager, logger, cloudBaseOptions, }) {
|
|
4407
|
+
const managerStartedAt = Date.now();
|
|
4126
4408
|
const cloudbase = await getManager();
|
|
4127
|
-
|
|
4409
|
+
logNoSqlLatency("writeNoSqlDatabaseContent", "getManager", {
|
|
4410
|
+
collectionName,
|
|
4411
|
+
action: "insert",
|
|
4412
|
+
durationMs: Date.now() - managerStartedAt,
|
|
4413
|
+
});
|
|
4128
4414
|
const docsAsStrings = documents.map((doc) => JSON.stringify(doc));
|
|
4129
|
-
const result = await
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4415
|
+
const result = await callNoSqlContentApi({
|
|
4416
|
+
toolName: "writeNoSqlDatabaseContent",
|
|
4417
|
+
action: "PutItem",
|
|
4418
|
+
collectionName,
|
|
4419
|
+
cloudbase,
|
|
4420
|
+
cloudBaseOptions,
|
|
4421
|
+
instanceIdOverride: instanceId,
|
|
4422
|
+
param: {
|
|
4133
4423
|
MgoDocs: docsAsStrings,
|
|
4134
|
-
Tag: instanceId,
|
|
4135
4424
|
},
|
|
4136
4425
|
});
|
|
4137
4426
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(logger, result);
|
|
@@ -4145,18 +4434,31 @@ async function insertDocuments({ collectionName, documents, getManager, logger,
|
|
|
4145
4434
|
message: "文档插入成功",
|
|
4146
4435
|
}), null, 2);
|
|
4147
4436
|
}
|
|
4148
|
-
async function updateDocuments({ collectionName, query, update, isMulti, upsert, getManager, logger, }) {
|
|
4437
|
+
async function updateDocuments({ collectionName, instanceId, query, update, isMulti, upsert, getManager, logger, cloudBaseOptions, }) {
|
|
4438
|
+
const managerStartedAt = Date.now();
|
|
4149
4439
|
const cloudbase = await getManager();
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4440
|
+
logNoSqlLatency("writeNoSqlDatabaseContent", "getManager", {
|
|
4441
|
+
collectionName,
|
|
4442
|
+
action: "update",
|
|
4443
|
+
durationMs: Date.now() - managerStartedAt,
|
|
4444
|
+
});
|
|
4445
|
+
const authLinkedDocWarning = buildAuthLinkedDocWarning({
|
|
4446
|
+
collectionName,
|
|
4447
|
+
query,
|
|
4448
|
+
upsert,
|
|
4449
|
+
});
|
|
4450
|
+
const result = await callNoSqlContentApi({
|
|
4451
|
+
toolName: "writeNoSqlDatabaseContent",
|
|
4452
|
+
action: "UpdateItem",
|
|
4453
|
+
collectionName,
|
|
4454
|
+
cloudbase,
|
|
4455
|
+
cloudBaseOptions,
|
|
4456
|
+
instanceIdOverride: instanceId,
|
|
4457
|
+
param: {
|
|
4155
4458
|
MgoQuery: toJSONString(query),
|
|
4156
4459
|
MgoUpdate: toJSONString(update),
|
|
4157
4460
|
MgoIsMulti: isMulti,
|
|
4158
4461
|
MgoUpsert: upsert,
|
|
4159
|
-
Tag: instanceId,
|
|
4160
4462
|
},
|
|
4161
4463
|
});
|
|
4162
4464
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(logger, result);
|
|
@@ -4166,19 +4468,56 @@ async function updateDocuments({ collectionName, query, update, isMulti, upsert,
|
|
|
4166
4468
|
modifiedCount: result.ModifiedNum,
|
|
4167
4469
|
matchedCount: result.MatchedNum,
|
|
4168
4470
|
upsertedId: result.UpsertedId,
|
|
4169
|
-
|
|
4471
|
+
...(authLinkedDocWarning ? { warning: authLinkedDocWarning } : {}),
|
|
4472
|
+
message: authLinkedDocWarning ? `文档更新成功;${authLinkedDocWarning}` : "文档更新成功",
|
|
4170
4473
|
}), null, 2);
|
|
4171
4474
|
}
|
|
4172
|
-
|
|
4475
|
+
function tryParseObjectLike(value) {
|
|
4476
|
+
if (typeof value === "string") {
|
|
4477
|
+
try {
|
|
4478
|
+
const parsed = JSON.parse(value);
|
|
4479
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
4480
|
+
? parsed
|
|
4481
|
+
: null;
|
|
4482
|
+
}
|
|
4483
|
+
catch {
|
|
4484
|
+
return null;
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
4488
|
+
? value
|
|
4489
|
+
: null;
|
|
4490
|
+
}
|
|
4491
|
+
function buildAuthLinkedDocWarning({ collectionName, query, upsert, }) {
|
|
4492
|
+
if (!upsert)
|
|
4493
|
+
return null;
|
|
4494
|
+
if (!["users", "profiles", "user_roles", "userProfiles"].includes(collectionName))
|
|
4495
|
+
return null;
|
|
4496
|
+
const queryObject = tryParseObjectLike(query);
|
|
4497
|
+
if (!queryObject)
|
|
4498
|
+
return null;
|
|
4499
|
+
if (!("uid" in queryObject) && !("userId" in queryObject))
|
|
4500
|
+
return null;
|
|
4501
|
+
return "若前端会用 doc(uid) 读取该集合,请改为直接创建 `_id = uid` 的文档;基于 uid 查询再 upsert 往往会生成不同的 `_id`,导致后续 doc(uid) 读取失败。";
|
|
4502
|
+
}
|
|
4503
|
+
async function deleteDocuments({ collectionName, instanceId, query, isMulti, getManager, logger, cloudBaseOptions, }) {
|
|
4504
|
+
const managerStartedAt = Date.now();
|
|
4173
4505
|
const cloudbase = await getManager();
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4506
|
+
logNoSqlLatency("writeNoSqlDatabaseContent", "getManager", {
|
|
4507
|
+
collectionName,
|
|
4508
|
+
action: "delete",
|
|
4509
|
+
durationMs: Date.now() - managerStartedAt,
|
|
4510
|
+
});
|
|
4511
|
+
const result = await callNoSqlContentApi({
|
|
4512
|
+
toolName: "writeNoSqlDatabaseContent",
|
|
4513
|
+
action: "DeleteItem",
|
|
4514
|
+
collectionName,
|
|
4515
|
+
cloudbase,
|
|
4516
|
+
cloudBaseOptions,
|
|
4517
|
+
instanceIdOverride: instanceId,
|
|
4518
|
+
param: {
|
|
4179
4519
|
MgoQuery: toJSONString(query),
|
|
4180
4520
|
MgoIsMulti: isMulti,
|
|
4181
|
-
Tag: instanceId,
|
|
4182
4521
|
},
|
|
4183
4522
|
});
|
|
4184
4523
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(logger, result);
|
|
@@ -5243,6 +5582,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5243
5582
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5244
5583
|
};
|
|
5245
5584
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
5585
|
+
exports.resolveSkillSearchRoots = resolveSkillSearchRoots;
|
|
5246
5586
|
exports.getClaudePrompt = getClaudePrompt;
|
|
5247
5587
|
exports.registerRagTools = registerRagTools;
|
|
5248
5588
|
const adm_zip_1 = __importDefault(__webpack_require__(2872));
|
|
@@ -5251,10 +5591,20 @@ const lockfile_1 = __importDefault(__webpack_require__(959));
|
|
|
5251
5591
|
const os = __importStar(__webpack_require__(8116));
|
|
5252
5592
|
const path = __importStar(__webpack_require__(2521));
|
|
5253
5593
|
const zod_1 = __webpack_require__(2971);
|
|
5594
|
+
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
5254
5595
|
const claude_prompt_js_1 = __webpack_require__(8359);
|
|
5596
|
+
const json_content_js_1 = __webpack_require__(4365);
|
|
5255
5597
|
const logger_js_1 = __webpack_require__(3039);
|
|
5256
5598
|
// 1. 枚举定义
|
|
5257
5599
|
const KnowledgeBaseEnum = zod_1.z.enum(["cloudbase", "scf", "miniprogram"]);
|
|
5600
|
+
const SearchKnowledgeModeEnum = zod_1.z.enum(["vector", "skill", "openapi", "docs"]);
|
|
5601
|
+
const CloudBaseDocsActionEnum = zod_1.z.enum([
|
|
5602
|
+
"listModules",
|
|
5603
|
+
"listModuleDocs",
|
|
5604
|
+
"findByName",
|
|
5605
|
+
"readDoc",
|
|
5606
|
+
"searchDocs",
|
|
5607
|
+
]);
|
|
5258
5608
|
// 2. 枚举到后端 id 的映射
|
|
5259
5609
|
const KnowledgeBaseIdMap = {
|
|
5260
5610
|
cloudbase: "ykfzskv4_ad28",
|
|
@@ -5314,6 +5664,39 @@ else {
|
|
|
5314
5664
|
}
|
|
5315
5665
|
// 共享的下载 Promise,防止并发重复下载
|
|
5316
5666
|
let resourceDownloadPromise = null;
|
|
5667
|
+
async function filterExistingDirs(pathsToCheck) {
|
|
5668
|
+
const resolved = [];
|
|
5669
|
+
for (const candidate of pathsToCheck) {
|
|
5670
|
+
if (!candidate || resolved.includes(candidate)) {
|
|
5671
|
+
continue;
|
|
5672
|
+
}
|
|
5673
|
+
try {
|
|
5674
|
+
const stat = await fs.stat(candidate);
|
|
5675
|
+
if (stat.isDirectory()) {
|
|
5676
|
+
resolved.push(candidate);
|
|
5677
|
+
}
|
|
5678
|
+
}
|
|
5679
|
+
catch {
|
|
5680
|
+
// Ignore missing candidates and keep checking fallbacks.
|
|
5681
|
+
}
|
|
5682
|
+
}
|
|
5683
|
+
return resolved;
|
|
5684
|
+
}
|
|
5685
|
+
async function resolveSkillSearchRoots(options = {}) {
|
|
5686
|
+
const cliEntryPath = options.cliEntryPath ?? process.argv[1];
|
|
5687
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
5688
|
+
const repoRoot = cliEntryPath
|
|
5689
|
+
? path.resolve(path.dirname(cliEntryPath), "..", "..")
|
|
5690
|
+
: undefined;
|
|
5691
|
+
return filterExistingDirs([
|
|
5692
|
+
repoRoot
|
|
5693
|
+
? path.join(repoRoot, ".generated", "compat-config", ".codebuddy", "skills")
|
|
5694
|
+
: undefined,
|
|
5695
|
+
repoRoot ? path.join(repoRoot, "config", "source", "skills") : undefined,
|
|
5696
|
+
options.fallbackSkillsRoot,
|
|
5697
|
+
path.join(homeDir, ".cloudbase-mcp", "web-template", ".claude", "skills"),
|
|
5698
|
+
]);
|
|
5699
|
+
}
|
|
5317
5700
|
// 检查缓存是否可用(未过期)
|
|
5318
5701
|
async function canUseCache() {
|
|
5319
5702
|
try {
|
|
@@ -5371,6 +5754,29 @@ function safeStringify(obj) {
|
|
|
5371
5754
|
return "";
|
|
5372
5755
|
}
|
|
5373
5756
|
}
|
|
5757
|
+
function buildDocsEnvelope(action, data, message) {
|
|
5758
|
+
return {
|
|
5759
|
+
success: true,
|
|
5760
|
+
data: {
|
|
5761
|
+
action,
|
|
5762
|
+
...data,
|
|
5763
|
+
},
|
|
5764
|
+
message,
|
|
5765
|
+
};
|
|
5766
|
+
}
|
|
5767
|
+
function buildDocsErrorEnvelope(error) {
|
|
5768
|
+
return {
|
|
5769
|
+
success: false,
|
|
5770
|
+
data: {},
|
|
5771
|
+
message: error instanceof Error ? error.message : String(error),
|
|
5772
|
+
};
|
|
5773
|
+
}
|
|
5774
|
+
function requireStringParam(value, fieldName, action) {
|
|
5775
|
+
if (!value?.trim()) {
|
|
5776
|
+
throw new Error(`action=${action} 时必须提供 ${fieldName}`);
|
|
5777
|
+
}
|
|
5778
|
+
return value.trim();
|
|
5779
|
+
}
|
|
5374
5780
|
// OpenAPI 文档 URL 列表
|
|
5375
5781
|
const OPENAPI_SOURCES = [
|
|
5376
5782
|
{
|
|
@@ -5676,21 +6082,38 @@ async function registerRagTools(server) {
|
|
|
5676
6082
|
});
|
|
5677
6083
|
let openapis = [];
|
|
5678
6084
|
let skills = [];
|
|
6085
|
+
let fallbackSkillsRoot;
|
|
5679
6086
|
try {
|
|
5680
6087
|
const { webTemplateDir, openAPIDocs } = await downloadResources();
|
|
5681
6088
|
openapis = openAPIDocs;
|
|
5682
|
-
|
|
6089
|
+
fallbackSkillsRoot = path.join(webTemplateDir, ".claude", "skills");
|
|
5683
6090
|
}
|
|
5684
6091
|
catch (error) {
|
|
5685
6092
|
(0, logger_js_1.warn)("[downloadResources] Failed to download resources", {
|
|
5686
6093
|
error,
|
|
5687
6094
|
});
|
|
5688
6095
|
}
|
|
6096
|
+
try {
|
|
6097
|
+
const skillRoots = await resolveSkillSearchRoots({
|
|
6098
|
+
fallbackSkillsRoot,
|
|
6099
|
+
});
|
|
6100
|
+
const preferredSkillRoot = skillRoots[0];
|
|
6101
|
+
if (preferredSkillRoot) {
|
|
6102
|
+
skills = await collectSkillDescriptions(preferredSkillRoot);
|
|
6103
|
+
}
|
|
6104
|
+
}
|
|
6105
|
+
catch (error) {
|
|
6106
|
+
(0, logger_js_1.warn)("[registerRagTools] Failed to resolve local skill roots", {
|
|
6107
|
+
error,
|
|
6108
|
+
});
|
|
6109
|
+
}
|
|
6110
|
+
const getDocsManager = () => (0, cloudbase_manager_js_1.createCloudBaseManagerWithOptions)(server.cloudBaseOptions ?? {})
|
|
6111
|
+
?.docs;
|
|
5689
6112
|
server.registerTool?.("searchKnowledgeBase", {
|
|
5690
6113
|
title: "云开发知识库检索",
|
|
5691
|
-
description: `云开发知识库智能检索工具,支持向量查询 (vector)、固定技能文档 (skill)
|
|
6114
|
+
description: `云开发知识库智能检索工具,支持向量查询 (vector)、固定技能文档 (skill)、OpenAPI 文档 (openapi) 和 CloudBase 官方文档 (docs) 查询。
|
|
5692
6115
|
|
|
5693
|
-
强烈推荐始终优先使用固定技能文档 (skill)
|
|
6116
|
+
强烈推荐始终优先使用固定技能文档 (skill)、OpenAPI 文档 (openapi) 或 CloudBase 官方文档 (docs) 模式进行检索,仅当固定文档无法覆盖你的问题时,再使用向量查询 (vector) 模式。
|
|
5694
6117
|
|
|
5695
6118
|
固定技能文档 (skill) 查询当前支持 ${skills.length} 个固定文档,分别是:
|
|
5696
6119
|
${skills
|
|
@@ -5702,7 +6125,7 @@ async function registerRagTools(server) {
|
|
|
5702
6125
|
.map((api) => `API名:${api.name} API介绍:${api.description}`)
|
|
5703
6126
|
.join("\n")}`,
|
|
5704
6127
|
inputSchema: {
|
|
5705
|
-
mode:
|
|
6128
|
+
mode: SearchKnowledgeModeEnum,
|
|
5706
6129
|
skillName: zod_1.z
|
|
5707
6130
|
.enum(skills.map((skill) => path.basename(path.dirname(skill.absolutePath))))
|
|
5708
6131
|
.optional()
|
|
@@ -5711,6 +6134,23 @@ async function registerRagTools(server) {
|
|
|
5711
6134
|
.enum(openapis.map((api) => api.name))
|
|
5712
6135
|
.optional()
|
|
5713
6136
|
.describe("mode=openapi 时指定。API 名称。"),
|
|
6137
|
+
action: CloudBaseDocsActionEnum.optional().describe("mode=docs 时指定。CloudBase 文档操作类型:listModules=列出所有文档模块,listModuleDocs=获取指定模块的目录结构,findByName=按名称/路径/URL 智能查找,readDoc=读取指定文档 Markdown,searchDocs=全文搜索官方文档。"),
|
|
6138
|
+
moduleName: zod_1.z
|
|
6139
|
+
.string()
|
|
6140
|
+
.optional()
|
|
6141
|
+
.describe("mode=docs 且 action=listModuleDocs 时指定。模块名称。"),
|
|
6142
|
+
input: zod_1.z
|
|
6143
|
+
.string()
|
|
6144
|
+
.optional()
|
|
6145
|
+
.describe("mode=docs 且 action=findByName 时指定。支持模块名、文档标题、层级路径或 URL。"),
|
|
6146
|
+
docPath: zod_1.z
|
|
6147
|
+
.string()
|
|
6148
|
+
.optional()
|
|
6149
|
+
.describe("mode=docs 且 action=readDoc 时指定。文档相对路径或完整 URL。"),
|
|
6150
|
+
query: zod_1.z
|
|
6151
|
+
.string()
|
|
6152
|
+
.optional()
|
|
6153
|
+
.describe("mode=docs 且 action=searchDocs 时指定。全文检索关键词。"),
|
|
5714
6154
|
threshold: zod_1.z
|
|
5715
6155
|
.number()
|
|
5716
6156
|
.default(0.5)
|
|
@@ -5740,14 +6180,62 @@ async function registerRagTools(server) {
|
|
|
5740
6180
|
openWorldHint: true,
|
|
5741
6181
|
category: "rag",
|
|
5742
6182
|
},
|
|
5743
|
-
}, async ({ id, content, options
|
|
6183
|
+
}, async ({ id, content, options, limit = 5, threshold = 0.5, mode, skillName, apiName, action, moduleName, input, docPath, query, }) => {
|
|
6184
|
+
const chunkExpand = options?.chunkExpand ?? [3, 3];
|
|
6185
|
+
if (mode === "docs") {
|
|
6186
|
+
try {
|
|
6187
|
+
const resolvedAction = action;
|
|
6188
|
+
if (!resolvedAction) {
|
|
6189
|
+
throw new Error("mode=docs 时必须提供 action");
|
|
6190
|
+
}
|
|
6191
|
+
const docsManager = getDocsManager();
|
|
6192
|
+
if (!docsManager) {
|
|
6193
|
+
throw new Error("当前 @cloudbase/manager-node 实例不支持 app.docs,请确认版本 >= 5.0.0。");
|
|
6194
|
+
}
|
|
6195
|
+
if (resolvedAction === "listModules") {
|
|
6196
|
+
const modules = await docsManager.listModules();
|
|
6197
|
+
return (0, json_content_js_1.jsonContent)(buildDocsEnvelope(resolvedAction, { modules }, "CloudBase 文档模块列表获取成功"));
|
|
6198
|
+
}
|
|
6199
|
+
if (resolvedAction === "listModuleDocs") {
|
|
6200
|
+
const resolvedModuleName = requireStringParam(moduleName, "moduleName", resolvedAction);
|
|
6201
|
+
const docs = await docsManager.listModuleDocs(resolvedModuleName);
|
|
6202
|
+
return (0, json_content_js_1.jsonContent)(buildDocsEnvelope(resolvedAction, { moduleName: resolvedModuleName, docs }, "CloudBase 模块文档目录获取成功"));
|
|
6203
|
+
}
|
|
6204
|
+
if (resolvedAction === "findByName") {
|
|
6205
|
+
const resolvedInput = requireStringParam(input, "input", resolvedAction);
|
|
6206
|
+
const result = await docsManager.findByName(resolvedInput);
|
|
6207
|
+
return (0, json_content_js_1.jsonContent)(buildDocsEnvelope(resolvedAction, { input: resolvedInput, result }, "CloudBase 文档查找成功"));
|
|
6208
|
+
}
|
|
6209
|
+
if (resolvedAction === "readDoc") {
|
|
6210
|
+
const resolvedDocPath = requireStringParam(docPath, "docPath", resolvedAction);
|
|
6211
|
+
const markdown = await docsManager.readDoc(resolvedDocPath);
|
|
6212
|
+
return (0, json_content_js_1.jsonContent)(buildDocsEnvelope(resolvedAction, { docPath: resolvedDocPath, content: markdown }, "CloudBase 文档读取成功"));
|
|
6213
|
+
}
|
|
6214
|
+
const resolvedQuery = requireStringParam(query, "query", resolvedAction);
|
|
6215
|
+
const results = await docsManager.searchDocs(resolvedQuery);
|
|
6216
|
+
return (0, json_content_js_1.jsonContent)(buildDocsEnvelope(resolvedAction, { query: resolvedQuery, results }, "CloudBase 文档搜索成功"));
|
|
6217
|
+
}
|
|
6218
|
+
catch (error) {
|
|
6219
|
+
return (0, json_content_js_1.jsonContent)(buildDocsErrorEnvelope(error));
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
5744
6222
|
if (mode === "skill") {
|
|
5745
|
-
const
|
|
6223
|
+
const skill = skills.find((item) => item.absolutePath.includes(skillName));
|
|
6224
|
+
if (!skill) {
|
|
6225
|
+
return {
|
|
6226
|
+
content: [
|
|
6227
|
+
{
|
|
6228
|
+
type: "text",
|
|
6229
|
+
text: `Skill document "${skillName}" not found. Available skill docs: ${skills.map((item) => path.basename(path.dirname(item.absolutePath))).join(", ") || "none"}`,
|
|
6230
|
+
},
|
|
6231
|
+
],
|
|
6232
|
+
};
|
|
6233
|
+
}
|
|
5746
6234
|
return {
|
|
5747
6235
|
content: [
|
|
5748
6236
|
{
|
|
5749
6237
|
type: "text",
|
|
5750
|
-
text: `The skill doc's absolute path is: ${absolutePath}. ${(await fs.readFile(absolutePath)).toString()}`,
|
|
6238
|
+
text: `The skill doc's absolute path is: ${skill.absolutePath}. ${(await fs.readFile(skill.absolutePath)).toString()}`,
|
|
5751
6239
|
},
|
|
5752
6240
|
],
|
|
5753
6241
|
};
|
|
@@ -5759,7 +6247,7 @@ async function registerRagTools(server) {
|
|
|
5759
6247
|
content: [
|
|
5760
6248
|
{
|
|
5761
6249
|
type: "text",
|
|
5762
|
-
text: `OpenAPI document "${apiName}" not found. Available APIs: ${openapis.map((a) => a.name).join(", ")}`,
|
|
6250
|
+
text: `OpenAPI document "${apiName}" not found. Available APIs: ${openapis.map((a) => a.name).join(", ") || "none"}`,
|
|
5763
6251
|
},
|
|
5764
6252
|
],
|
|
5765
6253
|
};
|
|
@@ -6106,12 +6594,25 @@ function registerGatewayTools(server) {
|
|
|
6106
6594
|
}
|
|
6107
6595
|
const cloudbase = await getManager();
|
|
6108
6596
|
const accessPath = normalizeAccessPath(input.path || `/${input.targetName}`);
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6597
|
+
let result;
|
|
6598
|
+
try {
|
|
6599
|
+
result = await cloudbase.access.createAccess({
|
|
6600
|
+
name: input.targetName,
|
|
6601
|
+
path: accessPath,
|
|
6602
|
+
type: ((input.type || "Event") === "HTTP" ? 6 : 1),
|
|
6603
|
+
auth: input.auth,
|
|
6604
|
+
});
|
|
6605
|
+
}
|
|
6606
|
+
catch (err) {
|
|
6607
|
+
if (err.message && err.message.includes("An error has occurred")) {
|
|
6608
|
+
let hint = "为目标资源配置访问路由失败(后端内部错误)。请确保:1) 目标云函数已成功创建并处于 Active 状态;2) 环境默认 HTTP 域名已完成初始化;3) 该访问路径未被占用。";
|
|
6609
|
+
if (input.type === "HTTP") {
|
|
6610
|
+
hint += "此外注意:如果目标函数最初是作为 Event 函数创建的,这里 type 必须依然传 Event(或省略),传 HTTP 会导致此错误。";
|
|
6611
|
+
}
|
|
6612
|
+
throw new Error(`${hint} 原始错误:${err.message}`);
|
|
6613
|
+
}
|
|
6614
|
+
throw err;
|
|
6615
|
+
}
|
|
6115
6616
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
6116
6617
|
return buildEnvelope({
|
|
6117
6618
|
action: input.action,
|
|
@@ -6287,7 +6788,7 @@ function registerGatewayTools(server) {
|
|
|
6287
6788
|
.describe("目标资源类型。当前支持 function,后续可扩展"),
|
|
6288
6789
|
targetName: zod_1.z.string().optional().describe("目标资源名称"),
|
|
6289
6790
|
path: zod_1.z.string().optional().describe("访问路径,默认 /{targetName}"),
|
|
6290
|
-
type: zod_1.z.enum(["Event", "HTTP"]).optional().describe("
|
|
6791
|
+
type: zod_1.z.enum(["Event", "HTTP"]).optional().describe("目标函数的本身类型(非接入形式)。如果被访问的函数是 Event 型(默认),此处必须传 Event;只有当被访问函数在创建时就是 HTTP 函数时才传 HTTP。"),
|
|
6291
6792
|
auth: zod_1.z.boolean().optional().describe("是否开启鉴权"),
|
|
6292
6793
|
route: zod_1.z
|
|
6293
6794
|
.object({
|
|
@@ -7524,28 +8025,36 @@ exports.registerAppAuthTools = registerAppAuthTools;
|
|
|
7524
8025
|
const zod_1 = __webpack_require__(2971);
|
|
7525
8026
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
7526
8027
|
const json_content_js_1 = __webpack_require__(4365);
|
|
8028
|
+
const tool_result_js_1 = __webpack_require__(9835);
|
|
7527
8029
|
const QUERY_APP_AUTH_ACTIONS = [
|
|
7528
8030
|
"getLoginConfig",
|
|
7529
8031
|
"listProviders",
|
|
7530
8032
|
"getProvider",
|
|
7531
8033
|
"getClientConfig",
|
|
7532
|
-
"
|
|
8034
|
+
"getPublishableKey",
|
|
7533
8035
|
"getStaticDomain",
|
|
8036
|
+
"listApiKeys",
|
|
7534
8037
|
];
|
|
7535
8038
|
const MANAGE_APP_AUTH_ACTIONS = [
|
|
7536
|
-
"
|
|
8039
|
+
"patchLoginStrategy",
|
|
8040
|
+
"addProvider",
|
|
7537
8041
|
"updateProvider",
|
|
8042
|
+
"deleteProvider",
|
|
7538
8043
|
"updateClientConfig",
|
|
7539
|
-
"
|
|
8044
|
+
"ensurePublishableKey",
|
|
8045
|
+
"createApiKey",
|
|
8046
|
+
"deleteApiKey",
|
|
7540
8047
|
"createCustomLoginKeys",
|
|
7541
8048
|
];
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
}
|
|
7548
|
-
}
|
|
8049
|
+
const APP_AUTH_KEY_TYPES = ["publish_key", "api_key"];
|
|
8050
|
+
const SUPABASE_LIKE_SDK_HINTS = {
|
|
8051
|
+
phoneOtp: "auth.signInWithOtp({ phone })",
|
|
8052
|
+
emailOtp: "auth.signInWithOtp({ email })",
|
|
8053
|
+
password: "auth.signInWithPassword({ username|email|phone, password })",
|
|
8054
|
+
signup: "auth.signUp({ phone|email, ... })",
|
|
8055
|
+
verifyOtp: "verifyOtp({ token })",
|
|
8056
|
+
anonymous: "auth.signInAnonymously()",
|
|
8057
|
+
};
|
|
7549
8058
|
function buildErrorEnvelope(error) {
|
|
7550
8059
|
return {
|
|
7551
8060
|
success: false,
|
|
@@ -7553,6 +8062,19 @@ function buildErrorEnvelope(error) {
|
|
|
7553
8062
|
message: error instanceof Error ? error.message : String(error),
|
|
7554
8063
|
};
|
|
7555
8064
|
}
|
|
8065
|
+
function buildShortError(error) {
|
|
8066
|
+
return {
|
|
8067
|
+
success: false,
|
|
8068
|
+
error,
|
|
8069
|
+
};
|
|
8070
|
+
}
|
|
8071
|
+
function buildShortErrorWithCode(error, code) {
|
|
8072
|
+
return {
|
|
8073
|
+
success: false,
|
|
8074
|
+
error,
|
|
8075
|
+
code,
|
|
8076
|
+
};
|
|
8077
|
+
}
|
|
7556
8078
|
function normalizePlainObject(value, label) {
|
|
7557
8079
|
if (value === undefined) {
|
|
7558
8080
|
return undefined;
|
|
@@ -7562,6 +8084,25 @@ function normalizePlainObject(value, label) {
|
|
|
7562
8084
|
}
|
|
7563
8085
|
return value;
|
|
7564
8086
|
}
|
|
8087
|
+
function normalizeLocalizedMessage(value, label, fallback) {
|
|
8088
|
+
if (typeof value === "string") {
|
|
8089
|
+
return { Message: value };
|
|
8090
|
+
}
|
|
8091
|
+
const normalized = normalizePlainObject(value, label);
|
|
8092
|
+
if (normalized) {
|
|
8093
|
+
return normalized;
|
|
8094
|
+
}
|
|
8095
|
+
if (fallback) {
|
|
8096
|
+
return { Message: fallback };
|
|
8097
|
+
}
|
|
8098
|
+
throw new Error(`${label} 必须是字符串或对象`);
|
|
8099
|
+
}
|
|
8100
|
+
function omitKeys(value, keys) {
|
|
8101
|
+
if (!value) {
|
|
8102
|
+
return undefined;
|
|
8103
|
+
}
|
|
8104
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => !keys.includes(key)));
|
|
8105
|
+
}
|
|
7565
8106
|
function extractLoginStrategyState(value) {
|
|
7566
8107
|
const source = normalizePlainObject(value, "loginStrategy") ?? {};
|
|
7567
8108
|
const data = normalizePlainObject(source.Data, "loginStrategy.Data") ?? source;
|
|
@@ -7577,13 +8118,154 @@ function extractLoginStrategyState(value) {
|
|
|
7577
8118
|
};
|
|
7578
8119
|
}
|
|
7579
8120
|
function normalizeLoginConfigPatch(value) {
|
|
7580
|
-
const { PhoneLogin, UsernameLogin, ...rest } = value;
|
|
8121
|
+
const { PhoneLogin, UsernameLogin, usernamePassword, email, anonymous, phone, ...rest } = value;
|
|
7581
8122
|
return {
|
|
7582
8123
|
...rest,
|
|
7583
8124
|
...(typeof PhoneLogin === "boolean" ? { PhoneNumberLogin: PhoneLogin } : {}),
|
|
7584
8125
|
...(typeof UsernameLogin === "boolean" ? { UserNameLogin: UsernameLogin } : {}),
|
|
8126
|
+
...(typeof usernamePassword === "boolean" ? { UserNameLogin: usernamePassword } : {}),
|
|
8127
|
+
...(typeof email === "boolean" ? { EmailLogin: email } : {}),
|
|
8128
|
+
...(typeof anonymous === "boolean" ? { AnonymousLogin: anonymous } : {}),
|
|
8129
|
+
...(typeof phone === "boolean" ? { PhoneNumberLogin: phone } : {}),
|
|
8130
|
+
};
|
|
8131
|
+
}
|
|
8132
|
+
function buildLoginMethods(value) {
|
|
8133
|
+
const state = extractLoginStrategyState(value);
|
|
8134
|
+
return {
|
|
8135
|
+
usernamePassword: Boolean(state.UserNameLogin),
|
|
8136
|
+
email: Boolean(state.EmailLogin),
|
|
8137
|
+
anonymous: Boolean(state.AnonymousLogin),
|
|
8138
|
+
phone: Boolean(state.PhoneNumberLogin),
|
|
8139
|
+
};
|
|
8140
|
+
}
|
|
8141
|
+
function buildWebSdkHint(loginMethods) {
|
|
8142
|
+
if (!loginMethods.usernamePassword) {
|
|
8143
|
+
return {
|
|
8144
|
+
blocked: true,
|
|
8145
|
+
reason: "plain username-style identifiers require usernamePassword auth",
|
|
8146
|
+
nextStep: 'manageAppAuth({ action: "patchLoginStrategy", patch: { usernamePassword: true } })',
|
|
8147
|
+
accountInputType: "text",
|
|
8148
|
+
avoidEmailHelpers: true,
|
|
8149
|
+
};
|
|
8150
|
+
}
|
|
8151
|
+
return {
|
|
8152
|
+
blocked: false,
|
|
8153
|
+
register: "auth.signUp({ username, password })",
|
|
8154
|
+
login: "auth.signInWithPassword({ username, password })",
|
|
8155
|
+
accountInputType: "text",
|
|
8156
|
+
avoidEmailHelpers: true,
|
|
8157
|
+
};
|
|
8158
|
+
}
|
|
8159
|
+
function buildLoginConfigNextStep(loginMethods) {
|
|
8160
|
+
if (loginMethods.usernamePassword) {
|
|
8161
|
+
return undefined;
|
|
8162
|
+
}
|
|
8163
|
+
return {
|
|
8164
|
+
tool: "manageAppAuth",
|
|
8165
|
+
action: "patchLoginStrategy",
|
|
8166
|
+
patch: {
|
|
8167
|
+
usernamePassword: true,
|
|
8168
|
+
},
|
|
8169
|
+
};
|
|
8170
|
+
}
|
|
8171
|
+
function extractProviders(value) {
|
|
8172
|
+
const payload = normalizePlainObject(value, "providersResult");
|
|
8173
|
+
const providers = payload?.Providers ?? payload?.ProviderList ?? payload?.Data ?? [];
|
|
8174
|
+
return Array.isArray(providers) ? providers : [];
|
|
8175
|
+
}
|
|
8176
|
+
function extractStaticStores(value) {
|
|
8177
|
+
const payload = normalizePlainObject(value, "staticStoreResult");
|
|
8178
|
+
const stores = payload?.Data ?? [];
|
|
8179
|
+
return Array.isArray(stores) ? stores : [];
|
|
8180
|
+
}
|
|
8181
|
+
function getNonEmptyString(value) {
|
|
8182
|
+
if (typeof value !== "string") {
|
|
8183
|
+
return undefined;
|
|
8184
|
+
}
|
|
8185
|
+
const trimmed = value.trim();
|
|
8186
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
8187
|
+
}
|
|
8188
|
+
function resolveStaticStoreDomain(store) {
|
|
8189
|
+
return getNonEmptyString(store?.CdnDomain) ?? getNonEmptyString(store?.StaticDomain) ?? null;
|
|
8190
|
+
}
|
|
8191
|
+
function extractApiKeyList(value) {
|
|
8192
|
+
const payload = normalizePlainObject(value, "apiKeyResult");
|
|
8193
|
+
const items = payload?.ApiKeyList ?? payload?.Data ?? [];
|
|
8194
|
+
return Array.isArray(items) ? items : [];
|
|
8195
|
+
}
|
|
8196
|
+
function findPublishableKey(value) {
|
|
8197
|
+
const apiKeys = extractApiKeyList(value);
|
|
8198
|
+
return (apiKeys.find((item) => item.Name === "publish_key" ||
|
|
8199
|
+
item.KeyName === "publish_key" ||
|
|
8200
|
+
item.KeyType === "publish_key") ?? null);
|
|
8201
|
+
}
|
|
8202
|
+
function buildPublishableKeyResponse(envId, record, options) {
|
|
8203
|
+
return {
|
|
8204
|
+
success: true,
|
|
8205
|
+
envId,
|
|
8206
|
+
sdkStyle: "supabase-like",
|
|
8207
|
+
sdkHints: SUPABASE_LIKE_SDK_HINTS,
|
|
8208
|
+
publishableKey: typeof record?.ApiKey === "string" ? record.ApiKey : null,
|
|
8209
|
+
keyId: typeof record?.KeyId === "string" ? record.KeyId : null,
|
|
8210
|
+
keyName: typeof record?.Name === "string"
|
|
8211
|
+
? record.Name
|
|
8212
|
+
: typeof record?.KeyName === "string"
|
|
8213
|
+
? record.KeyName
|
|
8214
|
+
: record
|
|
8215
|
+
? "publish_key"
|
|
8216
|
+
: null,
|
|
8217
|
+
expireAt: typeof record?.ExpireAt === "string" ? record.ExpireAt : null,
|
|
8218
|
+
createdAt: typeof record?.CreateAt === "string" ? record.CreateAt : null,
|
|
8219
|
+
...(typeof options?.created === "boolean" ? { created: options.created } : {}),
|
|
8220
|
+
};
|
|
8221
|
+
}
|
|
8222
|
+
function buildSupabaseLikeAuthResponse(payload) {
|
|
8223
|
+
return {
|
|
8224
|
+
...payload,
|
|
8225
|
+
sdkStyle: "supabase-like",
|
|
8226
|
+
sdkHints: SUPABASE_LIKE_SDK_HINTS,
|
|
8227
|
+
};
|
|
8228
|
+
}
|
|
8229
|
+
function buildClientConfigResponse(envId, clientId, clientConfig) {
|
|
8230
|
+
return {
|
|
8231
|
+
success: true,
|
|
8232
|
+
envId,
|
|
8233
|
+
clientId,
|
|
8234
|
+
clientConfig,
|
|
7585
8235
|
};
|
|
7586
8236
|
}
|
|
8237
|
+
async function getActiveEnvId(cloudBaseOptions) {
|
|
8238
|
+
try {
|
|
8239
|
+
return await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
8240
|
+
}
|
|
8241
|
+
catch (error) {
|
|
8242
|
+
const payload = (0, tool_result_js_1.isToolPayloadError)(error)
|
|
8243
|
+
? error.payload
|
|
8244
|
+
: normalizePlainObject(error?.payload, "error.payload");
|
|
8245
|
+
if (payload?.code === "ENV_REQUIRED") {
|
|
8246
|
+
const nextError = new Error("no active environment selected");
|
|
8247
|
+
nextError.code = "ENV_REQUIRED";
|
|
8248
|
+
throw nextError;
|
|
8249
|
+
}
|
|
8250
|
+
if (payload?.code === "AUTH_REQUIRED") {
|
|
8251
|
+
const nextError = new Error("authentication required");
|
|
8252
|
+
nextError.code = "AUTH_REQUIRED";
|
|
8253
|
+
throw nextError;
|
|
8254
|
+
}
|
|
8255
|
+
throw error;
|
|
8256
|
+
}
|
|
8257
|
+
}
|
|
8258
|
+
function isResourceInUseError(error) {
|
|
8259
|
+
if (!error || typeof error !== "object") {
|
|
8260
|
+
return false;
|
|
8261
|
+
}
|
|
8262
|
+
const candidate = error;
|
|
8263
|
+
const code = typeof candidate.code === "string" ? candidate.code : "";
|
|
8264
|
+
const name = typeof candidate.name === "string" ? candidate.name : "";
|
|
8265
|
+
const message = typeof candidate.message === "string" ? candidate.message : "";
|
|
8266
|
+
const haystack = `${code} ${name} ${message}`;
|
|
8267
|
+
return /ResourceInUse/i.test(haystack);
|
|
8268
|
+
}
|
|
7587
8269
|
function registerAppAuthTools(server) {
|
|
7588
8270
|
const cloudBaseOptions = server.cloudBaseOptions;
|
|
7589
8271
|
const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
|
|
@@ -7592,117 +8274,161 @@ function registerAppAuthTools(server) {
|
|
|
7592
8274
|
return (0, json_content_js_1.jsonContent)(await handler());
|
|
7593
8275
|
}
|
|
7594
8276
|
catch (error) {
|
|
8277
|
+
if (error instanceof Error && (error.message === "no active environment selected" || error.message === "authentication required")) {
|
|
8278
|
+
const code = error.code;
|
|
8279
|
+
if (code) {
|
|
8280
|
+
return (0, json_content_js_1.jsonContent)(buildShortErrorWithCode(error.message, code));
|
|
8281
|
+
}
|
|
8282
|
+
return (0, json_content_js_1.jsonContent)(buildShortError(error.message));
|
|
8283
|
+
}
|
|
7595
8284
|
return (0, json_content_js_1.jsonContent)(buildErrorEnvelope(error));
|
|
7596
8285
|
}
|
|
7597
8286
|
};
|
|
7598
|
-
const
|
|
7599
|
-
const
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
});
|
|
7605
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7606
|
-
return result;
|
|
8287
|
+
const describePublishableKey = async (cloudbase, envId) => {
|
|
8288
|
+
const result = await cloudbase.env.describeApiKeyList({ KeyType: "publish_key", PageNumber: 1, PageSize: 10 });
|
|
8289
|
+
return {
|
|
8290
|
+
result,
|
|
8291
|
+
record: findPublishableKey(result),
|
|
8292
|
+
};
|
|
7607
8293
|
};
|
|
7608
8294
|
server.registerTool?.("queryAppAuth", {
|
|
7609
8295
|
title: "查询应用认证配置",
|
|
7610
|
-
description: "应用侧认证配置只读入口。用于查询登录方式、provider、publishable key、client
|
|
8296
|
+
description: "应用侧认证配置只读入口。用于查询登录方式、provider、publishable key、API key、client 配置和静态域名等认证准备状态。若业务要接受普通用户名样式标识符,先查询 action=getLoginConfig;若 usernamePassword=false,下一步应立即调用 manageAppAuth(action=patchLoginStrategy, patch={ usernamePassword: true }),不要直接写 email 登录 API。",
|
|
7611
8297
|
inputSchema: {
|
|
7612
8298
|
action: zod_1.z.enum(QUERY_APP_AUTH_ACTIONS),
|
|
7613
8299
|
providerId: zod_1.z.string().optional().describe("provider 标识,如 email、google"),
|
|
8300
|
+
clientId: zod_1.z
|
|
8301
|
+
.string()
|
|
8302
|
+
.optional()
|
|
8303
|
+
.describe("OAuth client_id / DescribeClient 的 Id;省略时默认使用当前环境 ID(默认客户端)"),
|
|
8304
|
+
keyType: zod_1.z
|
|
8305
|
+
.enum(APP_AUTH_KEY_TYPES)
|
|
8306
|
+
.optional()
|
|
8307
|
+
.describe("API key 类型过滤,可选 publish_key 或 api_key"),
|
|
8308
|
+
pageNumber: zod_1.z.number().int().positive().optional().describe("API key 列表页码,从 1 开始"),
|
|
8309
|
+
pageSize: zod_1.z.number().int().positive().optional().describe("API key 列表每页条数"),
|
|
7614
8310
|
},
|
|
7615
8311
|
annotations: {
|
|
7616
8312
|
readOnlyHint: true,
|
|
7617
8313
|
openWorldHint: true,
|
|
7618
8314
|
category: "auth",
|
|
7619
8315
|
},
|
|
7620
|
-
}, async ({ action, providerId, }) => withEnvelope(async () => {
|
|
7621
|
-
const envId = await (
|
|
8316
|
+
}, async ({ action, providerId, clientId, keyType, pageNumber, pageSize, }) => withEnvelope(async () => {
|
|
8317
|
+
const envId = await getActiveEnvId(cloudBaseOptions);
|
|
7622
8318
|
const cloudbase = await getManager();
|
|
7623
8319
|
switch (action) {
|
|
7624
8320
|
case "getLoginConfig": {
|
|
7625
|
-
|
|
7626
|
-
const result = await cloudbase.env.getLoginConfigListV2();
|
|
8321
|
+
const result = await cloudbase.env.getLoginConfig();
|
|
7627
8322
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7628
|
-
|
|
7629
|
-
|
|
8323
|
+
const loginMethods = buildLoginMethods(result);
|
|
8324
|
+
const nextStep = buildLoginConfigNextStep(loginMethods);
|
|
8325
|
+
const webSdkHint = buildWebSdkHint(loginMethods);
|
|
8326
|
+
return buildSupabaseLikeAuthResponse({
|
|
8327
|
+
success: true,
|
|
7630
8328
|
envId,
|
|
7631
|
-
|
|
7632
|
-
|
|
8329
|
+
loginMethods,
|
|
8330
|
+
...(nextStep ? { next_step: nextStep } : {}),
|
|
8331
|
+
...(webSdkHint ? { webSdkHint } : {}),
|
|
8332
|
+
});
|
|
7633
8333
|
}
|
|
7634
8334
|
case "listProviders": {
|
|
7635
|
-
const result = await
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
return buildEnvelope({
|
|
7639
|
-
action,
|
|
8335
|
+
const result = await cloudbase.env.getProviders();
|
|
8336
|
+
return {
|
|
8337
|
+
success: true,
|
|
7640
8338
|
envId,
|
|
7641
|
-
providers: result
|
|
7642
|
-
|
|
7643
|
-
}, "应用 provider 列表查询成功");
|
|
8339
|
+
providers: extractProviders(result),
|
|
8340
|
+
};
|
|
7644
8341
|
}
|
|
7645
8342
|
case "getProvider": {
|
|
7646
8343
|
if (!providerId) {
|
|
7647
8344
|
throw new Error("action=getProvider 时必须提供 providerId");
|
|
7648
8345
|
}
|
|
7649
|
-
const result = await
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
result.ProviderList ??
|
|
7654
|
-
result.Data ??
|
|
7655
|
-
[]);
|
|
7656
|
-
const provider = providers.find((item) => item.Id === providerId || item.id === providerId) ?? null;
|
|
7657
|
-
return buildEnvelope({
|
|
7658
|
-
action,
|
|
8346
|
+
const result = await cloudbase.env.getProviders();
|
|
8347
|
+
const provider = extractProviders(result).find((item) => item.Id === providerId || item.id === providerId) ?? null;
|
|
8348
|
+
return {
|
|
8349
|
+
success: true,
|
|
7659
8350
|
envId,
|
|
7660
8351
|
providerId,
|
|
7661
8352
|
provider,
|
|
7662
|
-
|
|
7663
|
-
}, `应用 provider ${providerId} 查询成功`);
|
|
8353
|
+
};
|
|
7664
8354
|
}
|
|
7665
8355
|
case "getClientConfig": {
|
|
7666
|
-
const
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
return buildEnvelope({
|
|
7670
|
-
action,
|
|
7671
|
-
envId,
|
|
7672
|
-
clientConfig: result,
|
|
7673
|
-
}, "应用 client 配置查询成功");
|
|
8356
|
+
const clientRecordId = clientId ?? envId;
|
|
8357
|
+
const result = await cloudbase.env.describeClient(clientRecordId);
|
|
8358
|
+
return buildClientConfigResponse(envId, clientRecordId, result);
|
|
7674
8359
|
}
|
|
7675
|
-
case "
|
|
7676
|
-
const
|
|
7677
|
-
|
|
8360
|
+
case "getPublishableKey": {
|
|
8361
|
+
const { record } = await describePublishableKey(cloudbase, envId);
|
|
8362
|
+
return buildPublishableKeyResponse(envId, record);
|
|
8363
|
+
}
|
|
8364
|
+
case "getStaticDomain": {
|
|
8365
|
+
const service = cloudbase.commonService("tcb", "2018-06-08");
|
|
8366
|
+
const result = await service.call({
|
|
8367
|
+
Action: "DescribeStaticStore",
|
|
8368
|
+
Param: { EnvId: envId },
|
|
7678
8369
|
});
|
|
7679
|
-
|
|
7680
|
-
|
|
8370
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
8371
|
+
const stores = extractStaticStores(result);
|
|
8372
|
+
const primaryDomain = resolveStaticStoreDomain(stores[0]);
|
|
8373
|
+
return {
|
|
8374
|
+
success: true,
|
|
7681
8375
|
envId,
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
8376
|
+
cdnDomain: primaryDomain,
|
|
8377
|
+
staticDomain: primaryDomain,
|
|
8378
|
+
staticStores: stores,
|
|
8379
|
+
};
|
|
7685
8380
|
}
|
|
7686
|
-
case "
|
|
7687
|
-
const
|
|
7688
|
-
|
|
8381
|
+
case "listApiKeys": {
|
|
8382
|
+
const currentPageNumber = pageNumber ?? 1;
|
|
8383
|
+
const currentPageSize = pageSize ?? 20;
|
|
8384
|
+
const result = await cloudbase.env.describeApiKeyList({
|
|
8385
|
+
...(keyType ? { KeyType: keyType } : {}),
|
|
8386
|
+
PageNumber: currentPageNumber,
|
|
8387
|
+
PageSize: currentPageSize,
|
|
7689
8388
|
});
|
|
7690
|
-
|
|
7691
|
-
|
|
8389
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
8390
|
+
const apiKeys = extractApiKeyList(result);
|
|
8391
|
+
return {
|
|
8392
|
+
success: true,
|
|
7692
8393
|
envId,
|
|
7693
|
-
|
|
7694
|
-
|
|
8394
|
+
...(keyType ? { keyType } : {}),
|
|
8395
|
+
apiKeys,
|
|
8396
|
+
total: typeof result.Total === "number"
|
|
8397
|
+
? result.Total
|
|
8398
|
+
: apiKeys.length,
|
|
8399
|
+
pageNumber: currentPageNumber,
|
|
8400
|
+
pageSize: currentPageSize,
|
|
8401
|
+
};
|
|
7695
8402
|
}
|
|
7696
8403
|
}
|
|
7697
8404
|
}));
|
|
7698
8405
|
server.registerTool?.("manageAppAuth", {
|
|
7699
8406
|
title: "管理应用认证配置",
|
|
7700
|
-
description: "应用侧认证配置写入口。用于修改登录方式、provider、client
|
|
8407
|
+
description: "应用侧认证配置写入口。用于修改登录方式、provider、client 配置,确保 publishable key,以及创建或删除 API key、自定义登录密钥。若前端要接受普通用户名样式标识符,应先执行 action=patchLoginStrategy 并传入 patch={ usernamePassword: true },再实现对应前端登录逻辑。",
|
|
7701
8408
|
inputSchema: {
|
|
7702
8409
|
action: zod_1.z.enum(MANAGE_APP_AUTH_ACTIONS),
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
8410
|
+
patch: zod_1.z
|
|
8411
|
+
.record(zod_1.z.any())
|
|
8412
|
+
.optional()
|
|
8413
|
+
.describe("patchLoginStrategy 使用的简化登录策略 patch,如 { usernamePassword: true }"),
|
|
8414
|
+
providerId: zod_1.z.string().optional().describe("provider 标识,如 email、google;addProvider 时也可作为自定义 provider Id"),
|
|
8415
|
+
providerType: zod_1.z.string().optional().describe("addProvider 时的 provider 协议类型,如 OAUTH、OIDC、EMAIL"),
|
|
8416
|
+
displayName: zod_1.z
|
|
8417
|
+
.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any())])
|
|
8418
|
+
.optional()
|
|
8419
|
+
.describe("addProvider 时的展示名称,可传字符串或多语言对象"),
|
|
8420
|
+
clientId: zod_1.z
|
|
8421
|
+
.string()
|
|
8422
|
+
.optional()
|
|
8423
|
+
.describe("updateClientConfig 时的客户端 Id;省略时默认使用当前环境 ID"),
|
|
8424
|
+
config: zod_1.z.record(zod_1.z.any()).optional().describe("provider / client 的配置对象"),
|
|
8425
|
+
keyType: zod_1.z
|
|
8426
|
+
.enum(APP_AUTH_KEY_TYPES)
|
|
8427
|
+
.optional()
|
|
8428
|
+
.describe("createApiKey 时的 API key 类型,默认 publish_key"),
|
|
8429
|
+
keyName: zod_1.z.string().optional().describe("createApiKey 时的 API key 名称"),
|
|
8430
|
+
expireIn: zod_1.z.number().int().min(0).optional().describe("createApiKey 时的有效期,单位秒;0 表示不过期"),
|
|
8431
|
+
keyId: zod_1.z.string().optional().describe("deleteApiKey 时的 API key 唯一标识"),
|
|
7706
8432
|
},
|
|
7707
8433
|
annotations: {
|
|
7708
8434
|
readOnlyHint: false,
|
|
@@ -7711,86 +8437,165 @@ function registerAppAuthTools(server) {
|
|
|
7711
8437
|
openWorldHint: true,
|
|
7712
8438
|
category: "auth",
|
|
7713
8439
|
},
|
|
7714
|
-
}, async ({ action,
|
|
7715
|
-
const envId = await (
|
|
8440
|
+
}, async ({ action, patch, providerId, providerType, displayName, clientId, config, keyType, keyName, expireIn, keyId, }) => withEnvelope(async () => {
|
|
8441
|
+
const envId = await getActiveEnvId(cloudBaseOptions);
|
|
7716
8442
|
const cloudbase = await getManager();
|
|
7717
8443
|
switch (action) {
|
|
7718
|
-
case "
|
|
7719
|
-
const
|
|
8444
|
+
case "patchLoginStrategy": {
|
|
8445
|
+
const input = normalizePlainObject(patch, "patch");
|
|
8446
|
+
const normalized = input ? normalizeLoginConfigPatch(input) : undefined;
|
|
7720
8447
|
if (!normalized) {
|
|
7721
|
-
throw new Error("action=
|
|
8448
|
+
throw new Error("action=patchLoginStrategy 时必须提供 patch");
|
|
7722
8449
|
}
|
|
7723
|
-
const current = await cloudbase.env.
|
|
8450
|
+
const current = await cloudbase.env.getLoginConfig();
|
|
7724
8451
|
const merged = {
|
|
7725
|
-
EnvId: envId,
|
|
7726
8452
|
...extractLoginStrategyState(current),
|
|
7727
|
-
...
|
|
8453
|
+
...normalized,
|
|
7728
8454
|
};
|
|
7729
|
-
|
|
7730
|
-
const result = await cloudbase.env.updateLoginConfigV2(merged);
|
|
8455
|
+
const result = await cloudbase.env.modifyLoginConfig(merged);
|
|
7731
8456
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7732
|
-
|
|
7733
|
-
|
|
8457
|
+
const confirmed = await cloudbase.env.getLoginConfig();
|
|
8458
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, confirmed);
|
|
8459
|
+
const loginMethods = buildLoginMethods(confirmed);
|
|
8460
|
+
const nextStep = buildLoginConfigNextStep(loginMethods);
|
|
8461
|
+
const webSdkHint = buildWebSdkHint(loginMethods);
|
|
8462
|
+
return buildSupabaseLikeAuthResponse({
|
|
8463
|
+
success: true,
|
|
7734
8464
|
envId,
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
8465
|
+
loginMethods,
|
|
8466
|
+
...(nextStep ? { next_step: nextStep } : {}),
|
|
8467
|
+
...(webSdkHint ? { webSdkHint } : {}),
|
|
8468
|
+
});
|
|
8469
|
+
}
|
|
8470
|
+
case "addProvider": {
|
|
8471
|
+
const normalized = omitKeys(normalizePlainObject(config, "config"), ["EnvId"]);
|
|
8472
|
+
if (!providerType) {
|
|
8473
|
+
throw new Error("action=addProvider 时必须提供 providerType");
|
|
8474
|
+
}
|
|
8475
|
+
const localizedDisplayName = normalizeLocalizedMessage(displayName, "displayName", providerId ?? providerType);
|
|
8476
|
+
const result = await cloudbase.env.addProvider({
|
|
8477
|
+
...(providerId ? { Id: providerId } : {}),
|
|
8478
|
+
Name: localizedDisplayName,
|
|
8479
|
+
ProviderType: providerType,
|
|
8480
|
+
...(normalized ? { Config: normalized } : {}),
|
|
8481
|
+
});
|
|
8482
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
8483
|
+
const addProviderResult = normalizePlainObject(result, "addProviderResult") ?? {};
|
|
8484
|
+
return {
|
|
8485
|
+
success: true,
|
|
8486
|
+
envId,
|
|
8487
|
+
providerId: providerId ??
|
|
8488
|
+
(typeof addProviderResult.Id === "string" ? addProviderResult.Id : null),
|
|
8489
|
+
providerType,
|
|
8490
|
+
};
|
|
7738
8491
|
}
|
|
7739
8492
|
case "updateProvider": {
|
|
7740
|
-
const normalized = normalizePlainObject(config, "config");
|
|
8493
|
+
const normalized = omitKeys(normalizePlainObject(config, "config"), ["EnvId", "Id"]);
|
|
7741
8494
|
if (!providerId) {
|
|
7742
8495
|
throw new Error("action=updateProvider 时必须提供 providerId");
|
|
7743
8496
|
}
|
|
7744
8497
|
if (!normalized) {
|
|
7745
8498
|
throw new Error("action=updateProvider 时必须提供 config");
|
|
7746
8499
|
}
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
...normalized,
|
|
7751
|
-
});
|
|
7752
|
-
return buildEnvelope({
|
|
7753
|
-
action,
|
|
8500
|
+
await cloudbase.env.modifyProvider({ Id: providerId, ...normalized });
|
|
8501
|
+
return {
|
|
8502
|
+
success: true,
|
|
7754
8503
|
envId,
|
|
7755
8504
|
providerId,
|
|
7756
|
-
|
|
7757
|
-
|
|
8505
|
+
};
|
|
8506
|
+
}
|
|
8507
|
+
case "deleteProvider": {
|
|
8508
|
+
if (!providerId) {
|
|
8509
|
+
throw new Error("action=deleteProvider 时必须提供 providerId");
|
|
8510
|
+
}
|
|
8511
|
+
const result = await cloudbase.env.deleteProvider(providerId);
|
|
8512
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
8513
|
+
return {
|
|
8514
|
+
success: true,
|
|
8515
|
+
envId,
|
|
8516
|
+
providerId,
|
|
8517
|
+
deleted: true,
|
|
8518
|
+
};
|
|
7758
8519
|
}
|
|
7759
8520
|
case "updateClientConfig": {
|
|
7760
|
-
const normalized = normalizePlainObject(config, "config");
|
|
8521
|
+
const normalized = omitKeys(normalizePlainObject(config, "config"), ["EnvId", "Id"]);
|
|
7761
8522
|
if (!normalized) {
|
|
7762
8523
|
throw new Error("action=updateClientConfig 时必须提供 config");
|
|
7763
8524
|
}
|
|
7764
|
-
const
|
|
7765
|
-
|
|
7766
|
-
|
|
8525
|
+
const clientRecordId = clientId ?? envId;
|
|
8526
|
+
await cloudbase.env.modifyClient({ Id: clientRecordId, ...normalized });
|
|
8527
|
+
const confirmed = await cloudbase.env.describeClient(clientRecordId);
|
|
8528
|
+
return buildClientConfigResponse(envId, clientRecordId, confirmed);
|
|
8529
|
+
}
|
|
8530
|
+
case "ensurePublishableKey": {
|
|
8531
|
+
const existing = await describePublishableKey(cloudbase, envId);
|
|
8532
|
+
if (existing.record) {
|
|
8533
|
+
return buildPublishableKeyResponse(envId, existing.record, { created: false });
|
|
8534
|
+
}
|
|
8535
|
+
let created;
|
|
8536
|
+
try {
|
|
8537
|
+
created = await cloudbase.env.createApiKey({ KeyType: "publish_key" });
|
|
8538
|
+
}
|
|
8539
|
+
catch (error) {
|
|
8540
|
+
if (!isResourceInUseError(error)) {
|
|
8541
|
+
throw error;
|
|
8542
|
+
}
|
|
8543
|
+
const reread = await describePublishableKey(cloudbase, envId);
|
|
8544
|
+
if (!reread.record) {
|
|
8545
|
+
throw error;
|
|
8546
|
+
}
|
|
8547
|
+
return buildPublishableKeyResponse(envId, reread.record, { created: false });
|
|
8548
|
+
}
|
|
8549
|
+
return buildPublishableKeyResponse(envId, normalizePlainObject(created, "createdApiKey") ?? null, {
|
|
8550
|
+
created: true,
|
|
7767
8551
|
});
|
|
7768
|
-
return buildEnvelope({
|
|
7769
|
-
action,
|
|
7770
|
-
envId,
|
|
7771
|
-
raw: result,
|
|
7772
|
-
}, "应用 client 配置更新成功");
|
|
7773
8552
|
}
|
|
7774
|
-
case "
|
|
7775
|
-
const
|
|
7776
|
-
const result = await
|
|
7777
|
-
|
|
7778
|
-
...(
|
|
8553
|
+
case "createApiKey": {
|
|
8554
|
+
const effectiveKeyType = keyType ?? "publish_key";
|
|
8555
|
+
const result = await cloudbase.env.createApiKey({
|
|
8556
|
+
KeyType: effectiveKeyType,
|
|
8557
|
+
...(keyName ? { KeyName: keyName } : {}),
|
|
8558
|
+
...(typeof expireIn === "number" ? { ExpireIn: expireIn } : {}),
|
|
7779
8559
|
});
|
|
7780
|
-
|
|
7781
|
-
|
|
8560
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
8561
|
+
const createApiKeyResult = normalizePlainObject(result, "createApiKeyResult") ?? {};
|
|
8562
|
+
return {
|
|
8563
|
+
success: true,
|
|
7782
8564
|
envId,
|
|
7783
|
-
|
|
7784
|
-
|
|
8565
|
+
keyType: effectiveKeyType,
|
|
8566
|
+
keyId: typeof result.KeyId === "string" ? result.KeyId : null,
|
|
8567
|
+
keyName: typeof result.Name === "string"
|
|
8568
|
+
? result.Name
|
|
8569
|
+
: typeof createApiKeyResult.KeyName === "string"
|
|
8570
|
+
? createApiKeyResult.KeyName
|
|
8571
|
+
: keyName ?? null,
|
|
8572
|
+
apiKey: typeof result.ApiKey === "string" ? result.ApiKey : null,
|
|
8573
|
+
expireAt: typeof result.ExpireAt === "string" ? result.ExpireAt : null,
|
|
8574
|
+
createdAt: typeof result.CreateAt === "string" ? result.CreateAt : null,
|
|
8575
|
+
};
|
|
8576
|
+
}
|
|
8577
|
+
case "deleteApiKey": {
|
|
8578
|
+
if (!keyId) {
|
|
8579
|
+
throw new Error("action=deleteApiKey 时必须提供 keyId");
|
|
8580
|
+
}
|
|
8581
|
+
const result = await cloudbase.env.deleteApiKey(keyId);
|
|
8582
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
8583
|
+
return {
|
|
8584
|
+
success: true,
|
|
8585
|
+
envId,
|
|
8586
|
+
keyId,
|
|
8587
|
+
deleted: true,
|
|
8588
|
+
};
|
|
7785
8589
|
}
|
|
7786
8590
|
case "createCustomLoginKeys": {
|
|
7787
8591
|
const result = await cloudbase.env.createCustomLoginKeys();
|
|
7788
8592
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
7789
|
-
return
|
|
7790
|
-
|
|
8593
|
+
return {
|
|
8594
|
+
success: true,
|
|
7791
8595
|
envId,
|
|
7792
|
-
|
|
7793
|
-
|
|
8596
|
+
privateKey: result.PrivateKey,
|
|
8597
|
+
keyId: result.KeyID,
|
|
8598
|
+
};
|
|
7794
8599
|
}
|
|
7795
8600
|
}
|
|
7796
8601
|
}));
|
|
@@ -7866,7 +8671,7 @@ class TelemetryReporter {
|
|
|
7866
8671
|
const nodeVersion = process.version; // Node.js版本
|
|
7867
8672
|
const arch = os_1.default.arch(); // 系统架构
|
|
7868
8673
|
// 从构建时注入的版本号获取MCP版本信息
|
|
7869
|
-
const mcpVersion = process.env.npm_package_version || "2.
|
|
8674
|
+
const mcpVersion = process.env.npm_package_version || "2.17.0-beta.2" || 0;
|
|
7870
8675
|
return {
|
|
7871
8676
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
7872
8677
|
deviceId: this.deviceId,
|
|
@@ -8065,7 +8870,7 @@ const reportToolCall = async (params) => {
|
|
|
8065
8870
|
toolName: params.toolName,
|
|
8066
8871
|
success: params.success ? 'true' : 'false',
|
|
8067
8872
|
requestId: params.requestId,
|
|
8068
|
-
duration: params.duration,
|
|
8873
|
+
duration: params.duration !== undefined ? String(params.duration) : undefined,
|
|
8069
8874
|
error: params.error ? params.error.substring(0, 200) : undefined, // 限制错误信息长度
|
|
8070
8875
|
envId: envId || 'unknown',
|
|
8071
8876
|
nodeVersion,
|
|
@@ -8120,8 +8925,8 @@ const reportToolkitLifecycle = async (params) => {
|
|
|
8120
8925
|
// 报告 Toolkit 生命周期事件
|
|
8121
8926
|
const eventData = {
|
|
8122
8927
|
event: params.event,
|
|
8123
|
-
duration: params.duration,
|
|
8124
|
-
exitCode: params.exitCode,
|
|
8928
|
+
duration: params.duration !== undefined ? String(params.duration) : undefined,
|
|
8929
|
+
exitCode: params.exitCode !== undefined ? String(params.exitCode) : undefined,
|
|
8125
8930
|
error: params.error ? params.error.substring(0, 200) : undefined, // 限制错误信息长度
|
|
8126
8931
|
envId: envId || 'unknown',
|
|
8127
8932
|
nodeVersion,
|
|
@@ -9153,11 +9958,11 @@ function registerFunctionTools(server) {
|
|
|
9153
9958
|
}, async (input) => withEnvelope(() => handleQueryFunctions(input)));
|
|
9154
9959
|
server.registerTool?.("manageFunctions", {
|
|
9155
9960
|
title: "管理云函数域资源",
|
|
9156
|
-
description: "函数域统一写入口。通过 action
|
|
9961
|
+
description: "函数域统一写入口。通过 action 管理函数创建、代码更新、配置更新、调用函数、触发器和层绑定。危险操作需要显式 confirm=true。",
|
|
9157
9962
|
inputSchema: {
|
|
9158
9963
|
action: zod_1.z
|
|
9159
9964
|
.enum(exports.MANAGE_FUNCTION_ACTIONS)
|
|
9160
|
-
.describe("写操作类型,例如 createFunction、
|
|
9965
|
+
.describe("写操作类型,例如 createFunction、invokeFunction、attachLayer"),
|
|
9161
9966
|
func: CREATE_FUNCTION_SCHEMA.optional().describe("createFunction 操作的函数配置"),
|
|
9162
9967
|
functionRootPath: zod_1.z.string().optional().describe("函数根目录(父目录绝对路径)"),
|
|
9163
9968
|
force: zod_1.z.boolean().optional().describe("createFunction 时是否覆盖"),
|
|
@@ -9234,6 +10039,12 @@ const MANAGE_PERMISSION_ACTIONS = [
|
|
|
9234
10039
|
"updateUser",
|
|
9235
10040
|
"deleteUsers",
|
|
9236
10041
|
];
|
|
10042
|
+
function buildWriteVerificationHint(resourceId) {
|
|
10043
|
+
return `对于 ${resourceId} 这类有后端权限控制的集合,前端调用 .doc(id).update() / .doc(id).remove() 后,不能只看是否没有抛异常。请显式检查返回结果中的 updated / deleted 是否大于 0;如果 result.code、result.message 存在,或 updated / deleted 为 0,要把它当作真实失败并向上抛错。`;
|
|
10044
|
+
}
|
|
10045
|
+
function buildPermissionPropagationHint(resourceId) {
|
|
10046
|
+
return `刚更新完 ${resourceId} 的安全规则时,后端权限可能需要一小段传播时间。若紧接着的真实写操作仍返回 DATABASE_PERMISSION_DENIED,请先等待一小段时间,再用同一登录态重试同一条 .doc(id).update() / .doc(id).remove();不要立刻连续重写规则,也不要在传播窗口里把旧拒绝直接当成规则表达式仍然错误。`;
|
|
10047
|
+
}
|
|
9237
10048
|
function buildEnvelope(data, message) {
|
|
9238
10049
|
return {
|
|
9239
10050
|
success: true,
|
|
@@ -9266,6 +10077,142 @@ function normalizeRecordArray(value, label) {
|
|
|
9266
10077
|
}
|
|
9267
10078
|
return value;
|
|
9268
10079
|
}
|
|
10080
|
+
function extractRiskyDocFieldOperations(securityRule) {
|
|
10081
|
+
if (!securityRule) {
|
|
10082
|
+
return [];
|
|
10083
|
+
}
|
|
10084
|
+
const operations = [];
|
|
10085
|
+
const operationPatterns = [
|
|
10086
|
+
["update", /"update"\s*:\s*"([^"]*)"/],
|
|
10087
|
+
["delete", /"delete"\s*:\s*"([^"]*)"/],
|
|
10088
|
+
];
|
|
10089
|
+
for (const [operation, pattern] of operationPatterns) {
|
|
10090
|
+
const match = securityRule.match(pattern);
|
|
10091
|
+
const expression = match?.[1];
|
|
10092
|
+
if (!expression) {
|
|
10093
|
+
continue;
|
|
10094
|
+
}
|
|
10095
|
+
const referencesNonIdDocField = /doc\.(?!_id\b)[A-Za-z_][A-Za-z0-9_]*/.test(expression);
|
|
10096
|
+
const usesGetByDocId = /get\('database\.[^']+'\s*\+\s*doc\._id\)/.test(expression);
|
|
10097
|
+
if (referencesNonIdDocField && !usesGetByDocId) {
|
|
10098
|
+
operations.push(operation);
|
|
10099
|
+
}
|
|
10100
|
+
}
|
|
10101
|
+
return operations;
|
|
10102
|
+
}
|
|
10103
|
+
function buildRecommendedOwnerWriteRule(resourceId) {
|
|
10104
|
+
return JSON.stringify({
|
|
10105
|
+
create: "auth.uid != null",
|
|
10106
|
+
update: "auth.uid != null && (get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid)",
|
|
10107
|
+
delete: "auth.uid != null && (get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid)",
|
|
10108
|
+
});
|
|
10109
|
+
}
|
|
10110
|
+
function buildRoleLookupNote() {
|
|
10111
|
+
return "如果你需要 app-level admin override(例如 CMS 中 admin 可编辑所有文章,而 editor 只能编辑自己的文章),CUSTOM 规则通常是必要的。一个已验证可用的模式是:角色集合文档主键就是 auth.uid,并在文章权限里写 get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid。若现有 schema 已经有 users / profiles / user_roles 其一,请复用已存在且能通过 _id == auth.uid 直接 get() 到的那一份;不要把 where({ uid }) 查询得到的集合误写成 get('database.users.' + auth.uid)。";
|
|
10112
|
+
}
|
|
10113
|
+
function buildRecommendedClientWritePattern(resourceId) {
|
|
10114
|
+
return `对于 CMS 文章这类使用 app-level admin override 的 CUSTOM 规则,前端可继续使用 db.collection('${resourceId}').doc(id).update(...) / remove(...)。关键是安全规则要采用已验证模式:get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid,并且文章文档中要真实写入 authorId。`;
|
|
10115
|
+
}
|
|
10116
|
+
function buildCreateRuleHint(securityRule, resourceId) {
|
|
10117
|
+
if (!securityRule) {
|
|
10118
|
+
return undefined;
|
|
10119
|
+
}
|
|
10120
|
+
const createMatch = securityRule.match(/"create"\s*:\s*"([^"]*)"/);
|
|
10121
|
+
const writeMatch = securityRule.match(/"write"\s*:\s*"([^"]*)"/);
|
|
10122
|
+
const createExpression = createMatch?.[1];
|
|
10123
|
+
const writeExpression = writeMatch?.[1];
|
|
10124
|
+
if (!createExpression && !writeExpression) {
|
|
10125
|
+
return undefined;
|
|
10126
|
+
}
|
|
10127
|
+
const referencesDoc = (createExpression && /doc\.[A-Za-z_]/.test(createExpression)) ||
|
|
10128
|
+
(writeExpression && /doc\.[A-Za-z_]/.test(writeExpression));
|
|
10129
|
+
if (!referencesDoc) {
|
|
10130
|
+
return undefined;
|
|
10131
|
+
}
|
|
10132
|
+
return {
|
|
10133
|
+
type: "createRuleDocWarning",
|
|
10134
|
+
severity: "warning",
|
|
10135
|
+
summary: "create 规则不应引用 doc.*,因为 create 时文档尚未存在。",
|
|
10136
|
+
detail: "CloudBase 的 create 规则验证的是写入数据(request.data),此时文档尚不存在,doc.* 不可用。" +
|
|
10137
|
+
"请将 create 规则改为仅使用 auth.* 检查(如 auth.uid != null && auth.loginType != 'ANONYMOUS')," +
|
|
10138
|
+
"或在写入时将 owner 字段(如 _openid / authorId)写入 request.data,然后在 create 规则中用 request.data._openid == auth.openid 做校验。" +
|
|
10139
|
+
"read / update / delete 规则可以使用 doc.* 引用已有文档字段,且客户端查询条件必须是规则约束的子集(如 _openid: '{openid}')。",
|
|
10140
|
+
recommendedRulePattern: "auth.uid != null && auth.loginType != 'ANONYMOUS'",
|
|
10141
|
+
recommendedPermission: "CUSTOM",
|
|
10142
|
+
recommendedSecurityRule: JSON.stringify({
|
|
10143
|
+
read: "auth.uid != null && auth.loginType != 'ANONYMOUS'",
|
|
10144
|
+
create: "auth.uid != null && auth.loginType != 'ANONYMOUS'",
|
|
10145
|
+
update: "auth.uid != null && auth.loginType != 'ANONYMOUS' && doc._openid == auth.openid",
|
|
10146
|
+
delete: "auth.uid != null && auth.loginType != 'ANONYMOUS' && doc._openid == auth.openid",
|
|
10147
|
+
}),
|
|
10148
|
+
};
|
|
10149
|
+
}
|
|
10150
|
+
function buildDocIdWriteRuleHint(securityRule, resourceId) {
|
|
10151
|
+
const appliesTo = extractRiskyDocFieldOperations(securityRule);
|
|
10152
|
+
if (!appliesTo.length) {
|
|
10153
|
+
return undefined;
|
|
10154
|
+
}
|
|
10155
|
+
return {
|
|
10156
|
+
type: "docIdWriteRuleWarning",
|
|
10157
|
+
severity: "warning",
|
|
10158
|
+
appliesTo,
|
|
10159
|
+
summary: "当前安全规则在 document-id 写入场景下可能被后端直接拒绝。",
|
|
10160
|
+
detail: "这类规则经常在 owner-only 集合里被写错,但对于 CMS 文章这种“admin 可编辑所有文章、editor 只能编辑自己的文章”的场景,已验证可用的做法是保留 doc.authorId,并通过独立角色集合做 admin override:get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid。不要默认改成 where(...),也不要把同集合 owner 判断重写成 get('database.collection.' + doc._id)。",
|
|
10161
|
+
recommendedRulePattern: "doc.authorId == auth.uid",
|
|
10162
|
+
recommendedPermission: "CUSTOM",
|
|
10163
|
+
recommendedSecurityRule: buildRecommendedOwnerWriteRule(resourceId),
|
|
10164
|
+
recommendedClientWritePattern: buildRecommendedClientWritePattern(resourceId),
|
|
10165
|
+
roleLookupNote: buildRoleLookupNote(),
|
|
10166
|
+
};
|
|
10167
|
+
}
|
|
10168
|
+
function buildInvalidGetPathHint(securityRule, resourceId) {
|
|
10169
|
+
if (!securityRule) {
|
|
10170
|
+
return undefined;
|
|
10171
|
+
}
|
|
10172
|
+
const hasFieldEmbeddedInsideGetPath = /get\('database\.[^']+'\s*\+\s*[^)]*\+\s*'\.[A-Za-z_][A-Za-z0-9_]*'\)/.test(securityRule);
|
|
10173
|
+
if (!hasFieldEmbeddedInsideGetPath) {
|
|
10174
|
+
return undefined;
|
|
10175
|
+
}
|
|
10176
|
+
return {
|
|
10177
|
+
type: "invalidGetPathWarning",
|
|
10178
|
+
severity: "warning",
|
|
10179
|
+
summary: "get() 的 path 只应包含 collection 和 documentId,不应把字段名拼进 path 字符串。",
|
|
10180
|
+
detail: "请写成 get('database.collection.' + doc._id).fieldName,而不是 get('database.collection.' + doc._id + '.fieldName')。但在 CMS 文章权限里,不要把 get('database.collection.' + doc._id) 当成默认首选方案;更稳的已验证模式是读取单独的角色集合:get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid。",
|
|
10181
|
+
recommendedRulePattern: "doc.authorId == auth.uid",
|
|
10182
|
+
recommendedPermission: "CUSTOM",
|
|
10183
|
+
recommendedSecurityRule: buildRecommendedOwnerWriteRule(resourceId),
|
|
10184
|
+
recommendedClientWritePattern: buildRecommendedClientWritePattern(resourceId),
|
|
10185
|
+
roleLookupNote: buildRoleLookupNote(),
|
|
10186
|
+
};
|
|
10187
|
+
}
|
|
10188
|
+
function buildTemplateLiteralRuleHint(securityRule, resourceId) {
|
|
10189
|
+
if (!securityRule) {
|
|
10190
|
+
return undefined;
|
|
10191
|
+
}
|
|
10192
|
+
const usesTemplateLiteralPlaceholderInRule = /\$\{(?:auth\.uid|doc\._id|doc\.[A-Za-z_][A-Za-z0-9_]*)\}/.test(securityRule);
|
|
10193
|
+
if (!usesTemplateLiteralPlaceholderInRule) {
|
|
10194
|
+
return undefined;
|
|
10195
|
+
}
|
|
10196
|
+
return {
|
|
10197
|
+
type: "templateLiteralRuleWarning",
|
|
10198
|
+
severity: "warning",
|
|
10199
|
+
summary: "CloudBase security rule 表达式不支持把 ${...} 当作 JS 模板字符串插值。",
|
|
10200
|
+
detail: "在 securityRule 字符串里,请使用表达式拼接,例如 get('database.user_roles.' + auth.uid).role,而不是 get('database.user_roles.${auth.uid}').role。对于 CMS 文章这类需要 app-level admin override 的规则,请优先使用已验证的 user_roles + doc.authorId 模式。",
|
|
10201
|
+
recommendedRulePattern: "doc.authorId == auth.uid",
|
|
10202
|
+
recommendedPermission: "CUSTOM",
|
|
10203
|
+
recommendedSecurityRule: buildRecommendedOwnerWriteRule(resourceId),
|
|
10204
|
+
recommendedClientWritePattern: buildRecommendedClientWritePattern(resourceId),
|
|
10205
|
+
roleLookupNote: buildRoleLookupNote(),
|
|
10206
|
+
};
|
|
10207
|
+
}
|
|
10208
|
+
function buildPermissionHints(securityRule, resourceId) {
|
|
10209
|
+
return [
|
|
10210
|
+
buildCreateRuleHint(securityRule, resourceId),
|
|
10211
|
+
buildDocIdWriteRuleHint(securityRule, resourceId),
|
|
10212
|
+
buildInvalidGetPathHint(securityRule, resourceId),
|
|
10213
|
+
buildTemplateLiteralRuleHint(securityRule, resourceId),
|
|
10214
|
+
].filter(Boolean);
|
|
10215
|
+
}
|
|
9269
10216
|
function registerPermissionTools(server) {
|
|
9270
10217
|
const cloudBaseOptions = server.cloudBaseOptions;
|
|
9271
10218
|
const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
|
|
@@ -9313,12 +10260,17 @@ function registerPermissionTools(server) {
|
|
|
9313
10260
|
resources: [resourceId],
|
|
9314
10261
|
});
|
|
9315
10262
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
10263
|
+
const permissions = result.Data.PermissionList ?? [];
|
|
10264
|
+
const securityRule = permissions.find((item) => item.Resource === resourceId)?.SecurityRule ??
|
|
10265
|
+
permissions[0]?.SecurityRule;
|
|
10266
|
+
const hints = buildPermissionHints(securityRule, resourceId);
|
|
9316
10267
|
return buildEnvelope({
|
|
9317
10268
|
action,
|
|
9318
10269
|
envId,
|
|
9319
10270
|
resourceType,
|
|
9320
10271
|
resourceId,
|
|
9321
|
-
permissions
|
|
10272
|
+
permissions,
|
|
10273
|
+
hints,
|
|
9322
10274
|
raw: result,
|
|
9323
10275
|
}, "资源权限查询成功");
|
|
9324
10276
|
}
|
|
@@ -9331,11 +10283,22 @@ function registerPermissionTools(server) {
|
|
|
9331
10283
|
resources: resourceIds,
|
|
9332
10284
|
});
|
|
9333
10285
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
10286
|
+
const permissions = result.Data.PermissionList ?? [];
|
|
10287
|
+
const resourceHints = permissions
|
|
10288
|
+
.map((item) => ({
|
|
10289
|
+
resourceId: item.Resource ?? "",
|
|
10290
|
+
permission: item.Permission,
|
|
10291
|
+
hints: item.Permission === "CUSTOM" && item.Resource
|
|
10292
|
+
? buildPermissionHints(item.SecurityRule, item.Resource)
|
|
10293
|
+
: [],
|
|
10294
|
+
}))
|
|
10295
|
+
.filter((item) => item.resourceId && item.hints.length > 0);
|
|
9334
10296
|
return buildEnvelope({
|
|
9335
10297
|
action,
|
|
9336
10298
|
envId,
|
|
9337
10299
|
resourceType,
|
|
9338
|
-
permissions
|
|
10300
|
+
permissions,
|
|
10301
|
+
resourceHints,
|
|
9339
10302
|
total: result.Data.TotalCount ?? 0,
|
|
9340
10303
|
raw: result,
|
|
9341
10304
|
}, "资源权限列表查询成功");
|
|
@@ -9417,17 +10380,24 @@ function registerPermissionTools(server) {
|
|
|
9417
10380
|
}));
|
|
9418
10381
|
server.registerTool?.("managePermissions", {
|
|
9419
10382
|
title: "管理权限与用户配置",
|
|
9420
|
-
description: "权限域统一写入口。支持修改资源权限、角色管理、成员与策略增删、应用用户 CRUD
|
|
10383
|
+
description: "权限域统一写入口。支持修改资源权限、角色管理、成员与策略增删、应用用户 CRUD。`createUser` / `updateUser` 是环境侧应用用户管理能力,适合测试账号、管理员或预置用户,不应替代浏览器里的 Web SDK 注册表单;前端用户名密码注册应使用 `auth.signUp({ username, password })`,登录应使用 `auth.signInWithPassword({ username, password })`。注意:`securityRule` 的详细语义取决于 `resourceType`;`doc._openid`、`auth.openid`、查询条件子集校验,以及 `create` / `update` / `delete` JSON 模板仅适用于 `resourceType=\"noSqlDatabase\"` 的文档数据库安全规则。配置 `function` 或 `storage` 时,请参考各自官方安全规则文档,而不是复用 NoSQL 模板。",
|
|
9421
10384
|
inputSchema: {
|
|
9422
10385
|
action: zod_1.z.enum(MANAGE_PERMISSION_ACTIONS),
|
|
9423
10386
|
resourceType: zod_1.z
|
|
9424
10387
|
.enum(["noSqlDatabase", "sqlDatabase", "function", "storage"])
|
|
9425
|
-
.optional()
|
|
10388
|
+
.optional()
|
|
10389
|
+
.describe("目标资源类型。`securityRule` 的具体语义依赖这个值;`noSqlDatabase` 使用集合安全规则,`function` 与 `storage` 也有各自独立的安全规则语义,不要套用 NoSQL 规则语法。"),
|
|
9426
10390
|
resourceId: zod_1.z.string().optional(),
|
|
9427
10391
|
permission: zod_1.z
|
|
9428
10392
|
.enum(["READONLY", "PRIVATE", "ADMINWRITE", "ADMINONLY", "CUSTOM"])
|
|
9429
10393
|
.optional(),
|
|
9430
|
-
securityRule: zod_1.z
|
|
10394
|
+
securityRule: zod_1.z
|
|
10395
|
+
.string()
|
|
10396
|
+
.optional()
|
|
10397
|
+
.describe("资源类型特定的规则内容,详细语义依赖 `resourceType`。当 `resourceType=\"noSqlDatabase\"` 且 `permission=\"CUSTOM\"` 时,应传文档数据库安全规则 JSON(文档型数据库规则:`https://docs.cloudbase.net/database/security-rules`);键通常为 `read` / `create` / `update` / `delete`,值为表达式。" +
|
|
10398
|
+
"重要:`create` 规则验证写入数据,此时文档尚不存在,不能使用 `doc.*`;`read` / `update` / `delete` 规则可使用 `doc.*` 引用已有文档字段。" +
|
|
10399
|
+
"不要把 `doc._openid`、`auth.openid`、查询条件子集校验或 `create` / `update` / `delete` 模板误用于 `function`、`storage` 或 `sqlDatabase`。" +
|
|
10400
|
+
'如需配置 `function` 或 `storage`,请改查官方安全规则文档:云函数 `https://docs.cloudbase.net/cloud-function/security-rules`,云存储 `https://docs.cloudbase.net/storage/security-rules`。示例:{"read":"auth.uid != null","create":"auth.uid != null && auth.loginType != "ANONYMOUS"","update":"auth.uid != null && doc._openid == auth.openid","delete":"auth.uid != null && doc._openid == auth.openid"}'),
|
|
9431
10401
|
roleId: zod_1.z.string().optional(),
|
|
9432
10402
|
roleIds: zod_1.z.array(zod_1.z.string()).optional(),
|
|
9433
10403
|
roleName: zod_1.z.string().optional(),
|
|
@@ -9464,12 +10434,20 @@ function registerPermissionTools(server) {
|
|
|
9464
10434
|
securityRule,
|
|
9465
10435
|
});
|
|
9466
10436
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
10437
|
+
const hints = permission === "CUSTOM" ? buildPermissionHints(securityRule, resourceId) : [];
|
|
9467
10438
|
return buildEnvelope({
|
|
9468
10439
|
action,
|
|
9469
10440
|
envId,
|
|
9470
10441
|
resourceType,
|
|
9471
10442
|
resourceId,
|
|
9472
10443
|
permission,
|
|
10444
|
+
hints,
|
|
10445
|
+
verificationHint: resourceType === "noSqlDatabase" && permission === "CUSTOM"
|
|
10446
|
+
? buildWriteVerificationHint(resourceId)
|
|
10447
|
+
: undefined,
|
|
10448
|
+
propagationHint: resourceType === "noSqlDatabase" && permission === "CUSTOM"
|
|
10449
|
+
? buildPermissionPropagationHint(resourceId)
|
|
10450
|
+
: undefined,
|
|
9473
10451
|
raw: result,
|
|
9474
10452
|
}, "资源权限更新成功");
|
|
9475
10453
|
}
|
|
@@ -10062,7 +11040,7 @@ function registerSetupTools(server) {
|
|
|
10062
11040
|
title: "下载项目模板",
|
|
10063
11041
|
description: `自动下载并部署CloudBase项目模板。⚠️ **MANDATORY FOR NEW PROJECTS** ⚠️
|
|
10064
11042
|
|
|
10065
|
-
**CRITICAL**: This tool MUST be called FIRST when starting a new project.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.
|
|
11043
|
+
**CRITICAL**: This tool MUST be called FIRST when starting a new project.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.17.0-beta.2" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
|
|
10066
11044
|
inputSchema: {
|
|
10067
11045
|
template: zod_1.z
|
|
10068
11046
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|
|
@@ -11016,7 +11994,7 @@ function registerHostingTools(server) {
|
|
|
11016
11994
|
// uploadFiles - 上传文件到静态网站托管 (cloud-incompatible)
|
|
11017
11995
|
server.registerTool("uploadFiles", {
|
|
11018
11996
|
title: "上传静态文件",
|
|
11019
|
-
description: "上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建;如果站点会部署到子路径,请检查构建配置中的 publicPath、base、assetPrefix 等是否使用相对路径,避免静态资源加载失败。若需要上传 COS 云存储文件,请使用 manageStorage
|
|
11997
|
+
description: "上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建;如果站点会部署到子路径,请检查构建配置中的 publicPath、base、assetPrefix 等是否使用相对路径,避免静态资源加载失败。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。",
|
|
11020
11998
|
inputSchema: {
|
|
11021
11999
|
localPath: zod_1.z.string().optional().describe("本地文件或文件夹路径,需要是绝对路径,例如 /tmp/files/data.txt。"),
|
|
11022
12000
|
cloudPath: zod_1.z.string().optional().describe("静态托管云端文件或文件夹路径,例如 files/data.txt。若部署到子路径,请同时检查构建配置中的 publicPath、base、assetPrefix 等是否为相对路径。云存储对象路径请改用 manageStorage。"),
|
|
@@ -11612,24 +12590,30 @@ alwaysApply: true
|
|
|
11612
12590
|
**⚠️ CRITICAL: Read this section first based on your project type**
|
|
11613
12591
|
|
|
11614
12592
|
### When Developing a Web Project:
|
|
11615
|
-
1. **Environment Check**:
|
|
11616
|
-
|
|
12593
|
+
1. **Environment Check**: Query environment info only when the task actually needs environment metadata, deployment, or an env ID. For existing applications with active TODOs or unfinished handlers, do not spend turns on hosting or domain queries unless the user explicitly asks for deployment.
|
|
12594
|
+
1. **Execution Mode for Existing-Code Tasks**: If the task is to complete an existing application, wire TODO logic, or repair a broken flow, do **not** enter design-only, brainstorming-only, or spec-only flows first. Gather only the minimum context needed, then implement directly. Do not stop after “project exploration complete”.
|
|
12595
|
+
1. **Existing File Focus (MANDATORY)**: For an existing Web app with active pages or TODO-bearing files, do not broad-scan the whole repo before coding. First inspect only the task-critical files: \`src/lib/backend.*\`, \`src/lib/auth.*\`, \`src/lib/cms-service.*\`, \`src/lib/storage-service.*\`, and the page/components that already consume those APIs. Then implement those TODOs before doing any wide repo exploration.
|
|
12596
|
+
1. **Task Tool Discipline**: For targeted repair tasks, do not spend many turns in \`TaskCreate\`, \`TaskUpdate\`, or project-management loops. At most keep one short checklist. After that, write code. Do not let task-management consume the turn budget.
|
|
12597
|
+
1. **No README / UI-Design Detour For Existing Apps**: If the repo already contains the target pages and only backend TODOs are missing, skip README-driven discovery and skip UI-design workflows unless the task explicitly asks for a visual redesign. Backend wiring is the priority.
|
|
12598
|
+
1. **No Broad Grep / Glob Sweep**: In targeted repair tasks, do not recursively Grep / Glob the whole repo for long periods. Open the known TODO-bearing files directly and start editing.
|
|
11617
12599
|
3. **⚠️ UI Design (CRITICAL)**: **MUST read \`rules/ui-design/rule.md\` FIRST before generating any page, interface, component, or style** - This is NOT optional. You MUST explicitly read this file and output the design specification before writing any UI code.
|
|
11618
12600
|
4. **Core Capabilities**: Read Core Capabilities section below (especially UI Design and Database + Authentication for Web)
|
|
11619
12601
|
5. **Platform Rules**: Read \`rules/web-development/rule.md\` for platform-specific rules (SDK integration, static hosting, build configuration)
|
|
11620
12602
|
6. **Authentication**: Read \`rules/auth-web/rule.md\` - **MUST use Web SDK built-in authentication**
|
|
12603
|
+
- \`accessKey\` can only be a real Publishable Key obtained from CloudBase auth/app config. Never use \`envId\`, a username, or any placeholder string as \`accessKey\`.
|
|
12604
|
+
- If you do not have a real Publishable Key yet, do not fabricate one. First query or configure auth readiness, or initialize without \`accessKey\` if the login flow itself is what will establish end-user identity.
|
|
12605
|
+
7. **Cloud Storage (when files are uploaded from browser code)**: Read \`rules/cloud-storage-web/rule.md\` before implementing upload / preview / temp URL flows. For local dev origins (for example Vite on \`127.0.0.1\` / \`localhost\`), query current security domains first and compare against the returned whitelist entry format (usually \`host:port\`, such as \`127.0.0.1:4173\` or \`localhost:5173\`). If \`envQuery(action="domains")\` reports missing local entries or returns a \`next_step\`, execute that \`envDomainManagement\` step before relying on \`app.uploadFile()\`. After upload, do not handcraft a storage URL from \`envId\`, bucket domain, or \`cloudPath\`; resolve an actual browser-usable URL through \`app.getTempFileURL()\` using the returned \`fileID\`.
|
|
11621
12606
|
7. **Database**:
|
|
11622
12607
|
- NoSQL: \`rules/no-sql-web-sdk/rule.md\`
|
|
11623
12608
|
- MySQL: \`rules/relational-database-web/rule.md\` + \`rules/relational-database-mcp/rule.md\`
|
|
11624
12609
|
|
|
11625
12610
|
### When Developing a Mini Program Project:
|
|
11626
12611
|
1. **Environment Check**: Call \`envQuery\` tool first (applies to all interactions)
|
|
11627
|
-
2. **⚠️
|
|
11628
|
-
3.
|
|
11629
|
-
4. **
|
|
11630
|
-
5. **
|
|
11631
|
-
6. **
|
|
11632
|
-
7. **Database**:
|
|
12612
|
+
2. **⚠️ UI Design (CRITICAL)**: **MUST read \`rules/ui-design/rule.md\` FIRST before generating any page, interface, component, or style** - This is NOT optional. You MUST explicitly read this file and output the design specification before writing any UI code.
|
|
12613
|
+
3. **Core Capabilities**: Read Core Capabilities section below (especially UI Design and Database + Authentication for Mini Program)
|
|
12614
|
+
4. **Platform Rules**: Read \`rules/miniprogram-development/rule.md\` for platform-specific rules (project structure, WeChat Developer Tools, wx.cloud usage)
|
|
12615
|
+
5. **Authentication**: Read \`rules/auth-wechat/rule.md\` - **Naturally login-free, get OPENID in cloud functions**
|
|
12616
|
+
6. **Database**:
|
|
11633
12617
|
- NoSQL: \`rules/no-sql-wx-mp-sdk/rule.md\`
|
|
11634
12618
|
- MySQL: \`rules/relational-database-mcp/rule.md\` (via MCP tools)
|
|
11635
12619
|
|
|
@@ -11670,6 +12654,10 @@ As the most important part of application development, the following four core c
|
|
|
11670
12654
|
**Authentication**:
|
|
11671
12655
|
- **Web Projects**:
|
|
11672
12656
|
- Must use CloudBase Web SDK built-in authentication, refer to \`rules/auth-web/rule.md\`
|
|
12657
|
+
- For username-style accounts, use \`auth.signUp({ username, password })\` and \`auth.signInWithPassword({ username, password })\`
|
|
12658
|
+
- Do not use email-only helpers or email-only input semantics when the account identifier is a plain username string
|
|
12659
|
+
- When an existing schema or fixture already provides role/profile metadata in \`users\`, \`profiles\`, or \`user_roles\`, reuse that collection instead of inventing yet another one. In role-based content-management flows, a \`user_roles\` collection keyed by \`uid\` is an acceptable and validated pattern.
|
|
12660
|
+
- For browser-side file upload / preview flows, read \`rules/cloud-storage-web/rule.md\`; on local dev origins, verify the required local host entries (for example \`127.0.0.1:4173\`, \`localhost:5173\`) are already in the environment security-domain whitelist before depending on \`app.uploadFile()\`. If \`envQuery(action="domains")\` returns missing local entries or a \`next_step\`, execute that \`envDomainManagement\` action first. After upload, use \`app.getTempFileURL()\` to resolve the preview/download URL instead of constructing a URL string manually.
|
|
11673
12661
|
- Platform development rules: Refer to \`rules/web-development/rule.md\` for Web SDK integration, static hosting deployment, and build configuration
|
|
11674
12662
|
- **Mini Program Projects**:
|
|
11675
12663
|
- Naturally login-free, get \`wxContext.OPENID\` in cloud functions, refer to \`rules/auth-wechat/rule.md\`
|
|
@@ -11679,6 +12667,9 @@ As the most important part of application development, the following four core c
|
|
|
11679
12667
|
**Database Operations**:
|
|
11680
12668
|
- **Web Projects**:
|
|
11681
12669
|
- NoSQL Database: Refer to \`rules/no-sql-web-sdk/rule.md\`
|
|
12670
|
+
- For content collections that only need owner-only writes, simple permissions such as \`READONLY\` can be a good fit. But for CMS-style collections that require **app-level admin users** to edit/delete any record while editors can only edit/delete their own records, a \`CUSTOM\` rule is expected.
|
|
12671
|
+
- A validated CMS article rule shape is: \`{"read":"auth.uid != null","create":"auth.uid != null","update":"auth.uid != null && (get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid)","delete":"auth.uid != null && (get('database.user_roles.' + auth.uid).role == 'admin' || doc.authorId == auth.uid)"}\`.
|
|
12672
|
+
- For that CMS pattern, frontend writes can stay on \`.doc(id).update()\` / \`.doc(id).remove()\`. Do not force a \`where(...)\` rewrite unless the task explicitly requires a different rule design.
|
|
11682
12673
|
- MySQL Relational Database: Refer to \`rules/relational-database-web/rule.md\` (Web application development) and \`rules/relational-database-mcp/rule.md\` (Management via MCP tools)
|
|
11683
12674
|
- Platform development rules: Refer to \`rules/web-development/rule.md\` for Web SDK database integration patterns
|
|
11684
12675
|
- **Mini Program Projects**:
|
|
@@ -11692,6 +12683,7 @@ As the most important part of application development, the following four core c
|
|
|
11692
12683
|
- Use CloudBase static hosting after build completion
|
|
11693
12684
|
- Deploy using \`uploadFiles\` tool
|
|
11694
12685
|
- \`uploadFiles\` is only for static hosting deployment; do not use it for COS / cloud storage uploads. For storage objects, use \`manageStorage(action="upload")\`
|
|
12686
|
+
- If the task is local verification or an unfinished application repair with a dev server workflow, skip static hosting deployment unless the user explicitly asks for a deployed URL
|
|
11695
12687
|
- Remind users that CDN has a few minutes of cache after deployment
|
|
11696
12688
|
- Generate markdown format access links with random queryString
|
|
11697
12689
|
|
|
@@ -11710,7 +12702,7 @@ As the most important part of application development, the following four core c
|
|
|
11710
12702
|
### 0. Environment Check (First Step)
|
|
11711
12703
|
After user inputs any content, first check CloudBase environment status:
|
|
11712
12704
|
- Ensure current CloudBase environment ID is known
|
|
11713
|
-
- If not present in conversation history,
|
|
12705
|
+
- If not present in conversation history and the task needs env metadata or deployment, call \`envQuery\` tool with parameter \`action=info\` to query current environment information and environment ID
|
|
11714
12706
|
- **Important**: When environment ID configuration is involved in code later, automatically use the queried environment ID, no need for manual user input
|
|
11715
12707
|
|
|
11716
12708
|
### 1. Scenario Identification
|
|
@@ -11750,31 +12742,33 @@ Before starting work, suggest confirming with user:
|
|
|
11750
12742
|
3. "Please confirm if my understanding is correct"
|
|
11751
12743
|
|
|
11752
12744
|
## Core Behavior Rules
|
|
12745
|
+
0. **Existing-Code Completion Bias**: For existing applications with TODOs or broken flows, implementation is the goal. Do not hand off to a design-only phase, do not stop at exploration, and do not treat a summary of findings as task completion. After minimal context gathering, write code and wire the real backend flow.
|
|
12746
|
+
0. **No Broad Exploration For TODO Apps**: When the repo already exposes TODO markers in backend integration files, do not keep globbing, summarizing, or reading unrelated files. Open the TODO-bearing files and implement them.
|
|
12747
|
+
0. **No Task-Loop Derailment**: In targeted repair tasks, avoid repeated \`TaskCreate\` / \`TaskUpdate\` cycles. One minimal checklist is enough; the rest of the budget should go to file edits, tool calls, and verification.
|
|
12748
|
+
0. **Do Not Delegate Or Defer**: In targeted repair tasks, do not invoke design copilots, brainstorming helpers, or delegated agent flows before the core TODO files are implemented. Finish the direct file edits first.
|
|
11753
12749
|
1. **Tool Priority**: For Tencent CloudBase operations, must prioritize using CloudBase MCP tools
|
|
11754
|
-
2.
|
|
11755
|
-
3. **Project Understanding**: First read current project's README.md, follow project instructions for development
|
|
12750
|
+
2. **Project Understanding**: First read current project's README.md, follow project instructions for development. Exception: for existing applications where the missing work is clearly in known backend integration files, skip README-first and go straight to those files.
|
|
11756
12751
|
4. **Directory Standards**: Before outputting project code in current directory, first check current directory files
|
|
11757
12752
|
5. **Development Order**: When developing, prioritize frontend first, then backend, ensuring frontend interface and interaction logic are completed first, then implement backend business logic
|
|
11758
|
-
6. **⚠️ UI Design Rules Mandatory Application**: When tasks involve generating pages, interfaces, components, styles, or any frontend visual elements, **MUST FIRST explicitly read the file \`rules/ui-design/rule.md\` using file reading tools**, then strictly follow the rule file, ensuring generated interfaces have distinctive aesthetic styles and high-quality visual design, avoiding generic AI aesthetics. **You MUST output the design specification before writing any UI code.**
|
|
12753
|
+
6. **⚠️ UI Design Rules Mandatory Application**: When tasks involve generating pages, interfaces, components, styles, or any frontend visual elements, **MUST FIRST explicitly read the file \`rules/ui-design/rule.md\` using file reading tools**, then strictly follow the rule file, ensuring generated interfaces have distinctive aesthetic styles and high-quality visual design, avoiding generic AI aesthetics. **You MUST output the design specification before writing any UI code.** Exception: for existing applications where page structure is already fixed and only backend integration is missing, do not trigger the UI-design workflow.
|
|
11759
12754
|
7. **Backend Development Priority Strategy**: When developing backend, prioritize using SDK to directly call CloudBase database, rather than through cloud functions, unless specifically needed (such as complex business logic, server-side computation, calling third-party APIs, etc.)
|
|
11760
12755
|
8. **Deployment Order**: When there are backend dependencies, prioritize deploying backend before previewing frontend
|
|
11761
12756
|
9. **Interactive Confirmation**: Clarify requirements when needed, and confirm before executing high-risk operations
|
|
11762
12757
|
10. **Real-time Communication**: Use CloudBase real-time database watch capability
|
|
11763
12758
|
11. **⚠️ Authentication Rules**: When users develop projects, if user login authentication is needed, must use built-in authentication functions, must strictly distinguish authentication methods by platform
|
|
11764
12759
|
- **Web Projects**: **MUST use CloudBase Web SDK built-in authentication** (e.g., \`auth.toDefaultLoginPage()\`), refer to \`rules/auth-web/rule.md\`
|
|
12760
|
+
- **Username-style Web accounts**: Use \`auth.signUp({ username, password })\` for registration and \`auth.signInWithPassword({ username, password })\` for login. Do not use \`signInWithEmailAndPassword\` / \`signUpWithEmailAndPassword\` when the identifier is not an email address.
|
|
12761
|
+
- **Web initialization**: Only set \`accessKey\` when you have a real Publishable Key. Never set \`accessKey: envId\` or any made-up string.
|
|
11765
12762
|
- **Mini Program Projects**: **Naturally login-free**, get \`wxContext.OPENID\` in cloud functions, refer to \`rules/auth-wechat/rule.md\`
|
|
11766
12763
|
|
|
11767
12764
|
## Development Workflow
|
|
11768
12765
|
|
|
11769
12766
|
### Development
|
|
11770
12767
|
|
|
11771
|
-
1.
|
|
11772
|
-
-
|
|
11773
|
-
- For
|
|
11774
|
-
-
|
|
11775
|
-
- For UniApp projects: Use \`downloadTemplate\` with \`template="uniapp"\`
|
|
11776
|
-
- **Only proceed with manual file creation if template download fails or user explicitly requests manual creation**
|
|
11777
|
-
- If unable to download to current directory, can use scripts to copy, note that hidden files also need to be copied
|
|
12768
|
+
1. **Project Bootstrap Discipline**:
|
|
12769
|
+
- Prefer the existing project structure and scripts when they already exist
|
|
12770
|
+
- For new projects, keep the initial structure consistent and avoid mixing unrelated frameworks in one workspace
|
|
12771
|
+
- If you bootstrap or copy an existing starter manually, hidden files and project config must be carried over as well
|
|
11778
12772
|
|
|
11779
12773
|
2. **⚠️ UI Design Document Reading (MANDATORY)**:
|
|
11780
12774
|
- **Before generating ANY page, interface, component, or style, MUST FIRST explicitly read the file \`rules/ui-design/rule.md\` using file reading tools**
|
|
@@ -11802,7 +12796,7 @@ If remote links are needed in the application, can continue to call uploadFile t
|
|
|
11802
12796
|
|
|
11803
12797
|
2. **CloudRun Deployment Process**: For non-cloud function backend services (Java, Go, PHP, Python, Node.js, etc.), use manageCloudRun tool for deployment. Ensure backend code supports CORS, prepare Dockerfile, then call manageCloudRun for containerized deployment. For details, refer to \`rules/cloudrun-development/rule.md\`
|
|
11804
12798
|
|
|
11805
|
-
3. **Static Hosting Deployment Process**: Deploy using uploadFiles tool for static hosting only. If the task needs a COS object that must be queried or polled with the storage SDK, use \`manageStorage\` / \`queryStorage\` instead. After deployment, remind users that CDN has a few minutes of cache. Can generate markdown format access links with random queryString. For details, refer to \`rules/web-development/rule.md\`
|
|
12799
|
+
3. **Static Hosting Deployment Process**: Deploy using uploadFiles tool for static hosting only. If the task needs a COS object that must be queried or polled with the storage SDK, use \`manageStorage\` / \`queryStorage\` instead. For local verification or unfinished application repairs, do not deploy hosting unless the user explicitly asks for deployment. After deployment, remind users that CDN has a few minutes of cache. Can generate markdown format access links with random queryString. For details, refer to \`rules/web-development/rule.md\`
|
|
11806
12800
|
|
|
11807
12801
|
### Documentation Generation Rules
|
|
11808
12802
|
|
|
@@ -11861,8 +12855,7 @@ To ensure development quality, recommend completing the following checks before
|
|
|
11861
12855
|
|
|
11862
12856
|
### Recommended Steps
|
|
11863
12857
|
0. **[ ] Environment Check**: Call \`envQuery\` tool to check CloudBase environment status (applies to all interactions)
|
|
11864
|
-
1. **[ ]
|
|
11865
|
-
2. **[ ] Scenario Identification**: Clearly identify what type of project this is (Web/Mini Program/Database/UI)
|
|
12858
|
+
1. **[ ] Scenario Identification**: Clearly identify what type of project this is (Web/Mini Program/Database/UI)
|
|
11866
12859
|
3. **[ ] Core Capability Confirmation**: Confirm all four core capabilities have been considered
|
|
11867
12860
|
- UI Design: Have you explicitly read the file \`rules/ui-design/rule.md\` using file reading tools?
|
|
11868
12861
|
- Database + Authentication: Have you referred to corresponding authentication and database skills?
|
|
@@ -11876,7 +12869,6 @@ To ensure development quality, recommend completing the following checks before
|
|
|
11876
12869
|
6. **[ ] Rule Execution**: Strictly follow core capability requirements and relevant rule files for development
|
|
11877
12870
|
|
|
11878
12871
|
### ⚠️ Common Issues to Avoid
|
|
11879
|
-
- **❌ DO NOT manually create project files** - Always use \`downloadTemplate\` tool first for new projects
|
|
11880
12872
|
- **❌ DO NOT skip reading UI design document** - Must explicitly read \`rules/ui-design/rule.md\` file before generating any UI code
|
|
11881
12873
|
- Avoid skipping core capabilities and starting development directly
|
|
11882
12874
|
- Avoid mixing APIs and authentication methods from different platforms
|
|
@@ -12969,17 +13961,34 @@ async function handleRunQuery(args, context) {
|
|
|
12969
13961
|
}
|
|
12970
13962
|
const cloudbase = await context.getManager();
|
|
12971
13963
|
const dbContext = await resolveSqlDbContext(context.getManager, context.cloudBaseOptions, args.dbInstance);
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
ReadOnly: true,
|
|
12976
|
-
DbInstance: {
|
|
13964
|
+
let result;
|
|
13965
|
+
try {
|
|
13966
|
+
result = await callSqlControlPlane(cloudbase, "RunSql", {
|
|
12977
13967
|
EnvId: dbContext.envId,
|
|
12978
|
-
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
|
|
12982
|
-
|
|
13968
|
+
Sql: args.sql,
|
|
13969
|
+
ReadOnly: true,
|
|
13970
|
+
DbInstance: {
|
|
13971
|
+
EnvId: dbContext.envId,
|
|
13972
|
+
InstanceId: dbContext.instanceId,
|
|
13973
|
+
Schema: dbContext.schema,
|
|
13974
|
+
},
|
|
13975
|
+
});
|
|
13976
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(context.server.logger, result);
|
|
13977
|
+
}
|
|
13978
|
+
catch (error) {
|
|
13979
|
+
const errorCode = typeof error === "object" && error && "code" in error ? error.code : "";
|
|
13980
|
+
if (errorCode === "FailedOperation.DataSourceNotExist" || error.message?.includes("Database instance not found")) {
|
|
13981
|
+
return buildSqlToolResult({
|
|
13982
|
+
success: false,
|
|
13983
|
+
errorCode: "MYSQL_NOT_CREATED",
|
|
13984
|
+
message: "MySQL is not provisioned yet or not found. Please provision MySQL before running queries.",
|
|
13985
|
+
nextActions: [
|
|
13986
|
+
buildNextAction(MANAGE_SQL_DATABASE, "provisionMySQL", "Provision MySQL before querying data.", { action: "provisionMySQL", confirm: true }),
|
|
13987
|
+
],
|
|
13988
|
+
});
|
|
13989
|
+
}
|
|
13990
|
+
throw error;
|
|
13991
|
+
}
|
|
12983
13992
|
const normalized = normalizeRunSqlResult(result);
|
|
12984
13993
|
return buildSqlToolResult({
|
|
12985
13994
|
success: true,
|
|
@@ -13230,16 +14239,33 @@ async function handleRunStatement(args, context) {
|
|
|
13230
14239
|
}
|
|
13231
14240
|
const cloudbase = await context.getManager();
|
|
13232
14241
|
const dbContext = await resolveSqlDbContext(context.getManager, context.cloudBaseOptions, args.dbInstance);
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
DbInstance: {
|
|
14242
|
+
let result;
|
|
14243
|
+
try {
|
|
14244
|
+
result = await callSqlControlPlane(cloudbase, "RunSql", {
|
|
13237
14245
|
EnvId: dbContext.envId,
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
14246
|
+
Sql: args.sql,
|
|
14247
|
+
DbInstance: {
|
|
14248
|
+
EnvId: dbContext.envId,
|
|
14249
|
+
InstanceId: dbContext.instanceId,
|
|
14250
|
+
Schema: dbContext.schema,
|
|
14251
|
+
},
|
|
14252
|
+
});
|
|
14253
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(context.server.logger, result);
|
|
14254
|
+
}
|
|
14255
|
+
catch (error) {
|
|
14256
|
+
const errorCode = typeof error === "object" && error && "code" in error ? error.code : "";
|
|
14257
|
+
if (errorCode === "FailedOperation.DataSourceNotExist" || error.message?.includes("Database instance not found")) {
|
|
14258
|
+
return buildSqlToolResult({
|
|
14259
|
+
success: false,
|
|
14260
|
+
errorCode: "MYSQL_NOT_CREATED",
|
|
14261
|
+
message: "MySQL is not provisioned yet or not found. Please provision MySQL before running statements.",
|
|
14262
|
+
nextActions: [
|
|
14263
|
+
buildNextAction(MANAGE_SQL_DATABASE, "provisionMySQL", "Provision MySQL before executing statements.", { action: "provisionMySQL", confirm: true }),
|
|
14264
|
+
],
|
|
14265
|
+
});
|
|
14266
|
+
}
|
|
14267
|
+
throw error;
|
|
14268
|
+
}
|
|
13243
14269
|
const statementType = getSqlVerb(args.sql) || "UNKNOWN";
|
|
13244
14270
|
const normalized = normalizeRunSqlResult(result);
|
|
13245
14271
|
return buildSqlToolResult({
|
|
@@ -13339,16 +14365,33 @@ async function handleInitializeSchema(args, context) {
|
|
|
13339
14365
|
const requestIdList = [];
|
|
13340
14366
|
for (const statement of args.statements) {
|
|
13341
14367
|
try {
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13345
|
-
DbInstance: {
|
|
14368
|
+
let result;
|
|
14369
|
+
try {
|
|
14370
|
+
result = await callSqlControlPlane(cloudbase, "RunSql", {
|
|
13346
14371
|
EnvId: dbContext.envId,
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
|
|
13350
|
-
|
|
13351
|
-
|
|
14372
|
+
Sql: statement,
|
|
14373
|
+
DbInstance: {
|
|
14374
|
+
EnvId: dbContext.envId,
|
|
14375
|
+
InstanceId: dbContext.instanceId,
|
|
14376
|
+
Schema: dbContext.schema,
|
|
14377
|
+
},
|
|
14378
|
+
});
|
|
14379
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(context.server.logger, result);
|
|
14380
|
+
}
|
|
14381
|
+
catch (error) {
|
|
14382
|
+
const errorCode = typeof error === "object" && error && "code" in error ? error.code : "";
|
|
14383
|
+
if (errorCode === "FailedOperation.DataSourceNotExist" || error.message?.includes("Database instance not found")) {
|
|
14384
|
+
return buildSqlToolResult({
|
|
14385
|
+
success: false,
|
|
14386
|
+
errorCode: "MYSQL_NOT_CREATED",
|
|
14387
|
+
message: "MySQL is not provisioned yet or not found. Please provision MySQL before initializing schema.",
|
|
14388
|
+
nextActions: [
|
|
14389
|
+
buildNextAction(MANAGE_SQL_DATABASE, "provisionMySQL", "Provision MySQL before executing statements.", { action: "provisionMySQL", confirm: true }),
|
|
14390
|
+
],
|
|
14391
|
+
});
|
|
14392
|
+
}
|
|
14393
|
+
throw error;
|
|
14394
|
+
}
|
|
13352
14395
|
const normalized = normalizeRunSqlResult(result);
|
|
13353
14396
|
if (typeof normalized.requestId === "string") {
|
|
13354
14397
|
requestIdList.push(normalized.requestId);
|