From daa2bdfa34735f2b7cedeb42343476fc5b174438 Mon Sep 17 00:00:00 2001 From: 15312618259 <15312618259@users.noreply.github.com> Date: Wed, 9 Sep 2026 21:15:44 +0800 Subject: [PATCH] Fix Windows MCP spawn/DNS issues; add remote file rename support - mcp-server: resolve CLI JS entry via require.resolve instead of spawning the npm .cmd shim (fixes ENOENT on Windows) - mcp-server: launch CLI child processes with --dns-result-order=ipv4first (fixes HTTP 400 InvalidSessionKey 'check ip error' on dual-stack networks) - cli/remote: add renameRemoteFile via official open API renameFile.action - cli: new 'rename-file ' command with --json output - agent-safe: add 'rename-file' to DANGEROUS_COMMANDS - mcp-server: add guarded cloud189_rename_folder/_file/rm/mv tools (require confirm: true, preview via cloud189_plan) --- packages/cli/src/agent-safe.js | 3 +- packages/cli/src/cli.js | 15 +++++ packages/cli/src/remote.js | 15 +++++ packages/mcp/src/mcp-server.js | 101 ++++++++++++++++++++++++++++++++- 4 files changed, 132 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/agent-safe.js b/packages/cli/src/agent-safe.js index 64ac441..70c9754 100644 --- a/packages/cli/src/agent-safe.js +++ b/packages/cli/src/agent-safe.js @@ -32,7 +32,8 @@ const SAFE_COMMANDS = new Set([ const DANGEROUS_COMMANDS = new Set([ 'rm', 'mv', - 'rename-folder' + 'rename-folder', + 'rename-file' ]); function defaultAgentConfig() { diff --git a/packages/cli/src/cli.js b/packages/cli/src/cli.js index f110025..5d97a71 100644 --- a/packages/cli/src/cli.js +++ b/packages/cli/src/cli.js @@ -20,6 +20,7 @@ const { listAll, moveRemoteItem, PERSONAL_ROOT_FOLDER_ID, + renameRemoteFile, renameRemoteFolder, searchRemoteEntries } = require('./remote'); @@ -496,6 +497,20 @@ async function main(argv = process.argv.slice(2)) { return; } + // Custom patch: rename a remote FILE by id. + if (parsed.command === 'rename-file') { + const remoteFileId = requireArg(parsed.args[0], 'remoteFileId'); + const newName = requireArg(parsed.args[1], 'newName'); + const client = createClient(); + await renameRemoteFile(client, remoteFileId, newName); + if (wantsJson) { + writeJsonOutput({ ok: true, fileId: String(remoteFileId), newName }); + } else { + console.log(`renamed file ${remoteFileId} ${newName}`); + } + return; + } + if (parsed.command === 'quota') { const client = createClient(); const info = await client.getUserSizeInfo(); diff --git a/packages/cli/src/remote.js b/packages/cli/src/remote.js index 75cd304..8af5c9d 100644 --- a/packages/cli/src/remote.js +++ b/packages/cli/src/remote.js @@ -55,6 +55,20 @@ async function renameRemoteFolder(client, folderId, folderName) { return client.renameFolder({ folderId, folderName }); } +// Custom patch: rename a FILE (the SDK only wraps renameFolder). +// Uses the official open API endpoint directly. +async function renameRemoteFile(client, fileId, fileName) { + const { API_URL } = require('cloud189-sdk/dist/const'); + return client.request + .post(`${API_URL}/open/file/renameFile.action`, { + form: { + destFileName: fileName, + fileId: String(fileId) + } + }) + .json(); +} + async function runBatchTask(client, type, taskInfos, options = {}) { const result = await client.createBatchTask({ type, @@ -194,6 +208,7 @@ module.exports = { moveRemoteItem, listAll, PERSONAL_ROOT_FOLDER_ID, + renameRemoteFile, renameRemoteFolder, runBatchTask, resolveFolderId, diff --git a/packages/mcp/src/mcp-server.js b/packages/mcp/src/mcp-server.js index ab2da7b..04d301d 100755 --- a/packages/mcp/src/mcp-server.js +++ b/packages/mcp/src/mcp-server.js @@ -9,11 +9,25 @@ const z = require('zod'); const PACKAGE_VERSION = require(path.join(__dirname, '..', 'package.json')).version; const PERSONAL_ROOT_FOLDER_ID = '-11'; +// Windows compatibility patch: resolve the cloud189 CLI as a JS entry executed by +// the current Node binary, because spawn('cloud189') cannot resolve the .cmd shim. +const CLOUD189_CLI_JS = path.join( + path.dirname(require.resolve('@codesentryai/cloud189/package.json')), + 'bin', + 'cloud189.js' +); + // --- helpers ---------------------------------------------------------------- +// Custom patch: Tianyi Cloud binds the sessionKey to the login egress IP. +// On dual-stack networks Node may prefer IPv6 while login used IPv4, +// causing HTTP 400 "InvalidSessionKey - check ip error". +// Force IPv4-first explicitly for every CLI child process (not reliant on env). +const DNS_ARGS = ['--dns-result-order=ipv4first']; + function runCloud189(args, opts = {}) { try { - const result = execFileSync('cloud189', [...args, '--json'], { + const result = execFileSync(process.execPath, [...DNS_ARGS, CLOUD189_CLI_JS, ...args, '--json'], { timeout: 30000, ...opts }); @@ -206,6 +220,91 @@ server.tool( (args) => runTool(() => runCloud189(['plan', args.command, ...args.args])) ); +// --- custom patch: destructive + rename tools ------------------------------- + +function assertConfirmed(confirm, action) { + if (confirm !== true) { + const err = new Error( + `Refused to ${action} because confirm is not true. First call cloud189_plan to preview, then re-run with confirm: true.` + ); + err.code = 'CONFIRM_REQUIRED'; + throw err; + } +} + +server.tool( + 'cloud189_rename_folder', + 'Rename a remote folder.', + { + remoteFolderId: remoteIdSchema.describe('Remote folder ID'), + newName: z.string().min(1).describe('New folder name'), + confirm: z.boolean().optional().describe('Must be true to execute') + }, + (args) => + runTool(() => { + assertConfirmed(args.confirm, 'rename folder'); + return runCloud189(['rename-folder', args.remoteFolderId, args.newName]); + }) +); + +server.tool( + 'cloud189_rename_file', + 'Rename a remote file (custom patch, uses the official renameFile API).', + { + remoteFileId: remoteIdSchema.describe('Remote file ID'), + newName: z.string().min(1).describe('New file name (including extension)'), + confirm: z.boolean().optional().describe('Must be true to execute') + }, + (args) => + runTool(() => { + assertConfirmed(args.confirm, 'rename file'); + return runCloud189(['rename-file', args.remoteFileId, args.newName]); + }) +); + +server.tool( + 'cloud189_rm', + 'Delete a remote file or folder. DANGEROUS. Use cloud189_plan first to preview.', + { + remoteId: remoteIdSchema.describe('Remote file or folder ID'), + dir: z.boolean().optional().describe('Set to true if deleting a folder'), + name: z.string().optional().describe('Remote item name (recommended, used for audit)'), + parent: remoteIdSchema.optional().describe('Parent folder ID (recommended, used for audit)'), + confirm: z.boolean().optional().describe('Must be true to execute') + }, + (args) => + runTool(() => { + assertConfirmed(args.confirm, 'delete'); + const cmdArgs = ['rm', args.remoteId]; + if (args.dir) cmdArgs.push('--dir'); + if (args.name) cmdArgs.push('--name', args.name); + if (args.parent) cmdArgs.push('--parent', args.parent); + return runCloud189(cmdArgs); + }) +); + +server.tool( + 'cloud189_mv', + 'Move a remote file or folder to another folder. DANGEROUS. Use cloud189_plan first to preview.', + { + remoteId: remoteIdSchema.describe('Remote file or folder ID'), + targetFolderId: remoteIdSchema.describe('Destination folder ID'), + dir: z.boolean().optional().describe('Set to true if moving a folder'), + name: z.string().optional().describe('Remote item name (recommended, used for audit)'), + parent: remoteIdSchema.optional().describe('Current parent folder ID (recommended, used for audit)'), + confirm: z.boolean().optional().describe('Must be true to execute') + }, + (args) => + runTool(() => { + assertConfirmed(args.confirm, 'move'); + const cmdArgs = ['mv', args.remoteId, args.targetFolderId]; + if (args.dir) cmdArgs.push('--dir'); + if (args.name) cmdArgs.push('--name', args.name); + if (args.parent) cmdArgs.push('--parent', args.parent); + return runCloud189(cmdArgs); + }) +); + // --- main ------------------------------------------------------------------- async function main() {