ftp-mcp 1.3.0 → 1.3.1

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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/index.js +142 -96
  3. package/package.json +122 -122
package/README.md CHANGED
@@ -9,7 +9,8 @@ An enterprise-grade Model Context Protocol (MCP) server providing sophisticated
9
9
  - **Security & Authorization**: Native support for SSH Key exchanges (both direct and Agent-forwarding), Pass-phrase authentication, and explicit `readOnly: true` config profiling to sandbox dangerous modifications.
10
10
  - **Smart Directory Syncing**: Deep hash-and-size sync algorithms for minimal network payload deployments, complete with `--dryRun` toggling that logs differences back to the LLM without modifying live directories.
11
11
  - **Git & Node Aware**: Automatically unpacks `.gitignore` and `.ftpignore` environments. Semantically evaluates `package.json` for smart context summarization.
12
- - **Interactive Initializer**: Simple scaffolding of configurations natively via the CLI.
12
+ - **AI-Guided Initialization**: A dedicated "AI-First" configuration track (`--init`) that provides highly verbose instruction context during setup, ensuring AI assistants absorb server capabilities and operational constraints directly into their working memory.
13
+ - **Interactive Initializer**: Simple scaffolding of configurations natively via the CLI for human users.
13
14
  - **Audit Logging**: Robust instrumentation generating structured `.ftp-mcp-audit.log` traces on all filesystem mutations.
14
15
  - **Comprehensive E2E Testing**: Guaranteed operational functionality validated continuously by Node-based MCP IO test orchestration.
15
16
 
package/index.js CHANGED
@@ -26,7 +26,7 @@ const __filename = fileURLToPath(import.meta.url);
26
26
  const __dirname = path.dirname(__filename);
27
27
 
28
28
  // Read version from package.json to avoid version drift (CODE-1)
