@cloudbase/cloudbase-mcp 2.23.9 → 2.23.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +3 -3
- package/dist/index.cjs +551 -315
- package/dist/index.js +551 -315
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2200,7 +2200,7 @@ ${envIdSection}
|
|
|
2200
2200
|
## 环境信息
|
|
2201
2201
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
2202
2202
|
- Node.js版本: ${process.version}
|
|
2203
|
-
- MCP 版本:${process.env.npm_package_version || "2.23.
|
|
2203
|
+
- MCP 版本:${process.env.npm_package_version || "2.23.11" || 0}
|
|
2204
2204
|
- 系统架构: ${os_1.default.arch()}
|
|
2205
2205
|
- 时间: ${new Date().toISOString()}
|
|
2206
2206
|
- 请求ID: ${requestId}
|
|
@@ -2281,7 +2281,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
2281
2281
|
requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
|
|
2282
2282
|
ide: server.ide || process.env.INTEGRATION_IDE || ''
|
|
2283
2283
|
});
|
|
2284
|
-
const mcpVersion = true ? "2.23.
|
|
2284
|
+
const mcpVersion = true ? "2.23.11" : 0;
|
|
2285
2285
|
const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
|
|
2286
2286
|
const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
|
|
2287
2287
|
// 创建新的错误对象,保持原有的错误类型但更新消息
|
|
@@ -6522,6 +6522,12 @@ async function resolveSkillSearchRoots(options = {}) {
|
|
|
6522
6522
|
? path.resolve(path.dirname(cliEntryPath), "..", "..")
|
|
6523
6523
|
: undefined;
|
|
6524
6524
|
return filterExistingDirs([
|
|
6525
|
+
repoRoot
|
|
6526
|
+
? path.join(repoRoot, ".generated", "compat-config", ".agents", "skills")
|
|
6527
|
+
: undefined,
|
|
6528
|
+
repoRoot
|
|
6529
|
+
? path.join(repoRoot, ".generated", "compat-config", ".claude", "skills")
|
|
6530
|
+
: undefined,
|
|
6525
6531
|
repoRoot
|
|
6526
6532
|
? path.join(repoRoot, ".generated", "compat-config", ".codebuddy", "skills")
|
|
6527
6533
|
: undefined,
|
|
@@ -7765,6 +7771,7 @@ const os = __importStar(__webpack_require__(8116));
|
|
|
7765
7771
|
const path = __importStar(__webpack_require__(2521));
|
|
7766
7772
|
const zod_1 = __webpack_require__(2971);
|
|
7767
7773
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
7774
|
+
const tool_result_js_1 = __webpack_require__(9835);
|
|
7768
7775
|
const MAX_INLINE_TEXT_BYTES = 256 * 1024;
|
|
7769
7776
|
const STORAGE_READ_TEMP_PREFIX = 'cloudbase-mcp-storage-read-';
|
|
7770
7777
|
// Input schema for queryStorage tool
|
|
@@ -7847,7 +7854,7 @@ function registerStorageTools(server) {
|
|
|
7847
7854
|
// Tool 1: queryStorage - 查询存储信息(只读操作)
|
|
7848
7855
|
server.registerTool("queryStorage", {
|
|
7849
7856
|
title: "查询 CloudBase 存储信息",
|
|
7850
|
-
description: "查询 CloudBase 云存储信息,支持列出目录文件、获取文件信息、获取临时下载链接等只读操作。返回的文件信息包括文件名、大小、修改时间、下载链接等。注意:action=url 返回的 temporaryUrl 是临时签名链接,有效期由 maxAge 参数决定(默认1小时),不要当作永久公网地址使用。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。\n\n💡 存储桶 ACL 权限管理请使用 permissions 工具:queryPermissions(action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\") 查询,managePermissions(action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"READONLY\") 设置。\n\n📦 CloudBase PG / pgstore 环境:`DescribeEnvs.Storages[]` 列出的 bucket 是旧 NoSQL 后端的,不等于 pgstore bucket。本工具用于查看常规存储;为 PG 浏览器上传准备 bucket 时,请确认目标 bucket 是 pgstore 后端可用的,否则浏览器 `app.storage.from().upload(...)` 会得到 `STORAGE_BUCKET_NOT_FOUND` 并出现 `PUT https://undefined/`。",
|
|
7857
|
+
description: "⚠️ PG 模式环境请使用 queryPgStorage 而非本工具(pgstore 与旧 COS 是两套独立系统)。\n\n查询 CloudBase 云存储信息,支持列出目录文件、获取文件信息、获取临时下载链接等只读操作。返回的文件信息包括文件名、大小、修改时间、下载链接等。注意:action=url 返回的 temporaryUrl 是临时签名链接,有效期由 maxAge 参数决定(默认1小时),不要当作永久公网地址使用。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。\n\n💡 存储桶 ACL 权限管理请使用 permissions 工具:queryPermissions(action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\") 查询,managePermissions(action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"READONLY\") 设置。\n\n📦 CloudBase PG / pgstore 环境:`DescribeEnvs.Storages[]` 列出的 bucket 是旧 NoSQL 后端的,不等于 pgstore bucket。本工具用于查看常规存储;为 PG 浏览器上传准备 bucket 时,请确认目标 bucket 是 pgstore 后端可用的,否则浏览器 `app.storage.from().upload(...)` 会得到 `STORAGE_BUCKET_NOT_FOUND` 并出现 `PUT https://undefined/`。",
|
|
7851
7858
|
inputSchema: queryStorageInputSchema,
|
|
7852
7859
|
annotations: {
|
|
7853
7860
|
readOnlyHint: true,
|
|
@@ -7856,125 +7863,89 @@ function registerStorageTools(server) {
|
|
|
7856
7863
|
}
|
|
7857
7864
|
}, async (args) => {
|
|
7858
7865
|
const input = args;
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
const storageService = manager.storage;
|
|
7864
|
-
switch (input.action) {
|
|
7865
|
-
case 'list': {
|
|
7866
|
-
let files;
|
|
7867
|
-
if (storageOverrides?.listFiles) {
|
|
7868
|
-
files = await storageOverrides.listFiles({ cloudPath: input.cloudPath });
|
|
7869
|
-
}
|
|
7870
|
-
else {
|
|
7871
|
-
const result = await storageService.listDirectoryFiles(input.cloudPath);
|
|
7872
|
-
files = result || [];
|
|
7873
|
-
}
|
|
7874
|
-
return {
|
|
7875
|
-
content: [
|
|
7876
|
-
{
|
|
7877
|
-
type: "text",
|
|
7878
|
-
text: JSON.stringify({
|
|
7879
|
-
success: true,
|
|
7880
|
-
data: {
|
|
7881
|
-
action: 'list',
|
|
7882
|
-
cloudPath: input.cloudPath,
|
|
7883
|
-
files,
|
|
7884
|
-
totalCount: files.length
|
|
7885
|
-
},
|
|
7886
|
-
message: `Successfully listed ${files.length} files in directory '${input.cloudPath}'`
|
|
7887
|
-
}, null, 2)
|
|
7888
|
-
}
|
|
7889
|
-
]
|
|
7890
|
-
};
|
|
7891
|
-
}
|
|
7892
|
-
case 'info': {
|
|
7893
|
-
let fileInfo;
|
|
7894
|
-
if (storageOverrides?.getFileInfo) {
|
|
7895
|
-
fileInfo = await storageOverrides.getFileInfo({ cloudPath: input.cloudPath });
|
|
7896
|
-
}
|
|
7897
|
-
else {
|
|
7898
|
-
fileInfo = await storageService.getFileInfo(input.cloudPath);
|
|
7899
|
-
}
|
|
7900
|
-
return {
|
|
7901
|
-
content: [
|
|
7902
|
-
{
|
|
7903
|
-
type: "text",
|
|
7904
|
-
text: JSON.stringify({
|
|
7905
|
-
success: true,
|
|
7906
|
-
data: {
|
|
7907
|
-
action: 'info',
|
|
7908
|
-
cloudPath: input.cloudPath,
|
|
7909
|
-
fileInfo
|
|
7910
|
-
},
|
|
7911
|
-
message: `Successfully retrieved file info for '${input.cloudPath}'`
|
|
7912
|
-
}, null, 2)
|
|
7913
|
-
}
|
|
7914
|
-
]
|
|
7915
|
-
};
|
|
7866
|
+
try {
|
|
7867
|
+
const manager = await getManager();
|
|
7868
|
+
if (!manager) {
|
|
7869
|
+
throw new Error("Failed to initialize CloudBase manager. Please check your credentials and environment configuration.");
|
|
7916
7870
|
}
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
cloudPath: input.cloudPath
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7871
|
+
const storageService = manager.storage;
|
|
7872
|
+
switch (input.action) {
|
|
7873
|
+
case 'list': {
|
|
7874
|
+
let files;
|
|
7875
|
+
if (storageOverrides?.listFiles) {
|
|
7876
|
+
files = await storageOverrides.listFiles({ cloudPath: input.cloudPath });
|
|
7877
|
+
}
|
|
7878
|
+
else {
|
|
7879
|
+
const result = await storageService.listDirectoryFiles(input.cloudPath);
|
|
7880
|
+
files = result || [];
|
|
7881
|
+
}
|
|
7882
|
+
return {
|
|
7883
|
+
content: [
|
|
7884
|
+
{
|
|
7885
|
+
type: "text",
|
|
7886
|
+
text: JSON.stringify({
|
|
7887
|
+
success: true,
|
|
7888
|
+
data: {
|
|
7889
|
+
action: 'list',
|
|
7890
|
+
cloudPath: input.cloudPath,
|
|
7891
|
+
files,
|
|
7892
|
+
totalCount: files.length
|
|
7893
|
+
},
|
|
7894
|
+
message: `Successfully listed ${files.length} files in directory '${input.cloudPath}'`
|
|
7895
|
+
}, null, 2)
|
|
7896
|
+
}
|
|
7897
|
+
]
|
|
7898
|
+
};
|
|
7935
7899
|
}
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
});
|
|
7941
|
-
return {
|
|
7942
|
-
content: [
|
|
7943
|
-
{
|
|
7944
|
-
type: "text",
|
|
7945
|
-
text: JSON.stringify({
|
|
7946
|
-
success: true,
|
|
7947
|
-
data: {
|
|
7948
|
-
action: 'url',
|
|
7949
|
-
cloudPath: input.cloudPath,
|
|
7950
|
-
temporaryUrl,
|
|
7951
|
-
expireTime: `${input.maxAge || 3600}秒`,
|
|
7952
|
-
fileId,
|
|
7953
|
-
storageCdnDomain: publicAccess.storageCdnDomain,
|
|
7954
|
-
publicUrl: publicAccess.publicUrl,
|
|
7955
|
-
note: "temporaryUrl 是临时签名链接,会按 expireTime 过期。publicUrl 基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导,⚠️ 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先将目标路径设置为公有读。"
|
|
7956
|
-
},
|
|
7957
|
-
message: `Successfully generated temporary URL for '${input.cloudPath}'`
|
|
7958
|
-
}, null, 2)
|
|
7959
|
-
}
|
|
7960
|
-
]
|
|
7961
|
-
};
|
|
7962
|
-
}
|
|
7963
|
-
case 'read': {
|
|
7964
|
-
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), STORAGE_READ_TEMP_PREFIX));
|
|
7965
|
-
const localPath = path.join(tempDir, getStorageTempFileName(input.cloudPath));
|
|
7966
|
-
try {
|
|
7967
|
-
if (storageOverrides?.downloadFile) {
|
|
7968
|
-
await storageOverrides.downloadFile({ cloudPath: input.cloudPath, localPath });
|
|
7900
|
+
case 'info': {
|
|
7901
|
+
let fileInfo;
|
|
7902
|
+
if (storageOverrides?.getFileInfo) {
|
|
7903
|
+
fileInfo = await storageOverrides.getFileInfo({ cloudPath: input.cloudPath });
|
|
7969
7904
|
}
|
|
7970
7905
|
else {
|
|
7971
|
-
await storageService.
|
|
7906
|
+
fileInfo = await storageService.getFileInfo(input.cloudPath);
|
|
7907
|
+
}
|
|
7908
|
+
return {
|
|
7909
|
+
content: [
|
|
7910
|
+
{
|
|
7911
|
+
type: "text",
|
|
7912
|
+
text: JSON.stringify({
|
|
7913
|
+
success: true,
|
|
7914
|
+
data: {
|
|
7915
|
+
action: 'info',
|
|
7916
|
+
cloudPath: input.cloudPath,
|
|
7917
|
+
fileInfo
|
|
7918
|
+
},
|
|
7919
|
+
message: `Successfully retrieved file info for '${input.cloudPath}'`
|
|
7920
|
+
}, null, 2)
|
|
7921
|
+
}
|
|
7922
|
+
]
|
|
7923
|
+
};
|
|
7924
|
+
}
|
|
7925
|
+
case 'url': {
|
|
7926
|
+
let temporaryUrl = "";
|
|
7927
|
+
let fileId = "";
|
|
7928
|
+
if (storageOverrides?.getFileUrl) {
|
|
7929
|
+
const urlResult = await storageOverrides.getFileUrl({
|
|
7972
7930
|
cloudPath: input.cloudPath,
|
|
7973
|
-
|
|
7931
|
+
maxAge: input.maxAge || 3600,
|
|
7974
7932
|
});
|
|
7933
|
+
temporaryUrl = urlResult.url;
|
|
7934
|
+
fileId = urlResult.fileId || "";
|
|
7935
|
+
}
|
|
7936
|
+
else {
|
|
7937
|
+
const result = await storageService.getTemporaryUrl([{
|
|
7938
|
+
cloudPath: input.cloudPath,
|
|
7939
|
+
maxAge: input.maxAge || 3600
|
|
7940
|
+
}]);
|
|
7941
|
+
temporaryUrl = result[0]?.url || "";
|
|
7942
|
+
fileId = result[0]?.fileId || "";
|
|
7975
7943
|
}
|
|
7976
|
-
const
|
|
7977
|
-
|
|
7944
|
+
const publicAccess = await resolveStoragePublicAccess({
|
|
7945
|
+
cloudPath: input.cloudPath,
|
|
7946
|
+
cloudBaseOptions,
|
|
7947
|
+
manager,
|
|
7948
|
+
});
|
|
7978
7949
|
return {
|
|
7979
7950
|
content: [
|
|
7980
7951
|
{
|
|
@@ -7982,33 +7953,80 @@ function registerStorageTools(server) {
|
|
|
7982
7953
|
text: JSON.stringify({
|
|
7983
7954
|
success: true,
|
|
7984
7955
|
data: {
|
|
7985
|
-
action: '
|
|
7956
|
+
action: 'url',
|
|
7986
7957
|
cloudPath: input.cloudPath,
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7958
|
+
temporaryUrl,
|
|
7959
|
+
expireTime: `${input.maxAge || 3600}秒`,
|
|
7960
|
+
fileId,
|
|
7961
|
+
storageCdnDomain: publicAccess.storageCdnDomain,
|
|
7962
|
+
publicUrl: publicAccess.publicUrl,
|
|
7963
|
+
note: "temporaryUrl 是临时签名链接,会按 expireTime 过期。publicUrl 基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导,⚠️ 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先将目标路径设置为公有读。"
|
|
7991
7964
|
},
|
|
7992
|
-
message:
|
|
7993
|
-
? `Successfully read text content for '${input.cloudPath}' (truncated to ${MAX_INLINE_TEXT_BYTES} bytes)`
|
|
7994
|
-
: `Successfully read text content for '${input.cloudPath}'`
|
|
7965
|
+
message: `Successfully generated temporary URL for '${input.cloudPath}'`
|
|
7995
7966
|
}, null, 2)
|
|
7996
7967
|
}
|
|
7997
7968
|
]
|
|
7998
7969
|
};
|
|
7999
7970
|
}
|
|
8000
|
-
|
|
8001
|
-
await fs.
|
|
7971
|
+
case 'read': {
|
|
7972
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), STORAGE_READ_TEMP_PREFIX));
|
|
7973
|
+
const localPath = path.join(tempDir, getStorageTempFileName(input.cloudPath));
|
|
7974
|
+
try {
|
|
7975
|
+
if (storageOverrides?.downloadFile) {
|
|
7976
|
+
await storageOverrides.downloadFile({ cloudPath: input.cloudPath, localPath });
|
|
7977
|
+
}
|
|
7978
|
+
else {
|
|
7979
|
+
await storageService.downloadFile({
|
|
7980
|
+
cloudPath: input.cloudPath,
|
|
7981
|
+
localPath
|
|
7982
|
+
});
|
|
7983
|
+
}
|
|
7984
|
+
const buffer = await fs.readFile(localPath);
|
|
7985
|
+
const decoded = decodeInlineTextContent(buffer);
|
|
7986
|
+
return {
|
|
7987
|
+
content: [
|
|
7988
|
+
{
|
|
7989
|
+
type: "text",
|
|
7990
|
+
text: JSON.stringify({
|
|
7991
|
+
success: true,
|
|
7992
|
+
data: {
|
|
7993
|
+
action: 'read',
|
|
7994
|
+
cloudPath: input.cloudPath,
|
|
7995
|
+
content: decoded.content,
|
|
7996
|
+
encoding: decoded.encoding,
|
|
7997
|
+
sizeBytes: decoded.sizeBytes,
|
|
7998
|
+
truncated: decoded.truncated
|
|
7999
|
+
},
|
|
8000
|
+
message: decoded.truncated
|
|
8001
|
+
? `Successfully read text content for '${input.cloudPath}' (truncated to ${MAX_INLINE_TEXT_BYTES} bytes)`
|
|
8002
|
+
: `Successfully read text content for '${input.cloudPath}'`
|
|
8003
|
+
}, null, 2)
|
|
8004
|
+
}
|
|
8005
|
+
]
|
|
8006
|
+
};
|
|
8007
|
+
}
|
|
8008
|
+
finally {
|
|
8009
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
8010
|
+
}
|
|
8002
8011
|
}
|
|
8012
|
+
default:
|
|
8013
|
+
throw new Error(`Unsupported action: ${input.action}`);
|
|
8003
8014
|
}
|
|
8004
|
-
|
|
8005
|
-
|
|
8015
|
+
}
|
|
8016
|
+
catch (error) {
|
|
8017
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8018
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
8019
|
+
success: false,
|
|
8020
|
+
message: `queryStorage 操作失败: ${message}`,
|
|
8021
|
+
action: input.action,
|
|
8022
|
+
cloudPath: input.cloudPath,
|
|
8023
|
+
});
|
|
8006
8024
|
}
|
|
8007
8025
|
});
|
|
8008
8026
|
// Tool 2: manageStorage - 管理存储文件(写操作)
|
|
8009
8027
|
server.registerTool("manageStorage", {
|
|
8010
8028
|
title: "管理 CloudBase 存储文件",
|
|
8011
|
-
description: "管理 CloudBase 云存储文件,仅用于 COS/Storage 对象,不用于静态网站托管。支持上传文件/目录、下载文件/目录、删除文件/目录等操作。删除操作需要设置force=true进行确认,防止误删除重要文件。注意:上传后返回的 temporaryUrl 是临时签名链接,1小时后过期,不要当作永久公网地址写入配置或持久化存储。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。\n\n💡 存储桶 ACL 权限管理请使用 permissions 工具:queryPermissions(action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\") 查询,managePermissions(action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"READONLY\") 设置。\n\n📦 CloudBase PG / pgstore 桶必须先创建后使用(与 Supabase Storage 一致:upload 前 bucket 必须存在)。浏览器 SDK `app.storage.from().upload(path, file)` 不会自动建桶,且 `path` 的第一段就是 bucket 名(例如 `covers/foo.png` → bucket=`covers`);`from('covers')` 这个参数当前不会被拼到 path 里。如果上传时浏览器看到 `STORAGE_BUCKET_NOT_FOUND` 或 `PUT https://undefined/`(DevTools 表现为 `net::ERR_NAME_NOT_RESOLVED`),先用本工具或控制台确认 / 创建对应的 pgstore bucket,再让前端重试上传,不要让前端把上传失败静默吞掉。`DescribeEnvs.Storages[]` 返回的旧 NoSQL bucket(形如 `<hash>-<envId>-<appId>`)不是可用的 pgstore bucket,切勿当作默认目标使用。",
|
|
8029
|
+
description: "⚠️ PG 模式环境请使用 queryPgStorage 而非本工具(pgstore 与旧 COS 是两套独立系统)。\n\n管理 CloudBase 云存储文件,仅用于 COS/Storage 对象,不用于静态网站托管。支持上传文件/目录、下载文件/目录、删除文件/目录等操作。删除操作需要设置force=true进行确认,防止误删除重要文件。注意:上传后返回的 temporaryUrl 是临时签名链接,1小时后过期,不要当作永久公网地址写入配置或持久化存储。工具还会基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导 publicUrl,⚠️ 警告:publicUrl 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先通过控制台/SDK 将目标路径设置为公有读。\n\n💡 存储桶 ACL 权限管理请使用 permissions 工具:queryPermissions(action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\") 查询,managePermissions(action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"READONLY\") 设置。\n\n📦 CloudBase PG / pgstore 桶必须先创建后使用(与 Supabase Storage 一致:upload 前 bucket 必须存在)。浏览器 SDK `app.storage.from().upload(path, file)` 不会自动建桶,且 `path` 的第一段就是 bucket 名(例如 `covers/foo.png` → bucket=`covers`);`from('covers')` 这个参数当前不会被拼到 path 里。如果上传时浏览器看到 `STORAGE_BUCKET_NOT_FOUND` 或 `PUT https://undefined/`(DevTools 表现为 `net::ERR_NAME_NOT_RESOLVED`),先用本工具或控制台确认 / 创建对应的 pgstore bucket,再让前端重试上传,不要让前端把上传失败静默吞掉。`DescribeEnvs.Storages[]` 返回的旧 NoSQL bucket(形如 `<hash>-<envId>-<appId>`)不是可用的 pgstore bucket,切勿当作默认目标使用。",
|
|
8012
8030
|
inputSchema: manageStorageInputSchema,
|
|
8013
8031
|
annotations: {
|
|
8014
8032
|
readOnlyHint: false,
|
|
@@ -8019,196 +8037,207 @@ function registerStorageTools(server) {
|
|
|
8019
8037
|
}
|
|
8020
8038
|
}, async (args) => {
|
|
8021
8039
|
const input = args;
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
if (
|
|
8033
|
-
|
|
8040
|
+
try {
|
|
8041
|
+
const manager = await getManager();
|
|
8042
|
+
if (!manager) {
|
|
8043
|
+
throw new Error("Failed to initialize CloudBase manager. Please check your credentials and environment configuration.");
|
|
8044
|
+
}
|
|
8045
|
+
const storageService = manager.storage;
|
|
8046
|
+
switch (input.action) {
|
|
8047
|
+
case 'upload': {
|
|
8048
|
+
const localPath = input.localPath;
|
|
8049
|
+
const cloudPath = input.cloudPath;
|
|
8050
|
+
if (input.isDirectory) {
|
|
8051
|
+
if (storageOverrides?.uploadDirectory) {
|
|
8052
|
+
await storageOverrides.uploadDirectory({ localPath, cloudPath });
|
|
8053
|
+
}
|
|
8054
|
+
else {
|
|
8055
|
+
await storageService.uploadDirectory({
|
|
8056
|
+
localPath,
|
|
8057
|
+
cloudPath,
|
|
8058
|
+
onProgress: (progressData) => {
|
|
8059
|
+
console.log("Upload directory progress:", progressData);
|
|
8060
|
+
}
|
|
8061
|
+
});
|
|
8062
|
+
}
|
|
8034
8063
|
}
|
|
8035
8064
|
else {
|
|
8036
|
-
|
|
8037
|
-
localPath,
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8065
|
+
if (storageOverrides?.uploadFile) {
|
|
8066
|
+
await storageOverrides.uploadFile({ localPath, cloudPath });
|
|
8067
|
+
}
|
|
8068
|
+
else {
|
|
8069
|
+
await storageService.uploadFile({
|
|
8070
|
+
localPath,
|
|
8071
|
+
cloudPath,
|
|
8072
|
+
onProgress: (progressData) => {
|
|
8073
|
+
console.log("Upload file progress:", progressData);
|
|
8074
|
+
}
|
|
8075
|
+
});
|
|
8076
|
+
}
|
|
8043
8077
|
}
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8078
|
+
// 上传成功后获取临时 URL 和公网访问信息
|
|
8079
|
+
let temporaryUrl = "";
|
|
8080
|
+
let fileId = "";
|
|
8081
|
+
if (storageOverrides?.getFileUrl) {
|
|
8082
|
+
const urlResult = await storageOverrides.getFileUrl({
|
|
8083
|
+
cloudPath: input.cloudPath,
|
|
8084
|
+
maxAge: 3600,
|
|
8085
|
+
});
|
|
8086
|
+
temporaryUrl = urlResult.url;
|
|
8087
|
+
fileId = urlResult.fileId || "";
|
|
8048
8088
|
}
|
|
8049
8089
|
else {
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8090
|
+
try {
|
|
8091
|
+
const fileUrls = await storageService.getTemporaryUrl([{
|
|
8092
|
+
cloudPath: input.cloudPath,
|
|
8093
|
+
maxAge: 3600
|
|
8094
|
+
}]);
|
|
8095
|
+
temporaryUrl = fileUrls[0]?.url || "";
|
|
8096
|
+
fileId = fileUrls[0]?.fileId || "";
|
|
8097
|
+
}
|
|
8098
|
+
catch {
|
|
8099
|
+
// StorageOverrides 下 getTemporaryUrl 可能不可用
|
|
8100
|
+
}
|
|
8057
8101
|
}
|
|
8058
|
-
|
|
8059
|
-
// 上传成功后获取临时 URL 和公网访问信息
|
|
8060
|
-
let temporaryUrl = "";
|
|
8061
|
-
let fileId = "";
|
|
8062
|
-
if (storageOverrides?.getFileUrl) {
|
|
8063
|
-
const urlResult = await storageOverrides.getFileUrl({
|
|
8064
|
-
cloudPath: input.cloudPath,
|
|
8065
|
-
maxAge: 3600,
|
|
8066
|
-
});
|
|
8067
|
-
temporaryUrl = urlResult.url;
|
|
8068
|
-
fileId = urlResult.fileId || "";
|
|
8069
|
-
}
|
|
8070
|
-
else {
|
|
8102
|
+
let publicAccess = {};
|
|
8071
8103
|
try {
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
fileId = fileUrls[0]?.fileId || "";
|
|
8104
|
+
publicAccess = await resolveStoragePublicAccess({
|
|
8105
|
+
cloudPath: input.cloudPath,
|
|
8106
|
+
cloudBaseOptions,
|
|
8107
|
+
manager,
|
|
8108
|
+
});
|
|
8078
8109
|
}
|
|
8079
8110
|
catch {
|
|
8080
|
-
//
|
|
8111
|
+
// DescribeEnvs 可能不支持(IDE 模式下会绕过)
|
|
8112
|
+
publicAccess = {};
|
|
8081
8113
|
}
|
|
8114
|
+
return {
|
|
8115
|
+
content: [
|
|
8116
|
+
{
|
|
8117
|
+
type: "text",
|
|
8118
|
+
text: JSON.stringify({
|
|
8119
|
+
success: true,
|
|
8120
|
+
data: {
|
|
8121
|
+
action: 'upload',
|
|
8122
|
+
localPath: input.localPath,
|
|
8123
|
+
cloudPath: input.cloudPath,
|
|
8124
|
+
isDirectory: input.isDirectory,
|
|
8125
|
+
temporaryUrl,
|
|
8126
|
+
expireTime: "1小时",
|
|
8127
|
+
storageCdnDomain: publicAccess.storageCdnDomain,
|
|
8128
|
+
publicUrl: publicAccess.publicUrl,
|
|
8129
|
+
note: "temporaryUrl 是临时签名链接,1小时后过期,不要当作永久公网地址写入配置或持久化存储。publicUrl 基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导,⚠️ 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先将目标路径设置为公有读。"
|
|
8130
|
+
},
|
|
8131
|
+
message: `Successfully uploaded ${input.isDirectory ? 'directory' : 'file'} from '${input.localPath}' to '${input.cloudPath}'`
|
|
8132
|
+
}, null, 2)
|
|
8133
|
+
}
|
|
8134
|
+
]
|
|
8135
|
+
};
|
|
8082
8136
|
}
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
return {
|
|
8096
|
-
content: [
|
|
8097
|
-
{
|
|
8098
|
-
type: "text",
|
|
8099
|
-
text: JSON.stringify({
|
|
8100
|
-
success: true,
|
|
8101
|
-
data: {
|
|
8102
|
-
action: 'upload',
|
|
8103
|
-
localPath: input.localPath,
|
|
8104
|
-
cloudPath: input.cloudPath,
|
|
8105
|
-
isDirectory: input.isDirectory,
|
|
8106
|
-
temporaryUrl,
|
|
8107
|
-
expireTime: "1小时",
|
|
8108
|
-
storageCdnDomain: publicAccess.storageCdnDomain,
|
|
8109
|
-
publicUrl: publicAccess.publicUrl,
|
|
8110
|
-
note: "temporaryUrl 是临时签名链接,1小时后过期,不要当作永久公网地址写入配置或持久化存储。publicUrl 基于 DescribeEnvs 返回的 Storages[0].CdnDomain 推导,⚠️ 仅在存储桶 ACL 为公有读(所有用户可读)时才能被匿名访问;默认私有读写存储桶返回的 publicUrl 会 403,此时请继续使用 temporaryUrl 或先将目标路径设置为公有读。"
|
|
8111
|
-
},
|
|
8112
|
-
message: `Successfully uploaded ${input.isDirectory ? 'directory' : 'file'} from '${input.localPath}' to '${input.cloudPath}'`
|
|
8113
|
-
}, null, 2)
|
|
8137
|
+
case 'download': {
|
|
8138
|
+
const dlCloudPath = input.cloudPath;
|
|
8139
|
+
const dlLocalPath = input.localPath;
|
|
8140
|
+
if (input.isDirectory) {
|
|
8141
|
+
if (storageOverrides?.downloadDirectory) {
|
|
8142
|
+
await storageOverrides.downloadDirectory({ cloudPath: dlCloudPath, localPath: dlLocalPath });
|
|
8143
|
+
}
|
|
8144
|
+
else {
|
|
8145
|
+
await storageService.downloadDirectory({
|
|
8146
|
+
cloudPath: dlCloudPath,
|
|
8147
|
+
localPath: dlLocalPath
|
|
8148
|
+
});
|
|
8114
8149
|
}
|
|
8115
|
-
]
|
|
8116
|
-
};
|
|
8117
|
-
}
|
|
8118
|
-
case 'download': {
|
|
8119
|
-
const dlCloudPath = input.cloudPath;
|
|
8120
|
-
const dlLocalPath = input.localPath;
|
|
8121
|
-
if (input.isDirectory) {
|
|
8122
|
-
if (storageOverrides?.downloadDirectory) {
|
|
8123
|
-
await storageOverrides.downloadDirectory({ cloudPath: dlCloudPath, localPath: dlLocalPath });
|
|
8124
|
-
}
|
|
8125
|
-
else {
|
|
8126
|
-
await storageService.downloadDirectory({
|
|
8127
|
-
cloudPath: dlCloudPath,
|
|
8128
|
-
localPath: dlLocalPath
|
|
8129
|
-
});
|
|
8130
|
-
}
|
|
8131
|
-
}
|
|
8132
|
-
else {
|
|
8133
|
-
if (storageOverrides?.downloadFile) {
|
|
8134
|
-
await storageOverrides.downloadFile({ cloudPath: dlCloudPath, localPath: dlLocalPath });
|
|
8135
8150
|
}
|
|
8136
8151
|
else {
|
|
8137
|
-
|
|
8138
|
-
cloudPath: dlCloudPath,
|
|
8139
|
-
localPath: dlLocalPath
|
|
8140
|
-
});
|
|
8141
|
-
}
|
|
8142
|
-
}
|
|
8143
|
-
return {
|
|
8144
|
-
content: [
|
|
8145
|
-
{
|
|
8146
|
-
type: "text",
|
|
8147
|
-
text: JSON.stringify({
|
|
8148
|
-
success: true,
|
|
8149
|
-
data: {
|
|
8150
|
-
action: 'download',
|
|
8151
|
-
cloudPath: input.cloudPath,
|
|
8152
|
-
localPath: input.localPath,
|
|
8153
|
-
isDirectory: input.isDirectory
|
|
8154
|
-
},
|
|
8155
|
-
message: `Successfully downloaded ${input.isDirectory ? 'directory' : 'file'} from '${input.cloudPath}' to '${input.localPath}'`
|
|
8156
|
-
}, null, 2)
|
|
8152
|
+
if (storageOverrides?.downloadFile) {
|
|
8153
|
+
await storageOverrides.downloadFile({ cloudPath: dlCloudPath, localPath: dlLocalPath });
|
|
8157
8154
|
}
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8155
|
+
else {
|
|
8156
|
+
await storageService.downloadFile({
|
|
8157
|
+
cloudPath: dlCloudPath,
|
|
8158
|
+
localPath: dlLocalPath
|
|
8159
|
+
});
|
|
8160
|
+
}
|
|
8161
|
+
}
|
|
8163
8162
|
return {
|
|
8164
8163
|
content: [
|
|
8165
8164
|
{
|
|
8166
8165
|
type: "text",
|
|
8167
8166
|
text: JSON.stringify({
|
|
8168
|
-
success:
|
|
8169
|
-
|
|
8170
|
-
|
|
8167
|
+
success: true,
|
|
8168
|
+
data: {
|
|
8169
|
+
action: 'download',
|
|
8170
|
+
cloudPath: input.cloudPath,
|
|
8171
|
+
localPath: input.localPath,
|
|
8172
|
+
isDirectory: input.isDirectory
|
|
8173
|
+
},
|
|
8174
|
+
message: `Successfully downloaded ${input.isDirectory ? 'directory' : 'file'} from '${input.cloudPath}' to '${input.localPath}'`
|
|
8171
8175
|
}, null, 2)
|
|
8172
8176
|
}
|
|
8173
8177
|
]
|
|
8174
8178
|
};
|
|
8175
8179
|
}
|
|
8176
|
-
|
|
8177
|
-
if (
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8180
|
+
case 'delete': {
|
|
8181
|
+
if (!input.force) {
|
|
8182
|
+
return {
|
|
8183
|
+
content: [
|
|
8184
|
+
{
|
|
8185
|
+
type: "text",
|
|
8186
|
+
text: JSON.stringify({
|
|
8187
|
+
success: false,
|
|
8188
|
+
error: "Delete operation requires confirmation",
|
|
8189
|
+
message: "Please set force: true to confirm deletion. This action cannot be undone."
|
|
8190
|
+
}, null, 2)
|
|
8191
|
+
}
|
|
8192
|
+
]
|
|
8193
|
+
};
|
|
8182
8194
|
}
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8195
|
+
if (input.isDirectory) {
|
|
8196
|
+
if (storageOverrides?.deleteDirectory) {
|
|
8197
|
+
await storageOverrides.deleteDirectory({ cloudPath: input.cloudPath });
|
|
8198
|
+
}
|
|
8199
|
+
else {
|
|
8200
|
+
await storageService.deleteDirectory(input.cloudPath);
|
|
8201
|
+
}
|
|
8187
8202
|
}
|
|
8188
8203
|
else {
|
|
8189
|
-
|
|
8204
|
+
if (storageOverrides?.deleteFiles) {
|
|
8205
|
+
await storageOverrides.deleteFiles({ cloudPaths: [input.cloudPath] });
|
|
8206
|
+
}
|
|
8207
|
+
else {
|
|
8208
|
+
await storageService.deleteFile([input.cloudPath]);
|
|
8209
|
+
}
|
|
8190
8210
|
}
|
|
8211
|
+
return {
|
|
8212
|
+
content: [
|
|
8213
|
+
{
|
|
8214
|
+
type: "text",
|
|
8215
|
+
text: JSON.stringify({
|
|
8216
|
+
success: true,
|
|
8217
|
+
data: {
|
|
8218
|
+
action: 'delete',
|
|
8219
|
+
cloudPath: input.cloudPath,
|
|
8220
|
+
isDirectory: input.isDirectory,
|
|
8221
|
+
deleted: true
|
|
8222
|
+
},
|
|
8223
|
+
message: `Successfully deleted ${input.isDirectory ? 'directory' : 'file'} '${input.cloudPath}'`
|
|
8224
|
+
}, null, 2)
|
|
8225
|
+
}
|
|
8226
|
+
]
|
|
8227
|
+
};
|
|
8191
8228
|
}
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
{
|
|
8195
|
-
type: "text",
|
|
8196
|
-
text: JSON.stringify({
|
|
8197
|
-
success: true,
|
|
8198
|
-
data: {
|
|
8199
|
-
action: 'delete',
|
|
8200
|
-
cloudPath: input.cloudPath,
|
|
8201
|
-
isDirectory: input.isDirectory,
|
|
8202
|
-
deleted: true
|
|
8203
|
-
},
|
|
8204
|
-
message: `Successfully deleted ${input.isDirectory ? 'directory' : 'file'} '${input.cloudPath}'`
|
|
8205
|
-
}, null, 2)
|
|
8206
|
-
}
|
|
8207
|
-
]
|
|
8208
|
-
};
|
|
8229
|
+
default:
|
|
8230
|
+
throw new Error(`Unsupported action: ${input.action}`);
|
|
8209
8231
|
}
|
|
8210
|
-
|
|
8211
|
-
|
|
8232
|
+
}
|
|
8233
|
+
catch (error) {
|
|
8234
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8235
|
+
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
8236
|
+
success: false,
|
|
8237
|
+
message: `manageStorage 操作失败: ${message}`,
|
|
8238
|
+
action: input.action,
|
|
8239
|
+
cloudPath: input.cloudPath,
|
|
8240
|
+
});
|
|
8212
8241
|
}
|
|
8213
8242
|
});
|
|
8214
8243
|
}
|
|
@@ -9908,7 +9937,7 @@ class TelemetryReporter {
|
|
|
9908
9937
|
const nodeVersion = process.version; // Node.js版本
|
|
9909
9938
|
const arch = os_1.default.arch(); // 系统架构
|
|
9910
9939
|
// 从构建时注入的版本号获取MCP版本信息
|
|
9911
|
-
const mcpVersion = process.env.npm_package_version || "2.23.
|
|
9940
|
+
const mcpVersion = process.env.npm_package_version || "2.23.11" || 0;
|
|
9912
9941
|
return {
|
|
9913
9942
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
9914
9943
|
deviceId: this.deviceId,
|
|
@@ -11605,6 +11634,7 @@ const MANAGE_ACTIONS = [
|
|
|
11605
11634
|
"listMigrations",
|
|
11606
11635
|
"migrationDetail",
|
|
11607
11636
|
"rollbackMigration",
|
|
11637
|
+
"repairMigration",
|
|
11608
11638
|
];
|
|
11609
11639
|
function buildPgToolResult(payload) {
|
|
11610
11640
|
return (0, tool_result_js_1.buildJsonToolResult)(payload);
|
|
@@ -12112,6 +12142,12 @@ async function executeManagerPGSql(context, sql, cloudBaseOptions) {
|
|
|
12112
12142
|
}
|
|
12113
12143
|
throw new Error("Current @cloudbase/manager-node runtime does not expose database.executePGSql or commonService fallback. Upgrade to @cloudbase/manager-node >= 5.4.0.");
|
|
12114
12144
|
}
|
|
12145
|
+
/** Generate a 14-digit migration version (YYYYMMDDHHMMSS) from current time */
|
|
12146
|
+
function generateMigrationVersion() {
|
|
12147
|
+
const d = new Date();
|
|
12148
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
12149
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
12150
|
+
}
|
|
12115
12151
|
/** Call a CloudBase PG migration API via commonService fallback */
|
|
12116
12152
|
async function callPgMigrationApi(context, action, params, cloudBaseOptions) {
|
|
12117
12153
|
const manager = await (0, cloudbase_manager_js_1.getCloudBaseManager)({
|
|
@@ -12436,16 +12472,32 @@ async function handlePlanMigration(args, context, deps, cloudBaseOptions) {
|
|
|
12436
12472
|
message: "Provide migration SQL when action=planMigration.",
|
|
12437
12473
|
});
|
|
12438
12474
|
}
|
|
12475
|
+
if (!args.migrationName?.trim()) {
|
|
12476
|
+
return buildPgToolResult({
|
|
12477
|
+
success: false,
|
|
12478
|
+
errorCode: "MIGRATION_NAME_REQUIRED",
|
|
12479
|
+
message: "Provide migrationName (lowercase letters, digits and underscores, starting with a letter) when action=planMigration.",
|
|
12480
|
+
});
|
|
12481
|
+
}
|
|
12482
|
+
const version = args.migrationVersion?.trim() || generateMigrationVersion();
|
|
12483
|
+
const migration = {
|
|
12484
|
+
Version: version,
|
|
12485
|
+
Name: args.migrationName,
|
|
12486
|
+
Query: args.sql,
|
|
12487
|
+
};
|
|
12488
|
+
if (args.rollbackSql?.trim()) {
|
|
12489
|
+
migration.Rollback = args.rollbackSql;
|
|
12490
|
+
}
|
|
12439
12491
|
try {
|
|
12440
12492
|
const result = await callPgMigrationApi(context, "PreviewPGUserMigrations", {
|
|
12441
|
-
|
|
12493
|
+
Migrations: [migration],
|
|
12442
12494
|
}, cloudBaseOptions);
|
|
12443
12495
|
return buildPgToolResult({
|
|
12444
12496
|
success: true,
|
|
12445
12497
|
data: result,
|
|
12446
12498
|
message: "Migration plan generated via PreviewPGUserMigrations.",
|
|
12447
12499
|
nextActions: [
|
|
12448
|
-
buildNextAction(MANAGE_PG_DATABASE, "applyMigration", "Review the plan above. If it looks correct, call applyMigration to execute.", { action: "applyMigration", sql: args.sql, confirm: true }),
|
|
12500
|
+
buildNextAction(MANAGE_PG_DATABASE, "applyMigration", "Review the plan above. If it looks correct, call applyMigration to execute.", { action: "applyMigration", migrationName: args.migrationName, sql: args.sql, confirm: true, ...(args.rollbackSql ? { rollbackSql: args.rollbackSql } : {}) }),
|
|
12449
12501
|
],
|
|
12450
12502
|
});
|
|
12451
12503
|
}
|
|
@@ -12465,6 +12517,13 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
|
|
|
12465
12517
|
message: "Provide migration SQL when action=applyMigration.",
|
|
12466
12518
|
});
|
|
12467
12519
|
}
|
|
12520
|
+
if (!args.migrationName?.trim()) {
|
|
12521
|
+
return buildPgToolResult({
|
|
12522
|
+
success: false,
|
|
12523
|
+
errorCode: "MIGRATION_NAME_REQUIRED",
|
|
12524
|
+
message: "Provide migrationName (lowercase letters, digits and underscores, starting with a letter) when action=applyMigration.",
|
|
12525
|
+
});
|
|
12526
|
+
}
|
|
12468
12527
|
if (args.confirm !== true) {
|
|
12469
12528
|
return buildPgToolResult({
|
|
12470
12529
|
success: false,
|
|
@@ -12472,10 +12531,24 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
|
|
|
12472
12531
|
message: "PushPGUserMigrations requires confirm=true. Run with confirm=true to proceed.",
|
|
12473
12532
|
});
|
|
12474
12533
|
}
|
|
12534
|
+
const version = args.migrationVersion?.trim() || generateMigrationVersion();
|
|
12535
|
+
const migration = {
|
|
12536
|
+
Version: version,
|
|
12537
|
+
Name: args.migrationName,
|
|
12538
|
+
Query: args.sql,
|
|
12539
|
+
};
|
|
12540
|
+
if (args.rollbackSql?.trim()) {
|
|
12541
|
+
migration.Rollback = args.rollbackSql;
|
|
12542
|
+
}
|
|
12543
|
+
const params = { Migrations: [migration] };
|
|
12544
|
+
if (args.lockTimeoutMs !== undefined) {
|
|
12545
|
+
params.LockTimeoutMs = args.lockTimeoutMs;
|
|
12546
|
+
}
|
|
12547
|
+
if (args.statementTimeoutMs !== undefined) {
|
|
12548
|
+
params.StatementTimeoutMs = args.statementTimeoutMs;
|
|
12549
|
+
}
|
|
12475
12550
|
try {
|
|
12476
|
-
const result = await callPgMigrationApi(context, "PushPGUserMigrations",
|
|
12477
|
-
Sql: args.sql,
|
|
12478
|
-
}, cloudBaseOptions);
|
|
12551
|
+
const result = await callPgMigrationApi(context, "PushPGUserMigrations", params, cloudBaseOptions);
|
|
12479
12552
|
return buildPgToolResult({
|
|
12480
12553
|
success: true,
|
|
12481
12554
|
data: result,
|
|
@@ -12491,8 +12564,15 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
|
|
|
12491
12564
|
}
|
|
12492
12565
|
}
|
|
12493
12566
|
async function handleListMigrations(args, context, deps, cloudBaseOptions) {
|
|
12567
|
+
const params = {};
|
|
12568
|
+
if (args.limit !== undefined) {
|
|
12569
|
+
params.Limit = args.limit;
|
|
12570
|
+
}
|
|
12571
|
+
if (args.offset !== undefined) {
|
|
12572
|
+
params.Offset = args.offset;
|
|
12573
|
+
}
|
|
12494
12574
|
try {
|
|
12495
|
-
const result = await callPgMigrationApi(context, "ListPGUserMigrations",
|
|
12575
|
+
const result = await callPgMigrationApi(context, "ListPGUserMigrations", params, cloudBaseOptions);
|
|
12496
12576
|
return buildPgToolResult({
|
|
12497
12577
|
success: true,
|
|
12498
12578
|
data: result,
|
|
@@ -12508,16 +12588,16 @@ async function handleListMigrations(args, context, deps, cloudBaseOptions) {
|
|
|
12508
12588
|
}
|
|
12509
12589
|
}
|
|
12510
12590
|
async function handleMigrationDetail(args, context, deps, cloudBaseOptions) {
|
|
12511
|
-
if (!args.
|
|
12591
|
+
if (!args.migrationVersion?.trim()) {
|
|
12512
12592
|
return buildPgToolResult({
|
|
12513
12593
|
success: false,
|
|
12514
|
-
errorCode: "
|
|
12515
|
-
message: "Provide
|
|
12594
|
+
errorCode: "MIGRATION_VERSION_REQUIRED",
|
|
12595
|
+
message: "Provide migrationVersion (14-digit timestamp YYYYMMDDHHMMSS) when action=migrationDetail.",
|
|
12516
12596
|
});
|
|
12517
12597
|
}
|
|
12518
12598
|
try {
|
|
12519
12599
|
const result = await callPgMigrationApi(context, "DescribePGUserMigration", {
|
|
12520
|
-
|
|
12600
|
+
MigrationVersion: args.migrationVersion,
|
|
12521
12601
|
}, cloudBaseOptions);
|
|
12522
12602
|
return buildPgToolResult({
|
|
12523
12603
|
success: true,
|
|
@@ -12534,11 +12614,11 @@ async function handleMigrationDetail(args, context, deps, cloudBaseOptions) {
|
|
|
12534
12614
|
}
|
|
12535
12615
|
}
|
|
12536
12616
|
async function handleRollbackMigration(args, context, deps, cloudBaseOptions) {
|
|
12537
|
-
if (!args.
|
|
12617
|
+
if (args.lastN === undefined || args.lastN < 1 || !Number.isInteger(args.lastN)) {
|
|
12538
12618
|
return buildPgToolResult({
|
|
12539
12619
|
success: false,
|
|
12540
|
-
errorCode: "
|
|
12541
|
-
message: "Provide
|
|
12620
|
+
errorCode: "LAST_N_REQUIRED",
|
|
12621
|
+
message: "Provide lastN (positive integer) when action=rollbackMigration.",
|
|
12542
12622
|
});
|
|
12543
12623
|
}
|
|
12544
12624
|
if (args.confirm !== true) {
|
|
@@ -12550,7 +12630,7 @@ async function handleRollbackMigration(args, context, deps, cloudBaseOptions) {
|
|
|
12550
12630
|
}
|
|
12551
12631
|
try {
|
|
12552
12632
|
const result = await callPgMigrationApi(context, "RollbackPGUserMigrations", {
|
|
12553
|
-
|
|
12633
|
+
LastN: args.lastN,
|
|
12554
12634
|
}, cloudBaseOptions);
|
|
12555
12635
|
return buildPgToolResult({
|
|
12556
12636
|
success: true,
|
|
@@ -12566,6 +12646,67 @@ async function handleRollbackMigration(args, context, deps, cloudBaseOptions) {
|
|
|
12566
12646
|
});
|
|
12567
12647
|
}
|
|
12568
12648
|
}
|
|
12649
|
+
async function handleRepairMigration(args, context, deps, cloudBaseOptions) {
|
|
12650
|
+
if (!args.migrationVersion?.trim()) {
|
|
12651
|
+
return buildPgToolResult({
|
|
12652
|
+
success: false,
|
|
12653
|
+
errorCode: "MIGRATION_VERSION_REQUIRED",
|
|
12654
|
+
message: "Provide migrationVersion (14-digit timestamp YYYYMMDDHHMMSS) when action=repairMigration.",
|
|
12655
|
+
});
|
|
12656
|
+
}
|
|
12657
|
+
if (!args.migrationName?.trim()) {
|
|
12658
|
+
return buildPgToolResult({
|
|
12659
|
+
success: false,
|
|
12660
|
+
errorCode: "MIGRATION_NAME_REQUIRED",
|
|
12661
|
+
message: "Provide migrationName when action=repairMigration.",
|
|
12662
|
+
});
|
|
12663
|
+
}
|
|
12664
|
+
if (!args.repairStatus) {
|
|
12665
|
+
return buildPgToolResult({
|
|
12666
|
+
success: false,
|
|
12667
|
+
errorCode: "REPAIR_STATUS_REQUIRED",
|
|
12668
|
+
message: "Provide repairStatus (applied or reverted) when action=repairMigration.",
|
|
12669
|
+
});
|
|
12670
|
+
}
|
|
12671
|
+
if (!args.repairReason?.trim()) {
|
|
12672
|
+
return buildPgToolResult({
|
|
12673
|
+
success: false,
|
|
12674
|
+
errorCode: "REPAIR_REASON_REQUIRED",
|
|
12675
|
+
message: "Provide repairReason when action=repairMigration.",
|
|
12676
|
+
});
|
|
12677
|
+
}
|
|
12678
|
+
if (args.repairStatus === "applied" && !args.sql?.trim()) {
|
|
12679
|
+
return buildPgToolResult({
|
|
12680
|
+
success: false,
|
|
12681
|
+
errorCode: "SQL_REQUIRED",
|
|
12682
|
+
message: "Provide sql (Query) when action=repairMigration with repairStatus=applied.",
|
|
12683
|
+
});
|
|
12684
|
+
}
|
|
12685
|
+
const params = {
|
|
12686
|
+
MigrationVersion: args.migrationVersion,
|
|
12687
|
+
Name: args.migrationName,
|
|
12688
|
+
Status: args.repairStatus,
|
|
12689
|
+
Reason: args.repairReason,
|
|
12690
|
+
};
|
|
12691
|
+
if (args.repairStatus === "applied" && args.sql?.trim()) {
|
|
12692
|
+
params.Query = args.sql;
|
|
12693
|
+
}
|
|
12694
|
+
try {
|
|
12695
|
+
const result = await callPgMigrationApi(context, "RepairPGUserMigrationHistory", params, cloudBaseOptions);
|
|
12696
|
+
return buildPgToolResult({
|
|
12697
|
+
success: true,
|
|
12698
|
+
data: result,
|
|
12699
|
+
message: "Migration history repaired via RepairPGUserMigrationHistory.",
|
|
12700
|
+
});
|
|
12701
|
+
}
|
|
12702
|
+
catch (error) {
|
|
12703
|
+
return buildPgToolResult({
|
|
12704
|
+
success: false,
|
|
12705
|
+
errorCode: "MIGRATION_API_ERROR",
|
|
12706
|
+
message: `RepairPGUserMigrationHistory failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
12707
|
+
});
|
|
12708
|
+
}
|
|
12709
|
+
}
|
|
12569
12710
|
async function handleGetPgSchema(args, context, deps) {
|
|
12570
12711
|
const objectName = args.objectName ?? "";
|
|
12571
12712
|
const parsed = parseSchemaQualifiedName(objectName);
|
|
@@ -12686,15 +12827,15 @@ function registerPGDatabaseTools(server, providedDeps) {
|
|
|
12686
12827
|
});
|
|
12687
12828
|
server.registerTool?.(MANAGE_PG_DATABASE, {
|
|
12688
12829
|
title: "管理 PostgreSQL 上下文或执行写入 SQL",
|
|
12689
|
-
description: "管理 CloudBase PostgreSQL:执行已确认的写入 SQL 或 DDL、SQL
|
|
12830
|
+
description: "管理 CloudBase PostgreSQL:执行已确认的写入 SQL 或 DDL、SQL 风险预检、迁移管理。建表、改 schema、数据结构变更优先使用 applyMigration(带版本管理与回滚),而非 execute。",
|
|
12690
12831
|
inputSchema: {
|
|
12691
12832
|
action: zod_1.z
|
|
12692
12833
|
.enum(MANAGE_ACTIONS)
|
|
12693
|
-
.describe("操作类型:execute=执行已确认的写入 SQL 或 DDL;dryRun=只分析 SQL 风险不执行;planMigration
|
|
12834
|
+
.describe("操作类型:execute=执行已确认的写入 SQL 或 DDL;dryRun=只分析 SQL 风险不执行;planMigration=预览迁移计划(需 migrationName + sql);applyMigration=应用迁移,建表/改 schema 首选(需 migrationName + sql + confirm=true);listMigrations=查询已应用的 Migration 列表(可传 limit/offset 分页);migrationDetail=查看单条 Migration 详情(需 migrationVersion);rollbackMigration=回滚最近 N 条 Migration(需 lastN + confirm=true);repairMigration=修复 Migration 历史记录(需 migrationVersion + migrationName + repairStatus + repairReason)"),
|
|
12694
12835
|
sql: zod_1.z
|
|
12695
12836
|
.string()
|
|
12696
12837
|
.optional()
|
|
12697
|
-
.describe("action=execute、dryRun、planMigration 或
|
|
12838
|
+
.describe("action=execute、dryRun、planMigration、applyMigration 或 repairMigration(applied) 使用的 SQL 语句"),
|
|
12698
12839
|
confirm: zod_1.z
|
|
12699
12840
|
.boolean()
|
|
12700
12841
|
.optional()
|
|
@@ -12718,7 +12859,58 @@ function registerPGDatabaseTools(server, providedDeps) {
|
|
|
12718
12859
|
objectName: zod_1.z
|
|
12719
12860
|
.string()
|
|
12720
12861
|
.optional()
|
|
12721
|
-
.describe("
|
|
12862
|
+
.describe("可选的对象名,当前仅用于非 migration 场景。migration 相关操作请使用 migrationName / migrationVersion / lastN。"),
|
|
12863
|
+
migrationName: zod_1.z
|
|
12864
|
+
.string()
|
|
12865
|
+
.regex(/^[a-z][a-z0-9_]*$/)
|
|
12866
|
+
.optional()
|
|
12867
|
+
.describe("plan/apply/repair 必填:migration 名称,小写字母开头,仅允许小写字母、数字和下划线。"),
|
|
12868
|
+
migrationVersion: zod_1.z
|
|
12869
|
+
.string()
|
|
12870
|
+
.regex(/^\d{14}$/)
|
|
12871
|
+
.optional()
|
|
12872
|
+
.describe("14 位时间戳 YYYYMMDDHHMMSS。detail/repair 必填;plan/apply 不传时自动生成。"),
|
|
12873
|
+
rollbackSql: zod_1.z
|
|
12874
|
+
.string()
|
|
12875
|
+
.optional()
|
|
12876
|
+
.describe("plan/apply 可选:回滚 SQL 语句。"),
|
|
12877
|
+
lastN: zod_1.z
|
|
12878
|
+
.number()
|
|
12879
|
+
.int()
|
|
12880
|
+
.positive()
|
|
12881
|
+
.optional()
|
|
12882
|
+
.describe("rollback 必填:回滚最近 N 条已应用的 Migration,正整数。"),
|
|
12883
|
+
limit: zod_1.z
|
|
12884
|
+
.number()
|
|
12885
|
+
.int()
|
|
12886
|
+
.min(1)
|
|
12887
|
+
.max(500)
|
|
12888
|
+
.optional()
|
|
12889
|
+
.describe("list 可选:返回数量上限,1-500,默认 100。"),
|
|
12890
|
+
offset: zod_1.z
|
|
12891
|
+
.number()
|
|
12892
|
+
.int()
|
|
12893
|
+
.min(0)
|
|
12894
|
+
.optional()
|
|
12895
|
+
.describe("list 可选:分页偏移,默认 0。"),
|
|
12896
|
+
lockTimeoutMs: zod_1.z
|
|
12897
|
+
.number()
|
|
12898
|
+
.int()
|
|
12899
|
+
.optional()
|
|
12900
|
+
.describe("apply 可选:获取数据库锁的最长时间(毫秒),默认 5000。"),
|
|
12901
|
+
statementTimeoutMs: zod_1.z
|
|
12902
|
+
.number()
|
|
12903
|
+
.int()
|
|
12904
|
+
.optional()
|
|
12905
|
+
.describe("apply 可选:单条 SQL 执行最长时间(毫秒),默认 300000。"),
|
|
12906
|
+
repairStatus: zod_1.z
|
|
12907
|
+
.enum(["applied", "reverted"])
|
|
12908
|
+
.optional()
|
|
12909
|
+
.describe("repair 必填:applied=标记为已应用(可补录 Query),reverted=删除 history 记录。"),
|
|
12910
|
+
repairReason: zod_1.z
|
|
12911
|
+
.string()
|
|
12912
|
+
.optional()
|
|
12913
|
+
.describe("repair 必填:修复原因。"),
|
|
12722
12914
|
},
|
|
12723
12915
|
annotations: {
|
|
12724
12916
|
readOnlyHint: false,
|
|
@@ -12754,13 +12946,15 @@ function registerPGDatabaseTools(server, providedDeps) {
|
|
|
12754
12946
|
case "applyMigration":
|
|
12755
12947
|
case "listMigrations":
|
|
12756
12948
|
case "migrationDetail":
|
|
12757
|
-
case "rollbackMigration":
|
|
12949
|
+
case "rollbackMigration":
|
|
12950
|
+
case "repairMigration": {
|
|
12758
12951
|
switch (args.action) {
|
|
12759
12952
|
case "planMigration": return handlePlanMigration(args, context, deps, cbOpts);
|
|
12760
12953
|
case "applyMigration": return handleApplyMigration(args, context, deps, cbOpts);
|
|
12761
12954
|
case "listMigrations": return handleListMigrations(args, context, deps, cbOpts);
|
|
12762
12955
|
case "migrationDetail": return handleMigrationDetail(args, context, deps, cbOpts);
|
|
12763
12956
|
case "rollbackMigration": return handleRollbackMigration(args, context, deps, cbOpts);
|
|
12957
|
+
case "repairMigration": return handleRepairMigration(args, context, deps, cbOpts);
|
|
12764
12958
|
default: return buildPgToolResult({ success: false, errorCode: "UNSUPPORTED_ACTION", message: `Unsupported action: ${args.action}` });
|
|
12765
12959
|
}
|
|
12766
12960
|
}
|
|
@@ -13472,7 +13666,7 @@ exports.IDE_TYPES = [
|
|
|
13472
13666
|
"openai-codex-cli", // OpenAI Codex CLI
|
|
13473
13667
|
"augment-code", // Augment Code
|
|
13474
13668
|
"github-copilot", // GitHub Copilot
|
|
13475
|
-
"roocode", // RooCode AI
|
|
13669
|
+
"roocode", // RooCode AI编辑器(已 deprecated 2026-05,可考虑引导转向 Cline/ZooCode)
|
|
13476
13670
|
"tongyi-lingma", // 通义灵码
|
|
13477
13671
|
"trae", // Trae AI编辑器
|
|
13478
13672
|
"qoder", // Qoder AI编辑器
|
|
@@ -13514,7 +13708,6 @@ exports.RAW_IDE_FILE_MAPPINGS = {
|
|
|
13514
13708
|
],
|
|
13515
13709
|
"baidu-comate": [
|
|
13516
13710
|
{ path: ".comate/rules/cloudbase-rules.mdr" },
|
|
13517
|
-
{ path: ".comate/rules/cloudbaase-rules.mdr" },
|
|
13518
13711
|
{ path: ".comate/mcp.json", isMcpConfig: true },
|
|
13519
13712
|
],
|
|
13520
13713
|
"openai-codex-cli": [
|
|
@@ -13524,10 +13717,10 @@ exports.RAW_IDE_FILE_MAPPINGS = {
|
|
|
13524
13717
|
"augment-code": [{ path: ".augment-guidelines" }],
|
|
13525
13718
|
"github-copilot": [{ path: ".github/copilot-instructions.md" }],
|
|
13526
13719
|
roocode: [
|
|
13527
|
-
{ path: ".roo/rules/
|
|
13720
|
+
{ path: ".roo/rules/cloudbase-rules.md" },
|
|
13528
13721
|
{ path: ".roo/mcp.json", isMcpConfig: true },
|
|
13529
13722
|
],
|
|
13530
|
-
"tongyi-lingma": [{ path: ".lingma/rules/
|
|
13723
|
+
"tongyi-lingma": [{ path: ".lingma/rules/cloudbase-rules.md" }],
|
|
13531
13724
|
trae: [{ path: ".trae/rules/" }],
|
|
13532
13725
|
qoder: [{ path: ".qoder/rules/" }],
|
|
13533
13726
|
antigravity: [{ path: ".agent/rules/" }],
|
|
@@ -13567,7 +13760,7 @@ const IDE_DESCRIPTIONS = {
|
|
|
13567
13760
|
"openai-codex-cli": "OpenAI Codex CLI",
|
|
13568
13761
|
"augment-code": "Augment Code",
|
|
13569
13762
|
"github-copilot": "GitHub Copilot",
|
|
13570
|
-
roocode: "RooCode AI
|
|
13763
|
+
roocode: "RooCode AI编辑器(deprecated)",
|
|
13571
13764
|
"tongyi-lingma": "通义灵码",
|
|
13572
13765
|
trae: "Trae AI编辑器",
|
|
13573
13766
|
qoder: "Qoder AI编辑器",
|
|
@@ -13862,7 +14055,7 @@ function registerSetupTools(server) {
|
|
|
13862
14055
|
server.registerTool("downloadTemplate", {
|
|
13863
14056
|
title: "下载项目模板",
|
|
13864
14057
|
description: `自动下载并部署CloudBase项目模板。
|
|
13865
|
-
**Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.23.
|
|
14058
|
+
**Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.23.11" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
|
|
13866
14059
|
inputSchema: {
|
|
13867
14060
|
template: zod_1.z
|
|
13868
14061
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|
|
@@ -16617,6 +16810,7 @@ const STORAGE_ACTIONS = [
|
|
|
16617
16810
|
"objectInfo",
|
|
16618
16811
|
"signUpload",
|
|
16619
16812
|
"signDownload",
|
|
16813
|
+
"createBucket",
|
|
16620
16814
|
];
|
|
16621
16815
|
function buildPgStorageResult(payload) {
|
|
16622
16816
|
return (0, tool_result_js_1.buildJsonToolResult)(payload);
|
|
@@ -16675,12 +16869,52 @@ async function handleBuckets(server) {
|
|
|
16675
16869
|
data: {
|
|
16676
16870
|
capability: "storageManagerSdkOrHttpApi",
|
|
16677
16871
|
envId: server.cloudBaseOptions?.envId ?? null,
|
|
16678
|
-
supportedActions: ["buckets", "config", "uploadPlan", "objectInfo"],
|
|
16872
|
+
supportedActions: ["buckets", "config", "uploadPlan", "objectInfo", "createBucket"],
|
|
16679
16873
|
note: "PG environment storage is exposed as metadata and implementation plans in MCP. Application data-plane upload/download should use SDK or HTTP API code.",
|
|
16680
16874
|
},
|
|
16681
16875
|
message: "Resolved PostgreSQL storage capability summary. Use uploadPlan for application-side upload implementation.",
|
|
16682
16876
|
});
|
|
16683
16877
|
}
|
|
16878
|
+
async function handleCreateBucket(args, server) {
|
|
16879
|
+
if (!args.bucket?.trim()) {
|
|
16880
|
+
return buildPgStorageResult({
|
|
16881
|
+
success: false,
|
|
16882
|
+
errorCode: "BUCKET_REQUIRED",
|
|
16883
|
+
message: "Provide bucket name to create a PG storage bucket.",
|
|
16884
|
+
});
|
|
16885
|
+
}
|
|
16886
|
+
const bucket = args.bucket.trim();
|
|
16887
|
+
const envId = server.cloudBaseOptions?.envId ?? "${envId}";
|
|
16888
|
+
return buildPgStorageResult({
|
|
16889
|
+
success: true,
|
|
16890
|
+
data: {
|
|
16891
|
+
action: "createBucket",
|
|
16892
|
+
bucket,
|
|
16893
|
+
description: `Creates PG storage bucket '${bucket}'`,
|
|
16894
|
+
plans: {
|
|
16895
|
+
sql: `INSERT INTO storage.buckets (id, name, public) VALUES ('${bucket}', '${bucket}', false);`,
|
|
16896
|
+
httpApi: {
|
|
16897
|
+
method: "POST",
|
|
16898
|
+
url: `https://${envId}.api.tcloudbasegateway.com/v1/storages/bucket/`,
|
|
16899
|
+
headers: {
|
|
16900
|
+
"Authorization": "Bearer {service_role_token}",
|
|
16901
|
+
"Content-Type": "application/json",
|
|
16902
|
+
},
|
|
16903
|
+
body: { name: bucket, public: false },
|
|
16904
|
+
},
|
|
16905
|
+
cli: `tcb db execute -e ${envId} --sql "INSERT INTO storage.buckets (id, name, public) VALUES ('${bucket}', '${bucket}', false);"`,
|
|
16906
|
+
},
|
|
16907
|
+
tokenRequired: "service_role",
|
|
16908
|
+
tokenGuide: "Use manageAppAuth(action='createApiKey', name='storage-server-key') to get a service_role level API key token.",
|
|
16909
|
+
nextSteps: [
|
|
16910
|
+
"Option A: Run the SQL via managePgDatabase(action='execute', confirm=true)",
|
|
16911
|
+
"Option B: Call the HTTP API with a service_role token",
|
|
16912
|
+
"Option C: Use CloudBase CLI: tcb db execute",
|
|
16913
|
+
],
|
|
16914
|
+
},
|
|
16915
|
+
message: `Generated bucket creation plan for '${bucket}'. Execute one of the provided plans to create the bucket, then configure RLS policies.`,
|
|
16916
|
+
});
|
|
16917
|
+
}
|
|
16684
16918
|
async function handleUploadPlan(args, server) {
|
|
16685
16919
|
const missingBucket = requireBucket(args);
|
|
16686
16920
|
if (missingBucket)
|
|
@@ -16751,7 +16985,7 @@ function registerPGStorageTools(server) {
|
|
|
16751
16985
|
inputSchema: {
|
|
16752
16986
|
action: zod_1.z
|
|
16753
16987
|
.enum(STORAGE_ACTIONS)
|
|
16754
|
-
.describe("操作类型:buckets/config=查询存储能力摘要;uploadPlan=生成 HTTP API/SDK 上传方案;objectInfo=生成对象元信息查询方案;signUpload/signDownload=显式的一次性签名 URL 请求占位"),
|
|
16988
|
+
.describe("操作类型:buckets/config=查询存储能力摘要;createBucket=生成 bucket 创建方案(SQL/HTTP API/CLI);uploadPlan=生成 HTTP API/SDK 上传方案;objectInfo=生成对象元信息查询方案;signUpload/signDownload=显式的一次性签名 URL 请求占位"),
|
|
16755
16989
|
bucket: zod_1.z.string().optional().describe("云存储 bucket 名称"),
|
|
16756
16990
|
objectKey: zod_1.z.string().optional().describe("单个对象 key"),
|
|
16757
16991
|
objectKeys: zod_1.z.array(zod_1.z.string()).optional().describe("多个对象 key,用于对象元信息查询规划"),
|
|
@@ -16775,6 +17009,8 @@ function registerPGStorageTools(server) {
|
|
|
16775
17009
|
case "buckets":
|
|
16776
17010
|
case "config":
|
|
16777
17011
|
return handleBuckets(server);
|
|
17012
|
+
case "createBucket":
|
|
17013
|
+
return handleCreateBucket(args, server);
|
|
16778
17014
|
case "uploadPlan":
|
|
16779
17015
|
return handleUploadPlan(args, server);
|
|
16780
17016
|
case "objectInfo":
|