From bc8140e86169a4da4d8ad54958839cda645a3947 Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:28:09 +0100 Subject: [PATCH 01/18] feat(config): Add remoteFileSystem zod object shape to tools schema Define the validation shape for SSH remote filesystem config with fields: enabled, sshHost, sshKeyPath, sshPort, and allowedPaths. --- src/config/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/config/index.ts b/src/config/index.ts index 3b7838a..14e6f8f 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -74,6 +74,13 @@ const configFileSchema = z.object({ enabled: z.boolean(), allowedCommands: z.array(z.string()), }), + remoteFileSystem: z.object({ + enabled: z.boolean(), + sshHost: z.string(), + sshKeyPath: z.string(), + sshPort: z.number().default(22), + allowedPaths: z.array(z.string()), + }), }), skills: z.object({ enabled: z.boolean(), From 867b06eb49f4fd36d62024f519fc373a8fd970a6 Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:28:23 +0100 Subject: [PATCH 02/18] feat(config): Make remoteFileSystem optional with sensible defaults Existing configs without remoteFileSystem will get disabled defaults automatically, ensuring zero breaking changes. --- src/config/index.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/config/index.ts b/src/config/index.ts index 14e6f8f..8f5f308 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -74,13 +74,22 @@ const configFileSchema = z.object({ enabled: z.boolean(), allowedCommands: z.array(z.string()), }), - remoteFileSystem: z.object({ - enabled: z.boolean(), - sshHost: z.string(), - sshKeyPath: z.string(), - sshPort: z.number().default(22), - allowedPaths: z.array(z.string()), - }), + remoteFileSystem: z + .object({ + enabled: z.boolean(), + sshHost: z.string(), + sshKeyPath: z.string(), + sshPort: z.number().default(22), + allowedPaths: z.array(z.string()), + }) + .optional() + .default({ + enabled: false, + sshHost: "", + sshKeyPath: "", + sshPort: 22, + allowedPaths: [], + }), }), skills: z.object({ enabled: z.boolean(), From fd1205dc40ff2c9d98c9670fd5f364f14fac473f Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:28:34 +0100 Subject: [PATCH 03/18] feat(config): Add remoteFileSystemEnabled to AppConfig interface --- src/config/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/config/index.ts b/src/config/index.ts index 8f5f308..2dd5ff3 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -135,6 +135,9 @@ export interface AppConfig { githubEnabled: boolean; githubToken: string; + // Remote Filesystem + remoteFileSystemEnabled: boolean; + // Features memoryEnabled: boolean; skillsEnabled: boolean; From 50388809278c714f197fb550088100af50e12e64 Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:28:50 +0100 Subject: [PATCH 04/18] feat(config): Map remoteFileSystemEnabled in toAppConfig --- src/config/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/config/index.ts b/src/config/index.ts index 2dd5ff3..f775395 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -192,6 +192,9 @@ function toAppConfig(file: ConfigFile): AppConfig { githubEnabled: file.channels.github?.enabled ?? false, githubToken: file.channels.github?.token ?? "", + // Remote Filesystem + remoteFileSystemEnabled: file.tools.remoteFileSystem?.enabled ?? false, + // Features memoryEnabled: file.memory.enabled, skillsEnabled: file.skills.enabled, From 2cfa55ebe913cd9b081ecc2740683de05d33d377 Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:29:02 +0100 Subject: [PATCH 05/18] feat(tools): Scaffold remoteFileSystemTools module with disabled check Returns empty array when remote filesystem is not enabled in config. --- src/tools/remoteFileSystemTools.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/tools/remoteFileSystemTools.ts diff --git a/src/tools/remoteFileSystemTools.ts b/src/tools/remoteFileSystemTools.ts new file mode 100644 index 0000000..e777b1f --- /dev/null +++ b/src/tools/remoteFileSystemTools.ts @@ -0,0 +1,22 @@ +/** + * Remote file system tools for SSH-based access to a remote machine + * Uses the same @modelcontextprotocol/server-filesystem via SSH stdio tunnel + */ + +import { type McpConfig, McpToolset } from "@iqai/adk"; +import { getConfig, getRawConfig } from "../config/index.js"; + +/** + * Get remote filesystem MCP tools via SSH tunnel + * Returns empty array when disabled — zero impact on existing functionality + */ +export async function getRemoteFileSystemTools() { + const rawConfig = getRawConfig(); + const remoteFsConfig = rawConfig.tools.remoteFileSystem; + + if (!remoteFsConfig?.enabled) { + return []; + } + + return []; +} From a8fd2fd948cc83bf9f768cfe539c423c54ebd4ba Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:29:22 +0100 Subject: [PATCH 06/18] feat(tools): Add base SSH MCP transport with StrictHostKeyChecking Auto-accepts first connection but rejects changed host keys for safety. --- src/tools/remoteFileSystemTools.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/tools/remoteFileSystemTools.ts b/src/tools/remoteFileSystemTools.ts index e777b1f..ef9d129 100644 --- a/src/tools/remoteFileSystemTools.ts +++ b/src/tools/remoteFileSystemTools.ts @@ -18,5 +18,19 @@ export async function getRemoteFileSystemTools() { return []; } - return []; + const mcpConfig: McpConfig = { + name: "Remote Filesystem MCP Client", + description: "Read-only access to files on remote machine via SSH", + transport: { + mode: "stdio", + command: "ssh", + args: ["-o", "StrictHostKeyChecking=accept-new"], + env: { PATH: process.env.PATH || "" }, + }, + retryOptions: { maxRetries: 2, initialDelay: 500 }, + debug: getConfig().debug, + }; + + const mcpToolset = new McpToolset(mcpConfig); + return await mcpToolset.getTools(); } From 1d373124c98037f893f0aa52d5813f919db182e8 Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:29:33 +0100 Subject: [PATCH 07/18] feat(tools): Add BatchMode=yes to prevent SSH password prompt hangs Ensures clean failure instead of indefinite hang when key auth fails. --- src/tools/remoteFileSystemTools.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/remoteFileSystemTools.ts b/src/tools/remoteFileSystemTools.ts index ef9d129..9ddf17b 100644 --- a/src/tools/remoteFileSystemTools.ts +++ b/src/tools/remoteFileSystemTools.ts @@ -24,7 +24,7 @@ export async function getRemoteFileSystemTools() { transport: { mode: "stdio", command: "ssh", - args: ["-o", "StrictHostKeyChecking=accept-new"], + args: ["-o", "StrictHostKeyChecking=accept-new", "-o", "BatchMode=yes"], env: { PATH: process.env.PATH || "" }, }, retryOptions: { maxRetries: 2, initialDelay: 500 }, From e3b4ea89762b0c4538c403f62f2e979989d4f6df Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:29:46 +0100 Subject: [PATCH 08/18] feat(tools): Add SSH key path, port, and host args from config --- src/tools/remoteFileSystemTools.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/tools/remoteFileSystemTools.ts b/src/tools/remoteFileSystemTools.ts index 9ddf17b..9ad0c1c 100644 --- a/src/tools/remoteFileSystemTools.ts +++ b/src/tools/remoteFileSystemTools.ts @@ -24,7 +24,17 @@ export async function getRemoteFileSystemTools() { transport: { mode: "stdio", command: "ssh", - args: ["-o", "StrictHostKeyChecking=accept-new", "-o", "BatchMode=yes"], + args: [ + "-o", + "StrictHostKeyChecking=accept-new", + "-o", + "BatchMode=yes", + "-i", + remoteFsConfig.sshKeyPath, + "-p", + String(remoteFsConfig.sshPort), + remoteFsConfig.sshHost, + ], env: { PATH: process.env.PATH || "" }, }, retryOptions: { maxRetries: 2, initialDelay: 500 }, From 5f888260d26b67aacfd55f904fd590f3c138aa20 Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:30:00 +0100 Subject: [PATCH 09/18] feat(tools): Run MCP filesystem server on remote machine via npx Pipes @modelcontextprotocol/server-filesystem stdio through SSH, passing configured allowedPaths to restrict remote access. --- src/tools/remoteFileSystemTools.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/remoteFileSystemTools.ts b/src/tools/remoteFileSystemTools.ts index 9ad0c1c..71046e5 100644 --- a/src/tools/remoteFileSystemTools.ts +++ b/src/tools/remoteFileSystemTools.ts @@ -34,6 +34,10 @@ export async function getRemoteFileSystemTools() { "-p", String(remoteFsConfig.sshPort), remoteFsConfig.sshHost, + "npx", + "-y", + "@modelcontextprotocol/server-filesystem", + ...remoteFsConfig.allowedPaths, ], env: { PATH: process.env.PATH || "" }, }, From 05064e6107dfd9e31516207f86e3f8345145dca1 Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:30:12 +0100 Subject: [PATCH 10/18] feat(tools): Add debug logging for SSH tunnel connections --- src/tools/remoteFileSystemTools.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tools/remoteFileSystemTools.ts b/src/tools/remoteFileSystemTools.ts index 71046e5..f1419e0 100644 --- a/src/tools/remoteFileSystemTools.ts +++ b/src/tools/remoteFileSystemTools.ts @@ -45,6 +45,12 @@ export async function getRemoteFileSystemTools() { debug: getConfig().debug, }; + if (getConfig().debug) { + console.log( + `[RemoteFileSystemTools] SSH tunnel to ${remoteFsConfig.sshHost}, paths: ${remoteFsConfig.allowedPaths.join(", ")}`, + ); + } + const mcpToolset = new McpToolset(mcpConfig); return await mcpToolset.getTools(); } From 8dca07dd9908373f9dfcc13c23d68e1ecb2a382f Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:30:24 +0100 Subject: [PATCH 11/18] feat(tools): Export getRemoteFileSystemTools from barrel index --- src/tools/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/index.ts b/src/tools/index.ts index 14f1d92..5118538 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,5 +1,6 @@ export { createDelegateTaskTool } from "./delegateTools.js"; export { getFileSystemTools } from "./fileSystemTools.js"; export { loadPrivateTools } from "./private-loader.js"; +export { getRemoteFileSystemTools } from "./remoteFileSystemTools.js"; export { scheduleTools, setSchedulerDeps } from "./scheduleTools.js"; export { execShellTool, shellTools } from "./shellTools.js"; From 0b983c0d23e4d34956fb34fb14fa92757d11d010 Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:30:37 +0100 Subject: [PATCH 12/18] feat(delegate): Add remoteFilesystem to tool group names Allows subagents to request remote filesystem tools via delegation. --- src/tools/delegateTools.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tools/delegateTools.ts b/src/tools/delegateTools.ts index 1f145d2..4e2998c 100644 --- a/src/tools/delegateTools.ts +++ b/src/tools/delegateTools.ts @@ -17,7 +17,12 @@ import { createLogger } from "../lib/logger.js"; const log = createLogger("Delegate"); -const toolGroupNames = ["shell", "filesystem", "memory"] as const; +const toolGroupNames = [ + "shell", + "filesystem", + "remoteFilesystem", + "memory", +] as const; type ToolGroupName = (typeof toolGroupNames)[number]; /** Max LLM round-trips per subagent to keep delegation fast */ From e16ae803b5510bba52b77922444fc227a19d9c5e Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:30:49 +0100 Subject: [PATCH 13/18] feat(delegate): Document remoteFilesystem in tool groups description --- src/tools/delegateTools.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/delegateTools.ts b/src/tools/delegateTools.ts index 4e2998c..af2c93e 100644 --- a/src/tools/delegateTools.ts +++ b/src/tools/delegateTools.ts @@ -43,7 +43,7 @@ const taskSchema = z.object({ .array(z.enum(toolGroupNames)) .default(["filesystem"]) .describe( - "Tool groups the subagent needs: shell (exec commands), filesystem (read/write files), memory (recall/write memories)", + "Tool groups the subagent needs: shell (exec commands), filesystem (read/write files), remoteFilesystem (read files on remote machine via SSH), memory (recall/write memories)", ), }); From d0a32ebbe29d2cddb660583a9f85a76144117a8b Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:31:00 +0100 Subject: [PATCH 14/18] feat(agent): Import getRemoteFileSystemTools --- src/agents/agent.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/agents/agent.ts b/src/agents/agent.ts index 3b987a7..7f461c8 100644 --- a/src/agents/agent.ts +++ b/src/agents/agent.ts @@ -21,6 +21,7 @@ import { buildSkillsPrompt } from "../skills/SkillService.js"; import { createDelegateTaskTool, getFileSystemTools, + getRemoteFileSystemTools, loadPrivateTools, scheduleTools, shellTools, From 516101a4045c384e4f8588fc6048d32c98432642 Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:31:13 +0100 Subject: [PATCH 15/18] feat(agent): Initialize remote filesystem tools after local filesystem --- src/agents/agent.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/agents/agent.ts b/src/agents/agent.ts index 7f461c8..79e3993 100644 --- a/src/agents/agent.ts +++ b/src/agents/agent.ts @@ -84,6 +84,9 @@ export async function createClawdAgent(options: CreateAgentOptions = {}) { // Get filesystem tools for workspace access const fileSystemTools = await getFileSystemTools(); + // Get remote filesystem tools (SSH tunnel, empty array when disabled) + const remoteFileSystemTools = await getRemoteFileSystemTools(); + // Memory tools shared with subagents (no ForgetMemoryTool for safety) const memoryTools = [new RecallMemoryTool(), new WriteMemoryTool()]; From 7f5ce4a04241d0f072f0369eeafe06f0c832b1e7 Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:31:25 +0100 Subject: [PATCH 16/18] feat(agent): Pass remote filesystem tools to delegate toolGroups Subagents can now request remoteFilesystem tools via delegation. --- src/agents/agent.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/agents/agent.ts b/src/agents/agent.ts index 79e3993..be6d465 100644 --- a/src/agents/agent.ts +++ b/src/agents/agent.ts @@ -98,6 +98,7 @@ export async function createClawdAgent(options: CreateAgentOptions = {}) { toolGroups: { shell: shellTools, filesystem: fileSystemTools, + remoteFilesystem: remoteFileSystemTools, memory: memoryTools, }, }); From c1de78f21bc1d6fc6b9a77f92bb31dc8bfb2b4dc Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Sat, 14 Feb 2026 15:31:37 +0100 Subject: [PATCH 17/18] feat(agent): Register remote filesystem tools with agent builder The main agent now has direct access to remote filesystem MCP tools alongside local filesystem tools. --- src/agents/agent.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/agents/agent.ts b/src/agents/agent.ts index be6d465..142968e 100644 --- a/src/agents/agent.ts +++ b/src/agents/agent.ts @@ -121,6 +121,7 @@ export async function createClawdAgent(options: CreateAgentOptions = {}) { ...scheduleTools, ...shellTools, ...fileSystemTools, + ...remoteFileSystemTools, ...privateTools, delegateTool, ) From 115d4657fe456d6226a201f14395cf977113a48a Mon Sep 17 00:00:00 2001 From: "Marvel.Codes" Date: Wed, 18 Feb 2026 15:29:46 +0100 Subject: [PATCH 18/18] feat(remote): Prefix remote tools with remote_ and add system prompt docs Remote filesystem tools had identical names to local tools (read_file, write_file, etc.), causing collisions. Prefix all remote tools with "remote_" and inject remote PC tool documentation into the agent system prompt so it knows when and how to use them. --- src/agents/config/agent.config.ts | 28 ++++++++++++++++++++++++++-- src/tools/remoteFileSystemTools.ts | 16 ++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/agents/config/agent.config.ts b/src/agents/config/agent.config.ts index 88fe92f..4954c8c 100644 --- a/src/agents/config/agent.config.ts +++ b/src/agents/config/agent.config.ts @@ -6,7 +6,7 @@ import * as fs from "node:fs/promises"; import * as path from "node:path"; import matter from "gray-matter"; -import { configExists, getConfig } from "../../config/index.js"; +import { configExists, getConfig, getRawConfig } from "../../config/index.js"; import { HEARTBEAT_OK, SILENT_REPLY_TOKEN } from "../../lib/tokens.js"; import { buildSkillsPrompt } from "../../skills/SkillService.js"; import type { @@ -184,7 +184,31 @@ You have FULL read/write access to the workspace directory. Use these tools to m - Extra flags/arguments ARE allowed: \`git log --author=Marvel --since=yesterday --oneline\` works because \`git log\` is allowlisted. - Avoid shell metacharacters (\`; & | $ !\`). Use simple commands without pipes or chaining. - For dates with spaces, use dotted format: \`--since=2.days.ago\` instead of \`--since="2 days ago"\`. - - \`gh\` (GitHub CLI) is available for GitHub API queries: PRs, issues, commits, etc.`; + - \`gh\` (GitHub CLI) is available for GitHub API queries: PRs, issues, commits, etc. +${buildRemoteFileSystemSection()}`; +} + +/** + * Build the remote filesystem tools section (only when enabled) + */ +function buildRemoteFileSystemSection(): string { + try { + const rawConfig = getRawConfig(); + const remoteFsConfig = rawConfig.tools.remoteFileSystem; + if (!remoteFsConfig?.enabled) return ""; + + const host = remoteFsConfig.sshHost; + const paths = remoteFsConfig.allowedPaths.join(", "); + + return `\n**Remote PC Tools (via SSH to \`${host}\`):** +- These tools access files on the user's *remote machine* over SSH — they are NOT local workspace tools. +- Remote host: \`${host}\` | Accessible paths: \`${paths}\` +- All remote tools are prefixed with \`remote_\`: \`remote_read_file\`, \`remote_write_file\`, \`remote_list_directory\`, \`remote_search_files\`, \`remote_get_file_info\`, \`remote_create_directory\`, \`remote_move_file\` +- When the user asks about files on "my PC", "my computer", "my desktop", or "remote machine", use these \`remote_*\` tools — NOT the local workspace tools. +- The remote tools operate on the remote machine's filesystem at the allowed paths listed above.`; + } catch { + return ""; + } } /** diff --git a/src/tools/remoteFileSystemTools.ts b/src/tools/remoteFileSystemTools.ts index f1419e0..e30b73d 100644 --- a/src/tools/remoteFileSystemTools.ts +++ b/src/tools/remoteFileSystemTools.ts @@ -1,11 +1,15 @@ /** * Remote file system tools for SSH-based access to a remote machine * Uses the same @modelcontextprotocol/server-filesystem via SSH stdio tunnel + * Tools are prefixed with "remote_" to avoid conflicts with local filesystem tools */ import { type McpConfig, McpToolset } from "@iqai/adk"; import { getConfig, getRawConfig } from "../config/index.js"; +/** Prefix applied to all remote filesystem tool names to distinguish from local tools */ +const REMOTE_TOOL_PREFIX = "remote_"; + /** * Get remote filesystem MCP tools via SSH tunnel * Returns empty array when disabled — zero impact on existing functionality @@ -20,7 +24,7 @@ export async function getRemoteFileSystemTools() { const mcpConfig: McpConfig = { name: "Remote Filesystem MCP Client", - description: "Read-only access to files on remote machine via SSH", + description: "Access to files on remote machine via SSH", transport: { mode: "stdio", command: "ssh", @@ -52,5 +56,13 @@ export async function getRemoteFileSystemTools() { } const mcpToolset = new McpToolset(mcpConfig); - return await mcpToolset.getTools(); + const tools = await mcpToolset.getTools(); + + // Prefix tool names with "remote_" to avoid collisions with local filesystem tools + for (const tool of tools) { + tool.name = `${REMOTE_TOOL_PREFIX}${tool.name}`; + tool.description = `[Remote PC] ${tool.description}`; + } + + return tools; }