29
- let SERVER_VERSION = "1.3.0";
29
+ let SERVER_VERSION = "1.3.1";
30
30
  try {
31
31
  const pkg = JSON.parse(readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
32
32
  SERVER_VERSION = pkg.version || SERVER_VERSION;
@@ -42,27 +42,50 @@ if (process.argv.includes("--init")) {
42
42
 
43
43
  intro('🚀 Welcome to FTP-MCP Initialization Wizard');
44
44
 
45
+ const setupType = await select({
46
+ message: 'Who is running this setup wizard?',
47
+ options: [
48
+ { value: 'ai', label: '1. AI Agent Install (Provides verbose context on how to use the server)', hint: 'Detailed instructions for LLMs' },
49
+ { value: 'human', label: '2. Human Install (Standard Setup)', hint: 'Concise standard setup for human users' }
50
+ ]
51
+ });
52
+ if (isCancel(setupType)) { outro('Setup cancelled.'); process.exit(0); }
53
+
54
+ const isAI = setupType === 'ai';
55
+
45
56
  const host = await text({
46
- message: 'Enter your FTP/SFTP Host (e.g. sftp://ftp.example.com)',
57
+ message: isAI
58
+ ? '[AI INSTRUCTION] Provide the remote FTP/SFTP Host address. (e.g. sftp://ftp.example.com or ftp://1.2.3.4). Note: This tool brokers remote filesystem access as an MCP server.'
59
+ : 'Enter your FTP/SFTP Host (e.g. sftp://ftp.example.com)',
47
60
  placeholder: 'sftp://127.0.0.1',
48
- validate: (val) => val.length === 0 ? "Host is required!" : undefined,
61
+ validate: (val) => (!val || val.length === 0) ? "Host is required!" : undefined,
49
62
  });
50
63
  if (isCancel(host)) { outro('Setup cancelled.'); process.exit(0); }
51
64
 
52
65
  const user = await text({
53
- message: 'Enter your Username',
54
- validate: (val) => val.length === 0 ? "User is required!" : undefined,
66
+ message: isAI
67
+ ? '[AI INSTRUCTION] Provide the remote server username. This credential will be used for all subsequent tool calls in this session.'
68
+ : 'Enter your Username',
69
+ validate: (val) => (!val || val.length === 0) ? "User is required!" : undefined,
55
70
  });
56
71
  if (isCancel(user)) { outro('Setup cancelled.'); process.exit(0); }
57
72
 
58
73
  const pass = await promptPassword({
59
- message: 'Enter your Password (optional if using keys)',
74
+ message: isAI
75
+ ? '[AI INSTRUCTION] Provide the password for the connection. If you are using SSH key authentication for SFTP, you may leave this field empty.'
76
+ : 'Enter your Password (optional if using keys)',
60
77
  });
61
78
  if (isCancel(pass)) { outro('Setup cancelled.'); process.exit(0); }
62
79
 
63
80
  const port = await text({
64
- message: 'Enter port (optional, defaults to 21 for FTP, 22 for SFTP)',
65
- placeholder: '22'
81
+ message: isAI
82
+ ? '[AI INSTRUCTION] Provide the specific port for the connection. Default is 21 for FTP and 22 for SFTP. If the user has not specified a custom port, you should leave this blank.'
83
+ : 'Enter port (optional, defaults to 21 for FTP, 22 for SFTP)',
84
+ placeholder: '22',
85
+ validate: (val) => {
86
+ if (val && isNaN(parseInt(val, 10))) return "Port must be a number!";
87
+ return undefined;
88
+ }
66
89
  });
67
90
  if (isCancel(port)) { outro('Setup cancelled.'); process.exit(0); }
68
91
 
@@ -70,10 +93,17 @@ if (process.argv.includes("--init")) {
70
93
  let privateKey = '';
71
94
 
72
95
  if (isSFTP) {
73
- const usesKey = await confirm({ message: 'Are you using an SSH Private Key instead of a password?' });
74
- if (!isCancel(usesKey) && usesKey) {
96
+ const usesKey = await confirm({
97
+ message: isAI
98
+ ? '[AI INSTRUCTION] An SFTP protocol was detected. Is the user employing an SSH Private Key instead of a traditional password? (Recommended for production)'
99
+ : 'Are you using an SSH Private Key instead of a password?'
100
+ });
101
+ if (isCancel(usesKey)) { outro('Setup cancelled.'); process.exit(0); }
102
+ if (usesKey) {
75
103
  privateKey = await text({
76
- message: 'Path to your private key (e.g. ~/.ssh/id_rsa)',
104
+ message: isAI
105
+ ? '[AI INSTRUCTION] Provide the absolute local filesystem path to the SSH private key (e.g. ~/.ssh/id_rsa). This path must be accessible from the environment where the MCP server is running.'
106
+ : 'Path to your private key (e.g. ~/.ssh/id_rsa)',
77
107
  });
78
108
  if (isCancel(privateKey)) { outro('Setup cancelled.'); process.exit(0); }
79
109
  }
@@ -92,9 +122,26 @@ if (process.argv.includes("--init")) {
92
122
 
93
123
  await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
94
124
 
95
- note(`✅ Successfully generated config file at:\n${CONFIG_FILE}`, 'Success');
125
+ if (isAI) {
126
+ note(
127
+ "AI CONTEXT ESTABLISHED: The `.ftpconfig` file has been generated.\n" +
128
+ "You now have access to this MCP server's tools.\n\n" +
129
+ "Important capabilities:\n" +
130
+ "- Content-rich operations: `ftp_list`, `ftp_download`, `ftp_upload`.\n" +
131
+ "- Batched operations: `ftp_batch_upload`, `ftp_batch_download` (Highly recommended for speed).\n" +
132
+ "- Safe regex searches: `ftp_search`.\n" +
133
+ "- Transparent paths: All remote operations are relative to the server root.\n\n" +
134
+ "You may now proceed with the user's tasks.",
135
+ "Configuration Success"
136
+ );
137
+ } else {
138
+ note(`✅ Successfully generated config file at:\n${CONFIG_FILE}`, 'Success');
139
+ }
96
140
 
97
- outro("You're ready to deploy with MCP! Ask your AI to 'list remote files'");
141
+ outro(isAI
142
+ ? "Deployment complete. You are now configured to manage the remote filesystem."
143
+ : "You're ready to deploy with MCP! Ask your AI to 'list remote files'"
144
+ );
98
145
  } catch (err) {
99
146
  console.error(`❌ Init failed: ${err.message}`);
100
147
  }
@@ -753,17 +800,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
753
800
  tools: [
754
801
  {
755
802
  name: "ftp_connect",
756
- description: "Connect to a named FTP profile from .ftpconfig",
803
+ description: "Establish or switch the active connection to a specific remote server profile defined in your .ftpconfig (e.g., 'production', 'staging'). This is the first step before performing any remote operations.",
757
804
  inputSchema: {
758
805
  type: "object",
759
806
  properties: {
760
807
  profile: {
761
808
  type: "string",
762
- description: "Profile name from .ftpconfig (e.g., 'production', 'staging')"
809
+ description: "The named profile key from your .ftpconfig file."
763
810
  },
764
811
  useEnv: {
765
812
  type: "boolean",
766
- description: "Force use of environment variables instead of .ftpconfig",
813
+ description: "If true, bypasses .ftpconfig and connects using global environment variables (FTPMCP_HOST, etc.)",
767
814
  default: false
768
815
  }
769
816
  }
@@ -771,13 +818,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
771
818
  },
772
819
  {
773
820
  name: "ftp_deploy",
774
- description: "Run a named deployment preset from .ftpconfig",
821
+ description: "Execute a pre-defined deployment preset from .ftpconfig. This typically maps a specific local folder to a remote target with pre-configured exclusion rules.",
775
822
  inputSchema: {
776
823
  type: "object",
777
824
  properties: {
778
825
  deployment: {
779
826
  type: "string",
780
- description: "Deployment name from .ftpconfig deployments (e.g., 'deploy-frontend', 'deploy-api')"
827
+ description: "The name of the deployment preset (e.g., 'web-app', 'api-server')."
781
828
  }
782
829
  },
783
830
  required: ["deployment"]
@@ -793,23 +840,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
793
840
  },
794
841
  {
795
842
  name: "ftp_list",
796
- description: "List files and directories in a remote FTP/SFTP path",
843
+ description: "List files and directories in a remote path. Use 'limit' and 'offset' for pagination when dealing with directories containing hundreds of files to avoid context overflow.",
797
844
  inputSchema: {
798
845
  type: "object",
799
846
  properties: {
800
847
  path: {
801
848
  type: "string",
802
- description: "Remote path to list (defaults to current directory)",
849
+ description: "Remote directory path (defaults to current working directory).",
803
850
  default: "."
804
851
  },
805
852
  limit: {
806
853
  type: "number",
807
- description: "Maximum number of files to return",
854
+ description: "Maximum results to return in this chunk.",
808
855
  default: 100
809
856
  },
810
857
  offset: {
811
858
  type: "number",
812
- description: "Number of files to skip over",
859
+ description: "Starting position in the file list for pagination.",
813
860
  default: 0
814
861
  }
815
862
  }
@@ -817,21 +864,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
817
864
  },
818
865
  {
819
866
  name: "ftp_get_contents",
820
- description: "Read file content directly from FTP/SFTP without downloading",
867
+ description: "Read the source text of a remote file. CRITICAL: For large files, use 'startLine' and 'endLine' to extract specific chunks and prevent hitting the LLM context limit.",
821
868
  inputSchema: {
822
869
  type: "object",
823
870
  properties: {
824
871
  path: {
825
872
  type: "string",
826
- description: "Remote file path to read"
873
+ description: "Absolute or relative remote path to the file."
827
874
  },
828
875
  startLine: {
829
876
  type: "number",
830
- description: "Optional start line for reading chunk (1-indexed)"
877
+ description: "Optional: The first line to include in the output (1-indexed)."
831
878
  },
832
879
  endLine: {
833
880
  type: "number",
834
- description: "Optional end line for reading chunk (inclusive, 1-indexed)"
881
+ description: "Optional: The last line to include (inclusive, 1-indexed)."
835
882
  }
836
883
  },
837
884
  required: ["path"]
@@ -839,25 +886,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
839
886
  },
840
887
  {
841
888
  name: "ftp_patch_file",
842
- description: "Apply a Unified Diff patch to a remote file",
889
+ description: "Apply a Unified Diff patch to a remote file. RECOMMENDED: Use this instead of ftp_put_contents for updating existing files to minimize bandwidth and ensure atomic-like updates. For new files, use ftp_put_contents.",
843
890
  inputSchema: {
844
891
  type: "object",
845
892
  properties: {
846
893
  path: {
847
894
  type: "string",
848
- description: "Remote file path to patch"
895
+ description: "Remote path to the existing file to be patched."
849
896
  },
850
897
  patch: {
851
898
  type: "string",
852
- description: "Unified diff string containing the changes"
899
+ description: "The Unified Diff formatted string containing your local changes."
853
900
  },
854
901
  expectedHash: {
855
902
  type: "string",
856
- description: "Optional MD5 hash of the file before patching to prevent drift"
903
+ description: "Optional (but recommended): The SHA-256 hash of the remote file before patching to prevent race conditions (drift protection)."
857
904
  },
858
905
  createBackup: {
859
906
  type: "boolean",
860
- description: "Create a .bak file before patching",
907
+ description: "Generate a .bak copy of the remote file before applying the changes.",
861
908
  default: true
862
909
  }
863
910
  },
@@ -866,13 +913,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
866
913
  },
867
914
  {
868
915
  name: "ftp_analyze_workspace",
869
- description: "Semantically analyze a remote directory to detect project type and dependencies",
916
+ description: "Introspect the remote directory to identify technical environments (e.g., Node.js, PHP, Python) and read dependency manifests. Use this to gain architectural context of a new codebase.",
870
917
  inputSchema: {
871
918
  type: "object",
872
919
  properties: {
873
920
  path: {
874
921
  type: "string",
875
- description: "Remote directory path to analyze (defaults to current)",
922
+ description: "Remote directory to analyze (defaults to current server root).",
876
923
  default: "."
877
924
  }
878
925
  }
@@ -880,17 +927,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
880
927
  },
881
928
  {
882
929
  name: "ftp_put_contents",
883
- description: "Write content directly to FTP/SFTP file without local file",
930
+ description: "Write raw text directly to a remote destination. Best for creating NEW files. For modifying existing files, prefer ftp_patch_file.",
884
931
  inputSchema: {
885
932
  type: "object",
886
933
  properties: {
887
934
  path: {
888
935
  type: "string",
889
- description: "Remote file path to write"
936
+ description: "Remote destination path."
890
937
  },
891
938
  content: {
892
939
  type: "string",
893
- description: "Content to write to the file"
940
+ description: "The full string content to write."
894
941
  }
895
942
  },
896
943
  required: ["path", "content"]
@@ -898,13 +945,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
898
945
  },
899
946
  {
900
947
  name: "ftp_stat",
901
- description: "Get file metadata (size, modified date, permissions)",
948
+ description: "Retrieve comprehensive metadata for a remote property, including size, modification timestamps, and UNIX permissions.",
902
949
  inputSchema: {
903
950
  type: "object",
904
951
  properties: {
905
952
  path: {
906
953
  type: "string",
907
- description: "Remote file path"
954
+ description: "Remote file or directory path."
908
955
  }
909
956
  },
910
957
  required: ["path"]
@@ -912,13 +959,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
912
959
  },
913
960
  {
914
961
  name: "ftp_exists",
915
- description: "Check if file or folder exists on FTP/SFTP server",
962
+ description: "Check for the existence of a file or folder without performing heavy file operations. Use this for conditional logic workflows.",
916
963
  inputSchema: {
917
964
  type: "object",
918
965
  properties: {
919
966
  path: {
920
967
  type: "string",
921
- description: "Remote path to check"
968
+ description: "Remote target path."
922
969
  }
923
970
  },
924
971
  required: ["path"]
@@ -926,18 +973,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
926
973
  },
927
974
  {
928
975
  name: "ftp_tree",
929
- description: "Get recursive directory listing (entire structure at once)",
976
+ description: "Generate a complete recursive directory map. Use this to visualize project structure, but be cautious with 'maxDepth' in very large remote repositories to avoid excessive network payload.",
930
977
  inputSchema: {
931
978
  type: "object",
932
979
  properties: {
933
980
  path: {
934
981
  type: "string",
935
- description: "Remote path to start tree from",
982
+ description: "Remote directory path to start mapping from (defaults to root).",
936
983
  default: "."
937
984
  },
938
985
  maxDepth: {
939
986
  type: "number",
940
- description: "Maximum depth to recurse",
987
+ description: "Maximum recursion depth to prevent infinite loops or huge payloads.",
941
988
  default: 10
942
989
  }
943
990
  }
@@ -945,40 +992,40 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
945
992
  },
946
993
  {
947
994
  name: "ftp_search",
948
- description: "Advanced remote search: find files by name, content, or type",
995
+ description: "Advanced remote file search. Supports finding files by name (wildcards), extension, or content regex (grep-like). Use this to find specific code patterns across the remote workspace.",
949
996
  inputSchema: {
950
997
  type: "object",
951
998
  properties: {
952
999
  pattern: {
953
1000
  type: "string",
954
- description: "Filename search pattern (supports wildcards like *.js)"
1001
+ description: "File name pattern (e.g. `*.js`, `db_*`)."
955
1002
  },
956
1003
  contentPattern: {
957
1004
  type: "string",
958
- description: "Regex pattern to search inside file contents (grep)"
1005
+ description: "Regex pattern to search inside file contents. Highly efficient for finding variable usage or specific logic."
959
1006
  },
960
1007
  extension: {
961
1008
  type: "string",
962
- description: "Filter by file extension (e.g., '.js', '.php')"
1009
+ description: "Restrict search to specific file extensions (e.g. `.css`)."
963
1010
  },
964
1011
  findLikelyConfigs: {
965
1012
  type: "boolean",
966
- description: "If true, prioritizes finding config, auth, and build files",
1013
+ description: "Prioritize searching for project manifests and config files (package.json, .env, etc.)",
967
1014
  default: false
968
1015
  },
969
1016
  path: {
970
1017
  type: "string",
971
- description: "Remote path to search in",
1018
+ description: "Remote directory to start the recursive search in.",
972
1019
  default: "."
973
1020
  },
974
1021
  limit: {
975
1022
  type: "number",
976
- description: "Maximum results to return",
1023
+ description: "Maximum matches to return.",
977
1024
  default: 50
978
1025
  },
979
1026
  offset: {
980
1027
  type: "number",
981
- description: "Results to skip over",
1028
+ description: "Skip initial matches for pagination.",
982
1029
  default: 0
983
1030
  }
984
1031
  }
@@ -986,17 +1033,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
986
1033
  },
987
1034
  {
988
1035
  name: "ftp_copy",
989
- description: "Duplicate files on server (SFTP only)",
1036
+ description: "Directly duplicate a file on the remote server without downloading and re-uploading. CRITICAL: Only supported on SFTP connections.",
990
1037
  inputSchema: {
991
1038
  type: "object",
992
1039
  properties: {
993
1040
  sourcePath: {
994
1041
  type: "string",
995
- description: "Source file path"
1042
+ description: "Qualified remote source path."
996
1043
  },
997
1044
  destPath: {
998
1045
  type: "string",
999
- description: "Destination file path"
1046
+ description: "Target remote destination path."
1000
1047
  }
1001
1048
  },
1002
1049
  required: ["sourcePath", "destPath"]
@@ -1004,18 +1051,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1004
1051
  },
1005
1052
  {
1006
1053
  name: "ftp_batch_upload",
1007
- description: "Upload multiple files at once",
1054
+ description: "Upload a collection of local files to remote destinations in a single operation. HIGHLY RECOMMENDED for multiple files to minimize connection handshaking overhead and drastically improve performance.",
1008
1055
  inputSchema: {
1009
1056
  type: "object",
1010
1057
  properties: {
1011
1058
  files: {
1012
1059
  type: "array",
1013
- description: "Array of {localPath, remotePath} objects",
1060
+ description: "A list of objects, each defining a local source and a remote destination.",
1014
1061
  items: {
1015
1062
  type: "object",
1016
1063
  properties: {
1017
- localPath: { type: "string" },
1018
- remotePath: { type: "string" }
1064
+ localPath: { type: "string", description: "Source path on your local machine." },
1065
+ remotePath: { type: "string", description: "Target path on the remote server." }
1019
1066
  },
1020
1067
  required: ["localPath", "remotePath"]
1021
1068
  }
@@ -1026,18 +1073,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1026
1073
  },
1027
1074
  {
1028
1075
  name: "ftp_batch_download",
1029
- description: "Download multiple files at once",
1076
+ description: "Download a selection of remote files to local destinations. HIGHLY RECOMMENDED for bulk downloads to leverage stable socket persistence.",
1030
1077
  inputSchema: {
1031
1078
  type: "object",
1032
1079
  properties: {
1033
1080
  files: {
1034
1081
  type: "array",
1035
- description: "Array of {remotePath, localPath} objects",
1082
+ description: "A list of objects mapping remote sources to local destinations.",
1036
1083
  items: {
1037
1084
  type: "object",
1038
1085
  properties: {
1039
- remotePath: { type: "string" },
1040
- localPath: { type: "string" }
1086
+ remotePath: { type: "string", description: "Source path on the remote server." },
1087
+ localPath: { type: "string", description: "Destination path on your local machine." }
1041
1088
  },
1042
1089
  required: ["remotePath", "localPath"]
1043
1090
  }
@@ -1048,33 +1095,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1048
1095
  },
1049
1096
  {
1050
1097
  name: "ftp_sync",
1051
- description: "Smart sync local remote (only changed files)",
1098
+ description: "Deploy entire project folders using smart synchronization. Analyzes local and remote directory trees and only transfers files that have changed in size or modification date. Automatically respects .gitignore and .ftpignore.",
1052
1099
  inputSchema: {
1053
1100
  type: "object",
1054
1101
  properties: {
1055
1102
  localPath: {
1056
1103
  type: "string",
1057
- description: "Local directory path"
1104
+ description: "The source directory on your local machine."
1058
1105
  },
1059
1106
  remotePath: {
1060
1107
  type: "string",
1061
- description: "Remote directory path"
1108
+ description: "The target destination directory on the remote server."
1062
1109
  },
1063
1110
  direction: {
1064
1111
  type: "string",
1065
- // QUAL-2: Only 'upload' is implemented; removed 'download'/'both' to avoid silent no-ops
1066
- description: "Sync direction: currently only 'upload' is supported",
1112
+ description: "Sync direction. Currently only 'upload' is implemented for safety.",
1067
1113
  enum: ["upload"],
1068
1114
  default: "upload"
1069
1115
  },
1070
1116
  dryRun: {
1071
1117
  type: "boolean",
1072
- description: "If true, simulates the sync without transferring files",
1118
+ description: "If true, logs exactly which files would be changed without performing any actual transfers.",
1073
1119
  default: false
1074
1120
  },
1075
1121
  useManifest: {
1076
1122
  type: "boolean",
1077
- description: "Use local manifest cache for faster deploys (drift-aware)",
1123
+ description: "Enables the local manifest cache for extremely fast delta detection on subsequent syncs.",
1078
1124
  default: true
1079
1125
  }
1080
1126
  },
@@ -1083,13 +1129,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1083
1129
  },
1084
1130
  {
1085
1131
  name: "ftp_disk_space",
1086
- description: "Check available space on server (SFTP only)",
1132
+ description: "Query the remote server for available disk space. CRITICAL: Only available on SFTP connections.",
1087
1133
  inputSchema: {
1088
1134
  type: "object",
1089
1135
  properties: {
1090
1136
  path: {
1091
1137
  type: "string",
1092
- description: "Remote path to check",
1138
+ description: "Remote filesystem path to check (defaults to server root).",
1093
1139
  default: "."
1094
1140
  }
1095
1141
  }
@@ -1097,17 +1143,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1097
1143
  },
1098
1144
  {
1099
1145
  name: "ftp_upload",
1100
- description: "Upload a file to the FTP/SFTP server",
1146
+ description: "Standard single-file transport to the remote server. For bulk transfers, favor ftp_batch_upload.",
1101
1147
  inputSchema: {
1102
1148
  type: "object",
1103
1149
  properties: {
1104
1150
  localPath: {
1105
1151
  type: "string",
1106
- description: "Local file path to upload"
1152
+ description: "Source path on your local machine."
1107
1153
  },
1108
1154
  remotePath: {
1109
1155
  type: "string",
1110
- description: "Remote destination path"
1156
+ description: "Target location on the remote server."
1111
1157
  }
1112
1158
  },
1113
1159
  required: ["localPath", "remotePath"]
@@ -1115,17 +1161,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1115
1161
  },
1116
1162
  {
1117
1163
  name: "ftp_download",
1118
- description: "Download a file from the FTP/SFTP server",
1164
+ description: "Standard single-file transport from the remote server. For bulk downloads, favor ftp_batch_download.",
1119
1165
  inputSchema: {
1120
1166
  type: "object",
1121
1167
  properties: {
1122
1168
  remotePath: {
1123
1169
  type: "string",
1124
- description: "Remote file path to download"
1170
+ description: "Source file location on the remote server."
1125
1171
  },
1126
1172
  localPath: {
1127
1173
  type: "string",
1128
- description: "Local destination path"
1174
+ description: "Destination location on your local machine."
1129
1175
  }
1130
1176
  },
1131
1177
  required: ["remotePath", "localPath"]
@@ -1133,13 +1179,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1133
1179
  },
1134
1180
  {
1135
1181
  name: "ftp_delete",
1136
- description: "Delete a file from the FTP/SFTP server",
1182
+ description: "Permanently remove a file from the remote server. Use with caution.",
1137
1183
  inputSchema: {
1138
1184
  type: "object",
1139
1185
  properties: {
1140
1186
  path: {
1141
1187
  type: "string",
1142
- description: "Remote file path to delete"
1188
+ description: "The remote file path to be destroyed."
1143
1189
  }
1144
1190
  },
1145
1191
  required: ["path"]
@@ -1147,13 +1193,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1147
1193
  },
1148
1194
  {
1149
1195
  name: "ftp_mkdir",
1150
- description: "Create a directory on the FTP/SFTP server",
1196
+ description: "Create a new directory structure on the remote server. Supports nested creation (mkdir -p).",
1151
1197
  inputSchema: {
1152
1198
  type: "object",
1153
1199
  properties: {
1154
1200
  path: {
1155
1201
  type: "string",
1156
- description: "Remote directory path to create"
1202
+ description: "The remote directory path to create."
1157
1203
  }
1158
1204
  },
1159
1205
  required: ["path"]
@@ -1161,17 +1207,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1161
1207
  },
1162
1208
  {
1163
1209
  name: "ftp_rmdir",
1164
- description: "Remove a directory from the FTP/SFTP server",
1210
+ description: "Delete a directory from the remote server.",
1165
1211
  inputSchema: {
1166
1212
  type: "object",
1167
1213
  properties: {
1168
1214
  path: {
1169
1215
  type: "string",
1170
- description: "Remote directory path to remove"
1216
+ description: "The remote directory path to remove."
1171
1217
  },
1172
1218
  recursive: {
1173
1219
  type: "boolean",
1174
- description: "Remove directory recursively",
1220
+ description: "If true, deletes all files and subdirectories within the target directory.",
1175
1221
  default: false
1176
1222
  }
1177
1223
  },
@@ -1180,17 +1226,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1180
1226
  },
1181
1227
  {
1182
1228
  name: "ftp_chmod",
1183
- description: "Change file permissions on the FTP/SFTP server (SFTP only)",
1229
+ description: "Change remote file permissions. CRITICAL: Only supported on SFTP connections.",
1184
1230
  inputSchema: {
1185
1231
  type: "object",
1186
1232
  properties: {
1187
1233
  path: {
1188
1234
  type: "string",
1189
- description: "Remote file path"
1235
+ description: "Remote file path to modify."
1190
1236
  },
1191
1237
  mode: {
1192
1238
  type: "string",
1193
- description: "Permission mode in octal (e.g., '755', '644')"
1239
+ description: "Standard octal permission string (e.g., '755' for executable, '644' for read-write)."
1194
1240
  }
1195
1241
  },
1196
1242
  required: ["path", "mode"]
@@ -1198,17 +1244,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1198
1244
  },
1199
1245
  {
1200
1246
  name: "ftp_rename",
1201
- description: "Rename or move a file on the FTP/SFTP server",
1247
+ description: "Move or rename files and directories on the remote server.",
1202
1248
  inputSchema: {
1203
1249
  type: "object",
1204
1250
  properties: {
1205
1251
  oldPath: {
1206
1252
  type: "string",
1207
- description: "Current file path"
1253
+ description: "Current remote location."
1208
1254
  },
1209
1255
  newPath: {
1210
1256
  type: "string",
1211
- description: "New file path"
1257
+ description: "New desired remote location."
1212
1258
  }
1213
1259
  },
1214
1260
  required: ["oldPath", "newPath"]
@@ -1216,13 +1262,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1216
1262
  },
1217
1263
  {
1218
1264
  name: "ftp_rollback",
1219
- description: "Rollback a previous transaction using its snapshot",
1265
+ description: "Undo a previous file mutation by restoring it from a system-generated snapshot. This provides safety during complex refactoring tasks.",
1220
1266
  inputSchema: {
1221
1267
  type: "object",
1222
1268
  properties: {
1223
1269
  transactionId: {
1224
1270
  type: "string",
1225
- description: "Transaction ID to rollback (e.g., tx_1234567890_abcd)"
1271
+ description: "Specific ID associated with the snapshot (e.g., 'tx_12345'). Get this from ftp_list_transactions."
1226
1272
  }
1227
1273
  },
1228
1274
  required: ["transactionId"]
@@ -1230,7 +1276,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1230
1276
  },
1231
1277
  {
1232
1278
  name: "ftp_list_transactions",
1233
- description: "List available rollback transactions",
1279
+ description: "Expose all recent mutations currently stored in the system's SnapshotManager. Essential for planning rollbacks.",
1234
1280
  inputSchema: {
1235
1281
  type: "object",
1236
1282
  properties: {}
@@ -1238,13 +1284,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1238
1284
  },
1239
1285
  {
1240
1286
  name: "ftp_probe_capabilities",
1241
- description: "Probe the server to detect supported features (chmod, symlinks, disk space, etc.)",
1287
+ description: "Scan the remote server to determine supported filesystem features (e.g., chmod availability, symlink support, disk space querying). Helpful for troubleshooting capabilities.",
1242
1288
  inputSchema: {
1243
1289
  type: "object",
1244
1290
  properties: {
1245
1291
  testPath: {
1246
1292
  type: "string",
1247
- description: "A safe remote directory to run tests in (defaults to current directory)",
1293
+ description: "A safe, ephemeral directory to run capability benchmarks in.",
1248
1294
  default: "."
1249
1295
  }
1250
1296
  }
@@ -1252,7 +1298,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1252
1298
  },
1253
1299
  {
1254
1300
  name: "ftp_telemetry",
1255
- description: "Get connection health and performance telemetry",
1301
+ description: "Retrieve internal performance metrics, including average latency, connection pool health, and total processed bytes.",
1256
1302
  inputSchema: {
1257
1303
  type: "object",
1258
1304
  properties: {}
package/package.json CHANGED
@@ -1,122 +1,122 @@
1
- {
2
- "name": "ftp-mcp",
3
- "version": "1.3.0",
4
- "description": "Enterprise-grade MCP server providing heavily optimized FTP/SFTP operations with smart sync, patch/chunk streaming, caching, and explicit read-only security mappings for AI code assistants.",
5
- "type": "module",
6
- "main": "index.js",
7
- "bin": {
8
- "ftp-mcp": "./index.js",
9
- "ftp-mcp-server": "./index.js"
10
- },
11
- "files": [
12
- "index.js",
13
- "policy-engine.js",
14
- "snapshot-manager.js",
15
- "sync-manifest.js",
16
- "README.md",
17
- "LICENSE",
18
- ".ftpconfig.example"
19
- ],
20
- "scripts": {
21
- "start": "node index.js",
22
- "test": "vitest run"
23
- },
24
- "keywords": [
25
- "mcp",
26
- "mcp-protocol",
27
- "model-context-protocol",
28
- "ftp",
29
- "ftp-client",
30
- "sftp",
31
- "sftp-client",
32
- "ssh",
33
- "ssh2",
34
- "scp",
35
- "ssh-agent",
36
- "rsync",
37
- "file-transfer",
38
- "file-manager",
39
- "sync",
40
- "diff",
41
- "patch",
42
- "git",
43
- "gitignore",
44
- "deploy",
45
- "deployment",
46
- "devops",
47
- "continuous-deployment",
48
- "automation",
49
- "ai",
50
- "ai-tools",
51
- "llm",
52
- "assistant",
53
- "agentic",
54
- "code-assistant",
55
- "tool-call",
56
- "json-rpc",
57
- "server",
58
- "tools",
59
- "claude",
60
- "claude-mcp",
61
- "claude-ftp",
62
- "cline",
63
- "cline-mcp",
64
- "cline-ftp",
65
- "cursor",
66
- "cursor-mcp",
67
- "cursor-ftp",
68
- "windsurf",
69
- "windsurf-mcp",
70
- "roo",
71
- "roo-code",
72
- "roo-mcp",
73
- "copilot",
74
- "github-copilot",
75
- "amp",
76
- "amp-mcp",
77
- "openai",
78
- "anthropic",
79
- "gemini",
80
- "gemini-mcp",
81
- "mcp-server",
82
- "mcp-tools",
83
- "server-tool",
84
- "remote",
85
- "remote-fs",
86
- "vfs"
87
- ],
88
- "engines": {
89
- "node": ">=18.0.0"
90
- },
91
- "author": {
92
- "name": "Kynlo Akari",
93
- "url": "https://github.com/Kynlos/"
94
- },
95
- "license": "MIT",
96
- "repository": {
97
- "type": "git",
98
- "url": "https://github.com/Kynlos/ftp-mcp.git"
99
- },
100
- "homepage": "https://github.com/Kynlos/ftp-mcp#readme",
101
- "bugs": {
102
- "url": "https://github.com/Kynlos/ftp-mcp/issues"
103
- },
104
- "publishConfig": {
105
- "access": "public"
106
- },
107
- "dependencies": {
108
- "@clack/prompts": "^1.2.0",
109
- "@modelcontextprotocol/sdk": "^1.0.4",
110
- "basic-ftp": "^5.0.5",
111
- "diff": "^8.0.4",
112
- "dotenv": "^17.4.0",
113
- "ignore": "^7.0.5",
114
- "minimatch": "^10.0.3",
115
- "ssh2-sftp-client": "^11.0.0",
116
- "zod": "^4.3.6"
117
- },
118
- "devDependencies": {
119
- "ftp-srv": "^4.6.3",
120
- "vitest": "^4.1.2"
121
- }
122
- }
1
+ {
2
+ "name": "ftp-mcp",
3
+ "version": "1.3.1",
4
+ "description": "Enterprise-grade MCP server providing heavily optimized FTP/SFTP operations with smart sync, patch/chunk streaming, caching, and explicit read-only security mappings for AI code assistants.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "ftp-mcp": "./index.js",
9
+ "ftp-mcp-server": "./index.js"
10
+ },
11
+ "files": [
12
+ "index.js",
13
+ "policy-engine.js",
14
+ "snapshot-manager.js",
15
+ "sync-manifest.js",
16
+ "README.md",
17
+ "LICENSE",
18
+ ".ftpconfig.example"
19
+ ],
20
+ "scripts": {
21
+ "start": "node index.js",
22
+ "test": "vitest run"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "mcp-protocol",
27
+ "model-context-protocol",
28
+ "ftp",
29
+ "ftp-client",
30
+ "sftp",
31
+ "sftp-client",
32
+ "ssh",
33
+ "ssh2",
34
+ "scp",
35
+ "ssh-agent",
36
+ "rsync",
37
+ "file-transfer",
38
+ "file-manager",
39
+ "sync",
40
+ "diff",
41
+ "patch",
42
+ "git",
43
+ "gitignore",
44
+ "deploy",
45
+ "deployment",
46
+ "devops",
47
+ "continuous-deployment",
48
+ "automation",
49
+ "ai",
50
+ "ai-tools",
51
+ "llm",
52
+ "assistant",
53
+ "agentic",
54
+ "code-assistant",
55
+ "tool-call",
56
+ "json-rpc",
57
+ "server",
58
+ "tools",
59
+ "claude",
60
+ "claude-mcp",
61
+ "claude-ftp",
62
+ "cline",
63
+ "cline-mcp",
64
+ "cline-ftp",
65
+ "cursor",
66
+ "cursor-mcp",
67
+ "cursor-ftp",
68
+ "windsurf",
69
+ "windsurf-mcp",
70
+ "roo",
71
+ "roo-code",
72
+ "roo-mcp",
73
+ "copilot",
74
+ "github-copilot",
75
+ "amp",
76
+ "amp-mcp",
77
+ "openai",
78
+ "anthropic",
79
+ "gemini",
80
+ "gemini-mcp",
81
+ "mcp-server",
82
+ "mcp-tools",
83
+ "server-tool",
84
+ "remote",
85
+ "remote-fs",
86
+ "vfs"
87
+ ],
88
+ "engines": {
89
+ "node": ">=18.0.0"
90
+ },
91
+ "author": {
92
+ "name": "Kynlo Akari",
93
+ "url": "https://github.com/Kynlos/"
94
+ },
95
+ "license": "MIT",
96
+ "repository": {
97
+ "type": "git",
98
+ "url": "https://github.com/Kynlos/ftp-mcp.git"
99
+ },
100
+ "homepage": "https://github.com/Kynlos/ftp-mcp#readme",
101
+ "bugs": {
102
+ "url": "https://github.com/Kynlos/ftp-mcp/issues"
103
+ },
104
+ "publishConfig": {
105
+ "access": "public"
106
+ },
107
+ "dependencies": {
108
+ "@clack/prompts": "^1.2.0",
109
+ "@modelcontextprotocol/sdk": "^1.0.4",
110
+ "basic-ftp": "^5.0.5",
111
+ "diff": "^8.0.4",
112
+ "dotenv": "^17.4.0",
113
+ "ignore": "^7.0.5",
114
+ "minimatch": "^10.0.3",
115
+ "ssh2-sftp-client": "^11.0.0",
116
+ "zod": "^4.3.6"
117
+ },
118
+ "devDependencies": {
119
+ "ftp-srv": "^4.6.3",
120
+ "vitest": "^4.1.2"
121
+ }
122
+ }