sftp_tool
Connect to an SSH server to upload or download files between local and remote paths using this automation tool from the ToolBox MCP Server.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action: upload or download | |
| localPath | Yes | Local file path (absolute) | |
| remotePath | Yes | Remote file path | |
| serverName | Yes | SSH server name |
Implementation Reference
- src/tools/sftp_tool.ts:194-255 (handler)Main execution logic for the sftp_tool. Parses arguments, validates inputs, establishes SSH connection and SFTP session, performs upload or download, handles cleanup, and returns formatted response or error.export default async (request: any) => { try { // 解析请求参数 const { serverName, action, localPath, remotePath } = request.params.arguments; // 验证参数 if (!serverName || !action || !localPath || !remotePath) { throw new Error("Missing required parameters: serverName, action, localPath, remotePath"); } if (action !== 'upload' && action !== 'download') { throw new Error('Invalid action. Must be "upload" or "download".'); } // 获取SSH连接 const conn = await getSSHConnection(serverName); // 获取SFTP会话 const sftp = await getSFTPSession(conn); let result; try { // 执行文件操作 if (action === 'upload') { result = await uploadFile(sftp, localPath, remotePath); } else { result = await downloadFile(sftp, remotePath, localPath); } } finally { // 关闭SFTP会话 sftp.end(); } // 返回成功结果 return { content: [ { type: "text", text: JSON.stringify({ message: result, action: action, localPath: localPath, remotePath: remotePath }, null, 2) } ] }; } catch (error) { // 返回错误结果 return { content: [ { type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2) } ], isError: true }; } };
- src/tools/sftp_tool.ts:9-33 (schema)JSON schema defining the input parameters for sftp_tool: serverName, action (upload/download), localPath, remotePath.export const schema = { name: "sftp_tool", description: "Connect to SSH server and upload/download files", type: "object", properties: { serverName: { type: "string", description: "SSH server name", }, action: { type: "string", description: "Action: upload or download", enum: ["upload", "download"], }, localPath: { type: "string", description: "Local file path (absolute)", }, remotePath: { type: "string", description: "Remote file path", }, }, required: ["serverName", "action", "localPath", "remotePath"] };
- src/handler/ToolHandler.ts:91-139 (registration)Dynamic loader and registration function that scans src/tools for *_tool.ts files, imports them (including sftp_tool.ts), extracts default handler, schema, destroy, sets toolName from filename ('sftp_tool'), and registers in tools array and handlers map.export async function loadTools(reload: boolean = false): Promise<{ [key: string]: (request: ToolRequest) => Promise<ToolResponse> }> { // 如果是初始加载且已加载,则直接返回 if (!reload && isLoaded) return; // 如果是重新加载,则重置状态 if (reload) { for (const tool of tools) { await tool?.destroy?.(); delete handlers[tool.name]; } tools.length = 0; isLoaded = false; } // 获取所有工具文件 const toolFiles = fs.readdirSync(toolsDir).filter(file => file.endsWith('.js') || file.endsWith('.ts')); // 加载每个工具 for (const file of toolFiles) { const toolPath = path.join(toolsDir, file); try { // 如果是重新加载,清除模块缓存 if (reload) clearModuleCache(toolPath); // 导入模块,重新加载时添加时间戳防止缓存 const importPath = 'file://' + toolPath + (reload ? `?update=${Date.now()}` : ''); const { default: tool, schema, destroy } = await import(importPath); const toolName = path.parse(toolPath).name; // 注册工具 tools.push({ name: toolName, description: tool.description, inputSchema: schema, destroy: destroy }); // 注册处理函数 handlers[toolName] = async (request: ToolRequest) => { return await tool(request); }; } catch (error) { console.error(`Failed to ${reload ? 'reload' : 'load'} tool ${file}:`, error); } } isLoaded = true; if (reload) console.log(`Successfully reloaded ${tools.length} tools`); return handlers; }
- src/tools/sftp_tool.ts:40-82 (helper)Core helper to manage SSH connections: caches connections per serverName, parses URI from env var SSH_{serverName}_URI (format user:pass@host:port), creates ssh2 Client if not cached.async function getSSHConnection(serverName: string): Promise<Client> { // 如果已有连接,直接返回 if (sshConnections[serverName]) { return sshConnections[serverName]; } // 从环境变量获取连接信息 const sshUri = process.env[`SSH_${serverName}_URI`]; if (!sshUri) { throw new Error(`SSH_${serverName}_URI environment variable must be set.`); } // 解析连接信息 const [usernameAndpassword, HostAndport] = sshUri.split('@'); const [username, password] = usernameAndpassword.split(':'); const [host, port] = HostAndport.split(':'); // 创建新连接 return new Promise((resolve, reject) => { const conn = new Client(); conn.on('ready', () => { sshConnections[serverName] = conn; resolve(conn); }); conn.on('error', (err) => { delete sshConnections[serverName]; reject(new Error(`SSH connection error: ${err.message}`)); }); conn.on('end', () => { delete sshConnections[serverName]; }); conn.connect({ host: host, port: parseInt(port), username: username, password: password }); }); }
- src/tools/sftp_tool.ts:258-275 (helper)Destroy function called on tool unload to close all cached SSH connections and clear the cache.export async function destroy() { console.log("Destroy sftp_tool"); // 关闭所有SSH连接 for (const serverName in sshConnections) { if (sshConnections[serverName]) { try { if (sshConnections[serverName].end) { sshConnections[serverName].end(); } } catch (error) { console.error(`Failed to close SSH connection for ${serverName}: ${error}`); } finally { delete sshConnections[serverName]; } } } sshConnections = {}; }