@prmichaelsen/acp-mcp 0.4.1 → 0.5.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.
package/dist/server.js CHANGED
@@ -481,20 +481,127 @@ async function listRemoteFiles(ssh, dirPath, recursive) {
481
481
  const entries = await ssh.listFiles(dirPath);
482
482
  const files = [];
483
483
  for (const entry of entries) {
484
+ const fullPath = `${dirPath}/${entry.name}`.replace(/\/+/g, "/");
484
485
  if (entry.isDirectory) {
485
- files.push(`${entry.name}/`);
486
+ files.push(`${fullPath}/`);
486
487
  if (recursive) {
487
- const fullPath = `${dirPath}/${entry.name}`.replace(/\/+/g, "/");
488
488
  const subFiles = await listRemoteFiles(ssh, fullPath, recursive);
489
- files.push(...subFiles.map((f) => `${entry.name}/${f}`));
489
+ files.push(...subFiles);
490
490
  }
491
491
  } else {
492
- files.push(entry.name);
492
+ files.push(fullPath);
493
493
  }
494
494
  }
495
495
  return files.sort();
496
496
  }
497
497
 
498
+ // src/utils/logger.ts
499
+ var LOG_LEVELS = {
500
+ error: 0,
501
+ warn: 1,
502
+ info: 2,
503
+ debug: 3,
504
+ trace: 4
505
+ };
506
+ var Logger = class {
507
+ level;
508
+ enabled;
509
+ constructor() {
510
+ this.level = process.env.ACP_MCP_LOG_LEVEL || "info";
511
+ this.enabled = process.env.ACP_MCP_DEBUG === "true" || process.env.NODE_ENV === "development";
512
+ }
513
+ shouldLog(level) {
514
+ if (!this.enabled && level !== "error" && level !== "warn") {
515
+ return false;
516
+ }
517
+ return LOG_LEVELS[level] <= LOG_LEVELS[this.level];
518
+ }
519
+ formatMessage(level, message, data) {
520
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
521
+ const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
522
+ if (data !== void 0) {
523
+ const dataStr = typeof data === "object" ? JSON.stringify(data, null, 2) : String(data);
524
+ return `${prefix} ${message}
525
+ ${dataStr}`;
526
+ }
527
+ return `${prefix} ${message}`;
528
+ }
529
+ error(message, data) {
530
+ if (this.shouldLog("error")) {
531
+ console.error(this.formatMessage("error", message, data));
532
+ }
533
+ }
534
+ warn(message, data) {
535
+ if (this.shouldLog("warn")) {
536
+ console.error(this.formatMessage("warn", message, data));
537
+ }
538
+ }
539
+ info(message, data) {
540
+ if (this.shouldLog("info")) {
541
+ console.error(this.formatMessage("info", message, data));
542
+ }
543
+ }
544
+ debug(message, data) {
545
+ if (this.shouldLog("debug")) {
546
+ console.error(this.formatMessage("debug", message, data));
547
+ }
548
+ }
549
+ trace(message, data) {
550
+ if (this.shouldLog("trace")) {
551
+ console.error(this.formatMessage("trace", message, data));
552
+ }
553
+ }
554
+ /**
555
+ * Log tool invocation with parameters
556
+ */
557
+ toolInvoked(toolName, params, userId) {
558
+ this.info(`Tool invoked: ${toolName}`);
559
+ this.debug("Tool parameters", { tool: toolName, params, userId });
560
+ }
561
+ /**
562
+ * Log tool completion with result summary
563
+ */
564
+ toolCompleted(toolName, duration, resultSize) {
565
+ this.info(`Tool completed: ${toolName}`);
566
+ this.debug("Tool performance", { tool: toolName, duration: `${duration}ms`, resultSize });
567
+ }
568
+ /**
569
+ * Log tool failure with error details
570
+ */
571
+ toolFailed(toolName, error, params) {
572
+ this.error(`Tool execution failed: ${toolName}`, {
573
+ tool: toolName,
574
+ error: error.message,
575
+ stack: error.stack,
576
+ params
577
+ });
578
+ }
579
+ /**
580
+ * Log SSH command execution
581
+ */
582
+ sshCommand(command, cwd, timeout) {
583
+ this.debug("Executing SSH command", { command, cwd, timeout });
584
+ }
585
+ /**
586
+ * Log SSH command result
587
+ */
588
+ sshCommandResult(exitCode, duration, stdoutSize, stderrSize) {
589
+ this.debug("SSH command completed", {
590
+ exitCode,
591
+ duration: `${duration}ms`,
592
+ stdout: `${stdoutSize} bytes`,
593
+ stderr: `${stderrSize} bytes`
594
+ });
595
+ }
596
+ /**
597
+ * Log file operation
598
+ */
599
+ fileOperation(operation, path, details) {
600
+ this.info(`File operation: ${operation}`, { path, ...details });
601
+ }
602
+ };
603
+ var logger = new Logger();
604
+
498
605
  // src/tools/acp-remote-execute-command.ts
