filesystem_server.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import os
  2. import stat
  3. from pathlib import Path
  4. from mcp.server.fastmcp import FastMCP
  5. mcp = FastMCP("filesystem", host="0.0.0.0", port=8010)
  6. ALLOWED_DIRS = [
  7. "/projects",
  8. "/data",
  9. "/scripts",
  10. "/backups",
  11. "/tmp",
  12. ]
  13. def _resolve_path(path: str) -> Path:
  14. return Path(path).resolve()
  15. def _is_path_allowed(path: str) -> bool:
  16. resolved = _resolve_path(path)
  17. for allowed in ALLOWED_DIRS:
  18. allowed_path = Path(allowed).resolve()
  19. if allowed_path in resolved.parents or allowed_path == resolved:
  20. return True
  21. return False
  22. @mcp.tool()
  23. def read_file(path: str) -> str:
  24. """Read the complete contents of a text file at the given path.
  25. Args:
  26. path: Absolute path to the file to read.
  27. """
  28. if not _is_path_allowed(path):
  29. return f"Error: path '{path}' is not in allowed directories: {', '.join(ALLOWED_DIRS)}"
  30. try:
  31. with open(path, "r") as f:
  32. return f.read()
  33. except Exception as e:
  34. return f"Error reading file: {e}"
  35. @mcp.tool()
  36. def write_file(path: str, content: str) -> str:
  37. """Write or overwrite a file with the given content.
  38. Creates parent directories if they do not exist.
  39. Args:
  40. path: Absolute path to the file to write.
  41. content: Text content to write into the file.
  42. """
  43. if not _is_path_allowed(path):
  44. return f"Error: path '{path}' is not in allowed directories: {', '.join(ALLOWED_DIRS)}"
  45. try:
  46. p = _resolve_path(path)
  47. p.parent.mkdir(parents=True, exist_ok=True)
  48. p.write_text(content)
  49. return f"OK - wrote {len(content)} bytes to {path}"
  50. except Exception as e:
  51. return f"Error writing file: {e}"
  52. @mcp.tool()
  53. def list_directory(path: str) -> str:
  54. """List files and directories at the given path.
  55. Shows name, size (for files), and modification time.
  56. Args:
  57. path: Absolute path to the directory.
  58. """
  59. if not _is_path_allowed(path):
  60. return f"Error: path '{path}' is not in allowed directories: {', '.join(ALLOWED_DIRS)}"
  61. try:
  62. entries = []
  63. for entry in os.scandir(path):
  64. info = entry.stat()
  65. size = info.st_size if entry.is_file() else 0
  66. mtime = info.st_mtime
  67. kind = "F" if entry.is_file() else "D"
  68. entries.append(f"{kind} {entry.name:40s} {size:>10d} B")
  69. return "\n".join(sorted(entries))
  70. except Exception as e:
  71. return f"Error listing directory: {e}"
  72. @mcp.tool()
  73. def get_file_info(path: str) -> str:
  74. """Get metadata about a file or directory.
  75. Args:
  76. path: Absolute path to the file or directory.
  77. """
  78. if not _is_path_allowed(path):
  79. return f"Error: path '{path}' is not in allowed directories: {', '.join(ALLOWED_DIRS)}"
  80. try:
  81. st = os.stat(path)
  82. kind = "directory" if stat.S_ISDIR(st.st_mode) else "file"
  83. return (
  84. f"Path: {path}\n"
  85. f"Type: {kind}\n"
  86. f"Size: {st.st_size} B\n"
  87. f"Mode: {oct(st.st_mode)}\n"
  88. f"Modified: {st.st_mtime}"
  89. )
  90. except Exception as e:
  91. return f"Error: {e}"
  92. if __name__ == "__main__":
  93. mcp.run(transport="stdio")