@prmichaelsen/acp-mcp 0.7.1 → 1.0.0
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/CHANGELOG.md +57 -0
- package/README.md +121 -41
- package/agent/progress.yaml +29 -5
- package/dist/server-factory.js +2 -496
- package/dist/server-factory.js.map +4 -4
- package/dist/server.js +2 -496
- package/dist/server.js.map +4 -4
- package/dist/utils/ssh-connection.d.ts +0 -40
- package/package.json +1 -1
- package/src/server-factory.ts +2 -12
- package/src/server.ts +2 -11
- package/src/utils/ssh-connection.ts +1 -304
- package/dist/tools/acp-remote-list-files.d.ts +0 -16
- package/dist/tools/acp-remote-read-file.d.ts +0 -16
- package/dist/tools/acp-remote-write-file.d.ts +0 -16
- package/dist/types/file-entry.d.ts +0 -88
- package/src/tools/acp-remote-list-files.ts +0 -100
- package/src/tools/acp-remote-read-file.ts +0 -94
- package/src/tools/acp-remote-write-file.ts +0 -107
- package/src/types/file-entry.ts +0 -123
package/dist/server.js
CHANGED
|
@@ -433,70 +433,6 @@ function loadSSHPrivateKey() {
|
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
-
// src/tools/acp-remote-list-files.ts
|
|
437
|
-
var acpRemoteListFilesTool = {
|
|
438
|
-
name: "acp_remote_list_files",
|
|
439
|
-
description: "List files and directories in a specified path on the remote machine via SSH. Returns comprehensive metadata including permissions, timestamps, size, and ownership. Includes hidden files by default.",
|
|
440
|
-
inputSchema: {
|
|
441
|
-
type: "object",
|
|
442
|
-
properties: {
|
|
443
|
-
path: {
|
|
444
|
-
type: "string",
|
|
445
|
-
description: "The directory path to list files from"
|
|
446
|
-
},
|
|
447
|
-
recursive: {
|
|
448
|
-
type: "boolean",
|
|
449
|
-
description: "Whether to list files recursively",
|
|
450
|
-
default: false
|
|
451
|
-
},
|
|
452
|
-
includeHidden: {
|
|
453
|
-
type: "boolean",
|
|
454
|
-
description: "Whether to include hidden files (starting with .)",
|
|
455
|
-
default: true
|
|
456
|
-
}
|
|
457
|
-
},
|
|
458
|
-
required: ["path"]
|
|
459
|
-
}
|
|
460
|
-
};
|
|
461
|
-
async function handleAcpRemoteListFiles(args, sshConnection) {
|
|
462
|
-
const { path, recursive = false, includeHidden = true } = args;
|
|
463
|
-
try {
|
|
464
|
-
const entries = await listRemoteFiles(sshConnection, path, recursive, includeHidden);
|
|
465
|
-
const output = JSON.stringify(entries, null, 2);
|
|
466
|
-
return {
|
|
467
|
-
content: [
|
|
468
|
-
{
|
|
469
|
-
type: "text",
|
|
470
|
-
text: output
|
|
471
|
-
}
|
|
472
|
-
]
|
|
473
|
-
};
|
|
474
|
-
} catch (error) {
|
|
475
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
476
|
-
return {
|
|
477
|
-
content: [
|
|
478
|
-
{
|
|
479
|
-
type: "text",
|
|
480
|
-
text: `Error listing remote files: ${errorMessage}`
|
|
481
|
-
}
|
|
482
|
-
]
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
async function listRemoteFiles(ssh, dirPath, recursive, includeHidden) {
|
|
487
|
-
const entries = await ssh.listFiles(dirPath, includeHidden);
|
|
488
|
-
const allEntries = [...entries];
|
|
489
|
-
if (recursive) {
|
|
490
|
-
for (const entry of entries) {
|
|
491
|
-
if (entry.type === "directory") {
|
|
492
|
-
const subEntries = await listRemoteFiles(ssh, entry.path, recursive, includeHidden);
|
|
493
|
-
allEntries.push(...subEntries);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
return allEntries;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
436
|
// src/utils/logger.ts
|
|
501
437
|
var LOG_LEVELS = {
|
|
502
438
|
error: 0,
|
|
@@ -746,195 +682,8 @@ async function executeWithProgress(command, cwd, sshConnection, progressToken, s
|
|
|
746
682
|
};
|
|
747
683
|
}
|
|
748
684
|
|
|
749
|
-
// src/tools/acp-remote-read-file.ts
|
|
750
|
-
var acpRemoteReadFileTool = {
|
|
751
|
-
name: "acp_remote_read_file",
|
|
752
|
-
description: "Read file contents from the remote machine via SSH",
|
|
753
|
-
inputSchema: {
|
|
754
|
-
type: "object",
|
|
755
|
-
properties: {
|
|
756
|
-
path: {
|
|
757
|
-
type: "string",
|
|
758
|
-
description: "Absolute path to file"
|
|
759
|
-
},
|
|
760
|
-
encoding: {
|
|
761
|
-
type: "string",
|
|
762
|
-
description: "File encoding (default: utf-8)",
|
|
763
|
-
default: "utf-8",
|
|
764
|
-
enum: ["utf-8", "ascii", "base64"]
|
|
765
|
-
},
|
|
766
|
-
maxSize: {
|
|
767
|
-
type: "number",
|
|
768
|
-
description: "Max file size in bytes (default: 1MB)",
|
|
769
|
-
default: 1048576
|
|
770
|
-
}
|
|
771
|
-
},
|
|
772
|
-
required: ["path"]
|
|
773
|
-
}
|
|
774
|
-
};
|
|
775
|
-
async function handleAcpRemoteReadFile(args, sshConnection) {
|
|
776
|
-
const { path, encoding = "utf-8", maxSize = 1048576 } = args;
|
|
777
|
-
logger.debug("Reading remote file", { path, encoding, maxSize });
|
|
778
|
-
try {
|
|
779
|
-
const result = await sshConnection.readFile(path, encoding, maxSize);
|
|
780
|
-
logger.debug("File read successful", { path, size: result.size });
|
|
781
|
-
const output = {
|
|
782
|
-
content: result.content,
|
|
783
|
-
size: result.size,
|
|
784
|
-
encoding: result.encoding
|
|
785
|
-
};
|
|
786
|
-
return {
|
|
787
|
-
content: [
|
|
788
|
-
{
|
|
789
|
-
type: "text",
|
|
790
|
-
text: JSON.stringify(output, null, 2)
|
|
791
|
-
}
|
|
792
|
-
]
|
|
793
|
-
};
|
|
794
|
-
} catch (error) {
|
|
795
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
796
|
-
logger.error("File read error", { path, error: errorMessage });
|
|
797
|
-
return {
|
|
798
|
-
content: [
|
|
799
|
-
{
|
|
800
|
-
type: "text",
|
|
801
|
-
text: JSON.stringify({
|
|
802
|
-
error: errorMessage,
|
|
803
|
-
content: "",
|
|
804
|
-
size: 0,
|
|
805
|
-
encoding
|
|
806
|
-
}, null, 2)
|
|
807
|
-
}
|
|
808
|
-
]
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// src/tools/acp-remote-write-file.ts
|
|
814
|
-
var acpRemoteWriteFileTool = {
|
|
815
|
-
name: "acp_remote_write_file",
|
|
816
|
-
description: "Write file contents to the remote machine via SSH",
|
|
817
|
-
inputSchema: {
|
|
818
|
-
type: "object",
|
|
819
|
-
properties: {
|
|
820
|
-
path: {
|
|
821
|
-
type: "string",
|
|
822
|
-
description: "Absolute path to file"
|
|
823
|
-
},
|
|
824
|
-
content: {
|
|
825
|
-
type: "string",
|
|
826
|
-
description: "File contents to write"
|
|
827
|
-
},
|
|
828
|
-
encoding: {
|
|
829
|
-
type: "string",
|
|
830
|
-
description: "File encoding (default: utf-8)",
|
|
831
|
-
default: "utf-8"
|
|
832
|
-
},
|
|
833
|
-
createDirs: {
|
|
834
|
-
type: "boolean",
|
|
835
|
-
description: "Create parent directories if they don't exist (default: false)",
|
|
836
|
-
default: false
|
|
837
|
-
},
|
|
838
|
-
backup: {
|
|
839
|
-
type: "boolean",
|
|
840
|
-
description: "Backup existing file before overwriting (default: false)",
|
|
841
|
-
default: false
|
|
842
|
-
}
|
|
843
|
-
},
|
|
844
|
-
required: ["path", "content"]
|
|
845
|
-
}
|
|
846
|
-
};
|
|
847
|
-
async function handleAcpRemoteWriteFile(args, sshConnection) {
|
|
848
|
-
const { path, content, encoding = "utf-8", createDirs = false, backup = false } = args;
|
|
849
|
-
logger.debug("Writing remote file", { path, contentSize: content.length, encoding, createDirs, backup });
|
|
850
|
-
try {
|
|
851
|
-
const result = await sshConnection.writeFile(path, content, {
|
|
852
|
-
encoding,
|
|
853
|
-
createDirs,
|
|
854
|
-
backup
|
|
855
|
-
});
|
|
856
|
-
logger.debug("File write successful", { path, bytesWritten: result.bytesWritten, backupPath: result.backupPath });
|
|
857
|
-
const output = {
|
|
858
|
-
success: result.success,
|
|
859
|
-
bytesWritten: result.bytesWritten,
|
|
860
|
-
backupPath: result.backupPath
|
|
861
|
-
};
|
|
862
|
-
return {
|
|
863
|
-
content: [
|
|
864
|
-
{
|
|
865
|
-
type: "text",
|
|
866
|
-
text: JSON.stringify(output, null, 2)
|
|
867
|
-
}
|
|
868
|
-
]
|
|
869
|
-
};
|
|
870
|
-
} catch (error) {
|
|
871
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
872
|
-
logger.error("File write error", { path, error: errorMessage });
|
|
873
|
-
return {
|
|
874
|
-
content: [
|
|
875
|
-
{
|
|
876
|
-
type: "text",
|
|
877
|
-
text: JSON.stringify({
|
|
878
|
-
success: false,
|
|
879
|
-
bytesWritten: 0,
|
|
880
|
-
error: errorMessage
|
|
881
|
-
}, null, 2)
|
|
882
|
-
}
|
|
883
|
-
]
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
|
|
888
685
|
// src/utils/ssh-connection.ts
|
|
889
686
|
import { Client } from "ssh2";
|
|
890
|
-
|
|
891
|
-
// src/types/file-entry.ts
|
|
892
|
-
function modeToPermissionString(mode) {
|
|
893
|
-
const perms = [
|
|
894
|
-
mode & 256 ? "r" : "-",
|
|
895
|
-
mode & 128 ? "w" : "-",
|
|
896
|
-
mode & 64 ? "x" : "-",
|
|
897
|
-
mode & 32 ? "r" : "-",
|
|
898
|
-
mode & 16 ? "w" : "-",
|
|
899
|
-
mode & 8 ? "x" : "-",
|
|
900
|
-
mode & 4 ? "r" : "-",
|
|
901
|
-
mode & 2 ? "w" : "-",
|
|
902
|
-
mode & 1 ? "x" : "-"
|
|
903
|
-
];
|
|
904
|
-
return perms.join("");
|
|
905
|
-
}
|
|
906
|
-
function parsePermissions(mode) {
|
|
907
|
-
return {
|
|
908
|
-
mode,
|
|
909
|
-
string: modeToPermissionString(mode),
|
|
910
|
-
owner: {
|
|
911
|
-
read: (mode & 256) !== 0,
|
|
912
|
-
write: (mode & 128) !== 0,
|
|
913
|
-
execute: (mode & 64) !== 0
|
|
914
|
-
},
|
|
915
|
-
group: {
|
|
916
|
-
read: (mode & 32) !== 0,
|
|
917
|
-
write: (mode & 16) !== 0,
|
|
918
|
-
execute: (mode & 8) !== 0
|
|
919
|
-
},
|
|
920
|
-
others: {
|
|
921
|
-
read: (mode & 4) !== 0,
|
|
922
|
-
write: (mode & 2) !== 0,
|
|
923
|
-
execute: (mode & 1) !== 0
|
|
924
|
-
}
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
function getFileType(stats) {
|
|
928
|
-
if (stats.isDirectory())
|
|
929
|
-
return "directory";
|
|
930
|
-
if (stats.isFile())
|
|
931
|
-
return "file";
|
|
932
|
-
if (stats.isSymbolicLink())
|
|
933
|
-
return "symlink";
|
|
934
|
-
return "other";
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// src/utils/ssh-connection.ts
|
|
938
687
|
var SSHConnectionManager = class {
|
|
939
688
|
client;
|
|
940
689
|
config;
|
|
@@ -1113,243 +862,6 @@ var SSHConnectionManager = class {
|
|
|
1113
862
|
});
|
|
1114
863
|
});
|
|
1115
864
|
}
|
|
1116
|
-
/**
|
|
1117
|
-
* Get SFTP wrapper for file operations
|
|
1118
|
-
*/
|
|
1119
|
-
async getSFTP() {
|
|
1120
|
-
if (!this.connected) {
|
|
1121
|
-
await this.connect();
|
|
1122
|
-
}
|
|
1123
|
-
return new Promise((resolve, reject) => {
|
|
1124
|
-
this.client.sftp((err, sftp) => {
|
|
1125
|
-
if (err) {
|
|
1126
|
-
reject(err);
|
|
1127
|
-
} else {
|
|
1128
|
-
resolve(sftp);
|
|
1129
|
-
}
|
|
1130
|
-
});
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
/**
|
|
1134
|
-
* List files in a directory with comprehensive metadata
|
|
1135
|
-
* Uses hybrid approach: shell ls for filenames (includes hidden), SFTP stat for metadata
|
|
1136
|
-
*
|
|
1137
|
-
* @param path - Directory path to list
|
|
1138
|
-
* @param includeHidden - Whether to include hidden files (default: true)
|
|
1139
|
-
* @returns Array of FileEntry objects with complete metadata
|
|
1140
|
-
*/
|
|
1141
|
-
async listFiles(path, includeHidden = true) {
|
|
1142
|
-
const startTime = Date.now();
|
|
1143
|
-
logger.debug("Listing files", { path, includeHidden });
|
|
1144
|
-
try {
|
|
1145
|
-
const lsFlag = includeHidden ? "-A" : "";
|
|
1146
|
-
const command = `ls ${lsFlag} -1 "${path}" 2>/dev/null`;
|
|
1147
|
-
const result = await this.execWithTimeout(command, 10);
|
|
1148
|
-
if (result.exitCode !== 0) {
|
|
1149
|
-
throw new Error(`ls command failed: ${result.stderr}`);
|
|
1150
|
-
}
|
|
1151
|
-
const filenames = result.stdout.split("\n").map((f) => f.trim()).filter((f) => f !== "" && f !== "." && f !== "..");
|
|
1152
|
-
logger.debug("Filenames retrieved via shell", {
|
|
1153
|
-
path,
|
|
1154
|
-
count: filenames.length,
|
|
1155
|
-
method: "shell"
|
|
1156
|
-
});
|
|
1157
|
-
const sftp = await this.getSFTP();
|
|
1158
|
-
const entries = [];
|
|
1159
|
-
for (const filename of filenames) {
|
|
1160
|
-
const fullPath = `${path}/${filename}`.replace(/\/+/g, "/");
|
|
1161
|
-
try {
|
|
1162
|
-
const stats = await new Promise((resolve, reject) => {
|
|
1163
|
-
sftp.stat(fullPath, (err, stats2) => {
|
|
1164
|
-
if (err)
|
|
1165
|
-
reject(err);
|
|
1166
|
-
else
|
|
1167
|
-
resolve(stats2);
|
|
1168
|
-
});
|
|
1169
|
-
});
|
|
1170
|
-
entries.push({
|
|
1171
|
-
name: filename,
|
|
1172
|
-
path: fullPath,
|
|
1173
|
-
type: getFileType(stats),
|
|
1174
|
-
size: stats.size,
|
|
1175
|
-
permissions: parsePermissions(stats.mode),
|
|
1176
|
-
owner: {
|
|
1177
|
-
uid: stats.uid,
|
|
1178
|
-
gid: stats.gid
|
|
1179
|
-
},
|
|
1180
|
-
timestamps: {
|
|
1181
|
-
accessed: new Date(stats.atime * 1e3).toISOString(),
|
|
1182
|
-
modified: new Date(stats.mtime * 1e3).toISOString()
|
|
1183
|
-
}
|
|
1184
|
-
});
|
|
1185
|
-
} catch (error) {
|
|
1186
|
-
logger.warn("Failed to stat file, skipping", {
|
|
1187
|
-
path: fullPath,
|
|
1188
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
const duration = Date.now() - startTime;
|
|
1193
|
-
logger.debug("Files listed successfully", {
|
|
1194
|
-
path,
|
|
1195
|
-
count: entries.length,
|
|
1196
|
-
duration: `${duration}ms`,
|
|
1197
|
-
method: "hybrid"
|
|
1198
|
-
});
|
|
1199
|
-
return entries;
|
|
1200
|
-
} catch (error) {
|
|
1201
|
-
logger.warn("Shell ls command failed, falling back to SFTP readdir", {
|
|
1202
|
-
path,
|
|
1203
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1204
|
-
});
|
|
1205
|
-
return this.listFilesViaSFTP(path, includeHidden);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Fallback method: List files using SFTP readdir (may miss hidden files)
|
|
1210
|
-
* @private
|
|
1211
|
-
*/
|
|
1212
|
-
async listFilesViaSFTP(path, includeHidden) {
|
|
1213
|
-
const sftp = await this.getSFTP();
|
|
1214
|
-
return new Promise((resolve, reject) => {
|
|
1215
|
-
sftp.readdir(path, (err, list) => {
|
|
1216
|
-
if (err) {
|
|
1217
|
-
logger.error("SFTP readdir failed", { path, error: err.message });
|
|
1218
|
-
reject(err);
|
|
1219
|
-
return;
|
|
1220
|
-
}
|
|
1221
|
-
let entries = list.map((item) => ({
|
|
1222
|
-
name: item.filename,
|
|
1223
|
-
path: `${path}/${item.filename}`.replace(/\/+/g, "/"),
|
|
1224
|
-
type: getFileType(item.attrs),
|
|
1225
|
-
size: item.attrs.size,
|
|
1226
|
-
permissions: parsePermissions(item.attrs.mode),
|
|
1227
|
-
owner: {
|
|
1228
|
-
uid: item.attrs.uid,
|
|
1229
|
-
gid: item.attrs.gid
|
|
1230
|
-
},
|
|
1231
|
-
timestamps: {
|
|
1232
|
-
accessed: new Date(item.attrs.atime * 1e3).toISOString(),
|
|
1233
|
-
modified: new Date(item.attrs.mtime * 1e3).toISOString()
|
|
1234
|
-
}
|
|
1235
|
-
}));
|
|
1236
|
-
if (!includeHidden) {
|
|
1237
|
-
entries = entries.filter((e) => !e.name.startsWith("."));
|
|
1238
|
-
}
|
|
1239
|
-
logger.debug("Files listed via SFTP fallback", {
|
|
1240
|
-
path,
|
|
1241
|
-
count: entries.length,
|
|
1242
|
-
method: "sftp",
|
|
1243
|
-
note: "Hidden files may be missing (SFTP limitation)"
|
|
1244
|
-
});
|
|
1245
|
-
resolve(entries);
|
|
1246
|
-
});
|
|
1247
|
-
});
|
|
1248
|
-
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Read file contents from remote machine
|
|
1251
|
-
*/
|
|
1252
|
-
async readFile(path, encoding = "utf-8", maxSize = 1048576) {
|
|
1253
|
-
const startTime = Date.now();
|
|
1254
|
-
logger.fileOperation("read", path, { encoding, maxSize });
|
|
1255
|
-
const sftp = await this.getSFTP();
|
|
1256
|
-
return new Promise((resolve, reject) => {
|
|
1257
|
-
sftp.stat(path, (err, stats) => {
|
|
1258
|
-
if (err) {
|
|
1259
|
-
logger.error("File stat failed", { path, error: err.message });
|
|
1260
|
-
reject(new Error(`File not found or inaccessible: ${path}`));
|
|
1261
|
-
return;
|
|
1262
|
-
}
|
|
1263
|
-
logger.debug("File stat retrieved", { path, size: stats.size });
|
|
1264
|
-
if (stats.size > maxSize) {
|
|
1265
|
-
logger.warn("File too large", { path, size: stats.size, maxSize });
|
|
1266
|
-
reject(new Error(`File too large: ${stats.size} bytes (max: ${maxSize} bytes)`));
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
sftp.readFile(path, { encoding }, (err2, data) => {
|
|
1270
|
-
if (err2) {
|
|
1271
|
-
logger.error("File read failed", { path, error: err2.message });
|
|
1272
|
-
reject(new Error(`Failed to read file: ${err2.message}`));
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
const duration = Date.now() - startTime;
|
|
1276
|
-
logger.debug("File read completed", { path, size: stats.size, duration: `${duration}ms` });
|
|
1277
|
-
resolve({
|
|
1278
|
-
content: data.toString(),
|
|
1279
|
-
size: stats.size,
|
|
1280
|
-
encoding
|
|
1281
|
-
});
|
|
1282
|
-
});
|
|
1283
|
-
});
|
|
1284
|
-
});
|
|
1285
|
-
}
|
|
1286
|
-
/**
|
|
1287
|
-
* Write file contents to remote machine
|
|
1288
|
-
*/
|
|
1289
|
-
async writeFile(path, content, options = {}) {
|
|
1290
|
-
const { encoding = "utf-8", createDirs = false, backup = false } = options;
|
|
1291
|
-
const startTime = Date.now();
|
|
1292
|
-
logger.fileOperation("write", path, {
|
|
1293
|
-
contentSize: content.length,
|
|
1294
|
-
encoding,
|
|
1295
|
-
createDirs,
|
|
1296
|
-
backup
|
|
1297
|
-
});
|
|
1298
|
-
const sftp = await this.getSFTP();
|
|
1299
|
-
return new Promise((resolve, reject) => {
|
|
1300
|
-
const writeOperation = () => {
|
|
1301
|
-
if (backup) {
|
|
1302
|
-
const backupPath = `${path}.backup`;
|
|
1303
|
-
sftp.rename(path, backupPath, (err) => {
|
|
1304
|
-
if (err && err.message !== "No such file") {
|
|
1305
|
-
reject(new Error(`Failed to create backup: ${err.message}`));
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
performWrite(backupPath);
|
|
1309
|
-
});
|
|
1310
|
-
} else {
|
|
1311
|
-
performWrite();
|
|
1312
|
-
}
|
|
1313
|
-
};
|
|
1314
|
-
const performWrite = (backupPath) => {
|
|
1315
|
-
const buffer = Buffer.from(content, encoding);
|
|
1316
|
-
const tempPath = `${path}.tmp`;
|
|
1317
|
-
sftp.writeFile(tempPath, buffer, (err) => {
|
|
1318
|
-
if (err) {
|
|
1319
|
-
reject(new Error(`Failed to write file: ${err.message}`));
|
|
1320
|
-
return;
|
|
1321
|
-
}
|
|
1322
|
-
sftp.rename(tempPath, path, (err2) => {
|
|
1323
|
-
if (err2) {
|
|
1324
|
-
logger.error("File rename failed", { tempPath, path, error: err2.message });
|
|
1325
|
-
reject(new Error(`Failed to rename temp file: ${err2.message}`));
|
|
1326
|
-
return;
|
|
1327
|
-
}
|
|
1328
|
-
const duration = Date.now() - startTime;
|
|
1329
|
-
logger.debug("File write completed", {
|
|
1330
|
-
path,
|
|
1331
|
-
bytesWritten: buffer.length,
|
|
1332
|
-
duration: `${duration}ms`,
|
|
1333
|
-
backupPath
|
|
1334
|
-
});
|
|
1335
|
-
resolve({
|
|
1336
|
-
success: true,
|
|
1337
|
-
bytesWritten: buffer.length,
|
|
1338
|
-
backupPath
|
|
1339
|
-
});
|
|
1340
|
-
});
|
|
1341
|
-
});
|
|
1342
|
-
};
|
|
1343
|
-
if (createDirs) {
|
|
1344
|
-
const dirPath = path.substring(0, path.lastIndexOf("/"));
|
|
1345
|
-
this.exec(`mkdir -p ${dirPath}`).then(() => {
|
|
1346
|
-
writeOperation();
|
|
1347
|
-
}).catch(reject);
|
|
1348
|
-
} else {
|
|
1349
|
-
writeOperation();
|
|
1350
|
-
}
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
865
|
/**
|
|
1354
866
|
* Wrap command to source shell configuration files
|
|
1355
867
|
* This ensures PATH and other environment variables are properly set
|
|
@@ -1406,7 +918,7 @@ async function main() {
|
|
|
1406
918
|
);
|
|
1407
919
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1408
920
|
logger.debug("Tool discovery requested");
|
|
1409
|
-
const tools = [
|
|
921
|
+
const tools = [acpRemoteExecuteCommandTool];
|
|
1410
922
|
logger.debug(`Returning ${tools.length} tools`, { tools: tools.map((t) => t.name) });
|
|
1411
923
|
return { tools };
|
|
1412
924
|
});
|
|
@@ -1415,14 +927,8 @@ async function main() {
|
|
|
1415
927
|
logger.toolInvoked(request.params.name, request.params.arguments);
|
|
1416
928
|
try {
|
|
1417
929
|
let result;
|
|
1418
|
-
if (request.params.name === "
|
|
1419
|
-
result = await handleAcpRemoteListFiles(request.params.arguments, sshConnection);
|
|
1420
|
-
} else if (request.params.name === "acp_remote_execute_command") {
|
|
930
|
+
if (request.params.name === "acp_remote_execute_command") {
|
|
1421
931
|
result = await handleAcpRemoteExecuteCommand(request.params.arguments, sshConnection, extra, server);
|
|
1422
|
-
} else if (request.params.name === "acp_remote_read_file") {
|
|
1423
|
-
result = await handleAcpRemoteReadFile(request.params.arguments, sshConnection);
|
|
1424
|
-
} else if (request.params.name === "acp_remote_write_file") {
|
|
1425
|
-
result = await handleAcpRemoteWriteFile(request.params.arguments, sshConnection);
|
|
1426
932
|
} else {
|
|
1427
933
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
1428
934
|
}
|