499
606
  var acpRemoteExecuteCommandTool = {
500
607
  name: "acp_remote_execute_command",
@@ -521,9 +628,16 @@ var acpRemoteExecuteCommandTool = {
521
628
  };
522
629
  async function handleAcpRemoteExecuteCommand(args, sshConnection) {
523
630
  const { command, cwd, timeout = 30 } = args;
631
+ logger.debug("Executing remote command", { command, cwd, timeout });
524
632
  try {
525
633
  const fullCommand = cwd ? `cd ${cwd} && ${command}` : command;
526
634
  const result = await sshConnection.execWithTimeout(fullCommand, timeout);
635
+ logger.debug("Command execution result", {
636
+ exitCode: result.exitCode,
637
+ timedOut: result.timedOut,
638
+ stdoutLength: result.stdout.length,
639
+ stderrLength: result.stderr.length
640
+ });
527
641
  const output = {
528
642
  stdout: result.stdout,
529
643
  stderr: result.stderr,
@@ -540,6 +654,7 @@ async function handleAcpRemoteExecuteCommand(args, sshConnection) {
540
654
  };
541
655
  } catch (error) {
542
656
  const errorMessage = error instanceof Error ? error.message : String(error);
657
+ logger.error("Command execution error", { command, error: errorMessage });
543
658
  return {
544
659
  content: [
545
660
  {
@@ -584,8 +699,10 @@ var acpRemoteReadFileTool = {
584
699
  };
585
700
  async function handleAcpRemoteReadFile(args, sshConnection) {
586
701
  const { path, encoding = "utf-8", maxSize = 1048576 } = args;
702
+ logger.debug("Reading remote file", { path, encoding, maxSize });
587
703
  try {
588
704
  const result = await sshConnection.readFile(path, encoding, maxSize);
705
+ logger.debug("File read successful", { path, size: result.size });
589
706
  const output = {
590
707
  content: result.content,
591
708
  size: result.size,
@@ -601,6 +718,7 @@ async function handleAcpRemoteReadFile(args, sshConnection) {
601
718
  };
602
719
  } catch (error) {
603
720
  const errorMessage = error instanceof Error ? error.message : String(error);
721
+ logger.error("File read error", { path, error: errorMessage });
604
722
  return {
605
723
  content: [
606
724
  {
@@ -653,12 +771,14 @@ var acpRemoteWriteFileTool = {
653
771
  };
654
772
  async function handleAcpRemoteWriteFile(args, sshConnection) {
655
773
  const { path, content, encoding = "utf-8", createDirs = false, backup = false } = args;
774
+ logger.debug("Writing remote file", { path, contentSize: content.length, encoding, createDirs, backup });
656
775
  try {
657
776
  const result = await sshConnection.writeFile(path, content, {
658
777
  encoding,
659
778
  createDirs,
660
779
  backup
661
780
  });
781
+ logger.debug("File write successful", { path, bytesWritten: result.bytesWritten, backupPath: result.backupPath });
662
782
  const output = {
663
783
  success: result.success,
664
784
  bytesWritten: result.bytesWritten,
@@ -674,6 +794,7 @@ async function handleAcpRemoteWriteFile(args, sshConnection) {
674
794
  };
675
795
  } catch (error) {
676
796
  const errorMessage = error instanceof Error ? error.message : String(error);
797
+ logger.error("File write error", { path, error: errorMessage });
677
798
  return {
678
799
  content: [
679
800
  {
@@ -704,13 +825,27 @@ var SSHConnectionManager = class {
704
825
  */
705
826
  async connect() {
706
827
  if (this.connected) {
828
+ logger.debug("SSH connection already established");
707
829
  return;
708
830
  }
831
+ logger.info("Connecting to SSH server", {
832
+ host: this.config.host,
833
+ port: this.config.port || 22,
834
+ username: this.config.username
835
+ });
709
836
  return new Promise((resolve, reject) => {
710
837
  this.client.on("ready", () => {
711
838
  this.connected = true;
839
+ logger.info("SSH connection established", {
840
+ host: this.config.host,
841
+ username: this.config.username
842
+ });
712
843
  resolve();
713
844
  }).on("error", (err) => {
845
+ logger.error("SSH connection failed", {
846
+ host: this.config.host,
847
+ error: err.message
848
+ });
714
849
  reject(err);
715
850
  }).connect({
716
851
  host: this.config.host,
@@ -756,6 +891,8 @@ var SSHConnectionManager = class {
756
891
  if (!this.connected) {
757
892
  await this.connect();
758
893
  }
894
+ const startTime = Date.now();
895
+ logger.sshCommand(command, void 0, timeoutSeconds);
759
896
  const execPromise = new Promise((resolve, reject) => {
760
897
  this.client.exec(command, (err, stream) => {
761
898
  if (err) {
@@ -765,6 +902,8 @@ var SSHConnectionManager = class {
765
902
  let stdout = "";
766
903
  let stderr = "";
767
904
  stream.on("close", (code) => {
905
+ const duration = Date.now() - startTime;
906
+ logger.sshCommandResult(code, duration, stdout.length, stderr.length);
768
907
  resolve({ stdout, stderr, exitCode: code });
769
908
  }).on("data", (data) => {
770
909
  stdout += data.toString();
@@ -783,6 +922,7 @@ var SSHConnectionManager = class {
783
922
  return { ...result, timedOut: false };
784
923
  } catch (error) {
785
924
  if (error instanceof Error && error.message === "Command execution timed out") {
925
+ logger.warn("SSH command timed out", { command, timeout: timeoutSeconds });
786
926
  return {
787
927
  stdout: "",
788
928
  stderr: "Command execution timed out",
@@ -790,6 +930,10 @@ var SSHConnectionManager = class {
790
930
  timedOut: true
791
931
  };
792
932
  }
933
+ logger.error("SSH command execution failed", {
934
+ command,
935
+ error: error instanceof Error ? error.message : String(error)
936
+ });
793
937
  throw error;
794
938
  }
795
939
  }
@@ -833,22 +977,30 @@ var SSHConnectionManager = class {
833
977
  * Read file contents from remote machine
834
978
  */
835
979
  async readFile(path, encoding = "utf-8", maxSize = 1048576) {
980
+ const startTime = Date.now();
981
+ logger.fileOperation("read", path, { encoding, maxSize });
836
982
  const sftp = await this.getSFTP();
837
983
  return new Promise((resolve, reject) => {
838
984
  sftp.stat(path, (err, stats) => {
839
985
  if (err) {
986
+ logger.error("File stat failed", { path, error: err.message });
840
987
  reject(new Error(`File not found or inaccessible: ${path}`));
841
988
  return;
842
989
  }
990
+ logger.debug("File stat retrieved", { path, size: stats.size });
843
991
  if (stats.size > maxSize) {
992
+ logger.warn("File too large", { path, size: stats.size, maxSize });
844
993
  reject(new Error(`File too large: ${stats.size} bytes (max: ${maxSize} bytes)`));
845
994
  return;
846
995
  }
847
996
  sftp.readFile(path, { encoding }, (err2, data) => {
848
997
  if (err2) {
998
+ logger.error("File read failed", { path, error: err2.message });
849
999
  reject(new Error(`Failed to read file: ${err2.message}`));
850
1000
  return;
851
1001
  }
1002
+ const duration = Date.now() - startTime;
1003
+ logger.debug("File read completed", { path, size: stats.size, duration: `${duration}ms` });
852
1004
  resolve({
853
1005
  content: data.toString(),
854
1006
  size: stats.size,
@@ -863,6 +1015,13 @@ var SSHConnectionManager = class {
863
1015
  */
864
1016
  async writeFile(path, content, options = {}) {
865
1017
  const { encoding = "utf-8", createDirs = false, backup = false } = options;
1018
+ const startTime = Date.now();
1019
+ logger.fileOperation("write", path, {
1020
+ contentSize: content.length,
1021
+ encoding,
1022
+ createDirs,
1023
+ backup
1024
+ });
866
1025
  const sftp = await this.getSFTP();
867
1026
  return new Promise((resolve, reject) => {
868
1027
  const writeOperation = () => {
@@ -889,9 +1048,17 @@ var SSHConnectionManager = class {
889
1048
  }
890
1049
  sftp.rename(tempPath, path, (err2) => {
891
1050
  if (err2) {
1051
+ logger.error("File rename failed", { tempPath, path, error: err2.message });
892
1052
  reject(new Error(`Failed to rename temp file: ${err2.message}`));
893
1053
  return;
894
1054
  }
1055
+ const duration = Date.now() - startTime;
1056
+ logger.debug("File write completed", {
1057
+ path,
1058
+ bytesWritten: buffer.length,
1059
+ duration: `${duration}ms`,
1060
+ backupPath
1061
+ });
895
1062
  resolve({
896
1063
  success: true,
897
1064
  bytesWritten: buffer.length,
@@ -915,6 +1082,10 @@ var SSHConnectionManager = class {
915
1082
  */
916
1083
  disconnect() {
917
1084
  if (this.connected) {
1085
+ logger.info("Disconnecting from SSH server", {
1086
+ host: this.config.host,
1087
+ username: this.config.username
1088
+ });
918
1089
  this.client.end();
919
1090
  this.connected = false;
920
1091
  }
@@ -949,26 +1120,39 @@ async function main() {
949
1120
  }
950
1121
  }
951
1122
  );
952
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
953
- tools: [acpRemoteListFilesTool, acpRemoteExecuteCommandTool, acpRemoteReadFileTool, acpRemoteWriteFileTool]
954
- }));
1123
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
1124
+ logger.debug("Tool discovery requested");
1125
+ const tools = [acpRemoteListFilesTool, acpRemoteExecuteCommandTool, acpRemoteReadFileTool, acpRemoteWriteFileTool];
1126
+ logger.debug(`Returning ${tools.length} tools`, { tools: tools.map((t) => t.name) });
1127
+ return { tools };
1128
+ });
955
1129
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
956
- if (request.params.name === "acp_remote_list_files") {
957
- return await handleAcpRemoteListFiles(request.params.arguments, sshConnection);
958
- }
959
- if (request.params.name === "acp_remote_execute_command") {
960
- return await handleAcpRemoteExecuteCommand(request.params.arguments, sshConnection);
961
- }
962
- if (request.params.name === "acp_remote_read_file") {
963
- return await handleAcpRemoteReadFile(request.params.arguments, sshConnection);
964
- }
965
- if (request.params.name === "acp_remote_write_file") {
966
- return await handleAcpRemoteWriteFile(request.params.arguments, sshConnection);
1130
+ const startTime = Date.now();
1131
+ logger.toolInvoked(request.params.name, request.params.arguments);
1132
+ try {
1133
+ let result;
1134
+ if (request.params.name === "acp_remote_list_files") {
1135
+ result = await handleAcpRemoteListFiles(request.params.arguments, sshConnection);
1136
+ } else if (request.params.name === "acp_remote_execute_command") {
1137
+ result = await handleAcpRemoteExecuteCommand(request.params.arguments, sshConnection);
1138
+ } else if (request.params.name === "acp_remote_read_file") {
1139
+ result = await handleAcpRemoteReadFile(request.params.arguments, sshConnection);
1140
+ } else if (request.params.name === "acp_remote_write_file") {
1141
+ result = await handleAcpRemoteWriteFile(request.params.arguments, sshConnection);
1142
+ } else {
1143
+ throw new Error(`Unknown tool: ${request.params.name}`);
1144
+ }
1145
+ const duration = Date.now() - startTime;
1146
+ const resultSize = JSON.stringify(result).length;
1147
+ logger.toolCompleted(request.params.name, duration, resultSize);
1148
+ return result;
1149
+ } catch (error) {
1150
+ logger.toolFailed(request.params.name, error, request.params.arguments);
1151
+ throw error;
967
1152
  }
968
- throw new Error(`Unknown tool: ${request.params.name}`);
969
1153
  });
970
1154
  const cleanup = () => {
971
- console.error("Shutting down...");
1155
+ logger.info("Shutting down server");
972
1156
  sshConnection.disconnect();
973
1157
  process.exit(0);
974
1158
  };
@@ -976,10 +1160,10 @@ async function main() {
976
1160
  process.on("SIGTERM", cleanup);
977
1161
  const transport = new StdioServerTransport();
978
1162
  await server.connect(transport);
979
- console.error("ACP MCP Server running on stdio");
1163
+ logger.info("ACP MCP Server running on stdio");
980
1164
  }
981
1165
  main().catch((error) => {
982
- console.error("Server error:", error);
1166
+ logger.error("Server startup failed", { error: error.message, stack: error.stack });
983
1167
  process.exit(1);
984
1168
  });
985
1169
  //# sourceMappingURL=server.js.map