leinier310 commited on
Commit
d28fe3e
·
verified ·
1 Parent(s): c394d15

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -61
app.py CHANGED
@@ -1,81 +1,70 @@
1
  import os
2
- import threading
3
- import time
4
  import subprocess
5
  from pathlib import Path
6
- from typing import List
 
7
 
8
- from fastapi import FastAPI, HTTPException, Query
9
- from pydantic import BaseModel
10
-
11
- BASE_DIR = Path("./").resolve()
12
  BASE_DIR.mkdir(parents=True, exist_ok=True)
13
 
14
- app = FastAPI(title="Minimal MCP File Manager")
15
-
16
- class ModifyRequest(BaseModel):
17
- filename: str
18
- mode: str # 'replace' | 'line_edit'
19
- content: str
20
- start_line: int = None
21
- end_line: int = None
22
 
23
- class ExecRequest(BaseModel):
24
- command: List[str]
 
 
 
 
25
 
26
- # ----------------- Endpoints -----------------
27
- @app.post("/modify")
28
- def modify_file(req: ModifyRequest):
29
- path = BASE_DIR / req.filename
30
- if not path.exists() or path.is_dir():
31
- raise HTTPException(status_code=404, detail="Archivo no encontrado o es un directorio")
32
 
33
- if req.mode == "replace":
34
- path.write_text(req.content)
35
- return {"status": "replaced", "path": str(path)}
36
 
37
- elif req.mode == "line_edit":
38
- if req.start_line is None or req.end_line is None:
39
- raise HTTPException(status_code=400, detail="start_line y end_line requeridos para line_edit")
40
- lines = path.read_text().splitlines()
41
- s = max(1, req.start_line) - 1
42
- e = max(0, req.end_line)
43
- new_lines = lines[:s] + req.content.splitlines() + lines[e:]
44
- path.write_text("\n".join(new_lines) + ("\n" if req.content.endswith("\n") else ""))
45
- return {"status": "line_edited", "path": str(path)}
46
 
47
- else:
48
- raise HTTPException(status_code=400, detail="mode desconocido")
49
 
50
- @app.post("/exec")
51
- def exec_command(req: ExecRequest):
52
- if not req.command:
53
- raise HTTPException(status_code=400, detail="Comando vacío")
 
 
 
54
  try:
55
- res = subprocess.run(req.command, cwd=str(BASE_DIR), capture_output=True, timeout=15)
56
- return {
57
- "returncode": res.returncode,
58
- "stdout": res.stdout.decode(errors="ignore"),
59
- "stderr": res.stderr.decode(errors="ignore"),
60
- }
61
  except subprocess.TimeoutExpired:
62
- raise HTTPException(status_code=500, detail="Execution timeout")
 
 
63
 
64
- @app.get("/disk_usage")
65
- def disk_usage():
66
- import shutil
67
  usage = shutil.disk_usage(str(BASE_DIR))
68
  total_size = sum(f.stat().st_size for f in BASE_DIR.rglob('*') if f.is_file())
69
- return {
70
- "fs_total": usage.total,
71
- "fs_used": usage.used,
72
- "fs_free": usage.free,
73
- "dir_size": total_size,
74
- }
75
 
76
- # ----------------- Keep-alive -----------------
77
- def keep_alive_thread():
78
- import requests
 
79
  while True:
80
  try:
81
  requests.get("http://127.0.0.1:7860/disk_usage", timeout=2)
@@ -83,4 +72,10 @@ def keep_alive_thread():
83
  pass
84
  time.sleep(300)
85
 
86
- threading.Thread(target=keep_alive_thread, daemon=True).start()
 
 
 
 
 
 
 
1
  import os
2
+ import shutil
 
3
  import subprocess
4
  from pathlib import Path
5
+ from typing import List, Optional, Dict, Any
6
+ from mcp.server.fastmcp import FastMCP
7
 
8
+ BASE_DIR = Path(os.getenv("BASE_DIR", "./")).resolve()
 
 
 
9
  BASE_DIR.mkdir(parents=True, exist_ok=True)
10
 
11
+ mcp = FastMCP(name="HF-MCP-Server", stateless_http=True, json_response=True)
 
 
 
 
 
 
 
12
 
13
+ # Helper
14
+ def _safe_path(rel_path: str) -> Path:
15
+ candidate = (BASE_DIR / rel_path).resolve()
16
+ if not str(candidate).startswith(str(BASE_DIR)):
17
+ raise ValueError("Path escapes BASE_DIR")
18
+ return candidate
19
 
20
+ # Tools
21
+ @mcp.tool(name="modify_file", description="Modify or edit files")
22
+ def modify_file(filename: str, mode: str = "replace", content: str = "", start_line: Optional[int] = None, end_line: Optional[int] = None) -> Dict[str, Any]:
23
+ p = _safe_path(filename)
24
+ if not p.exists() or p.is_dir():
25
+ return {"status": "error", "error": "file not found or is a directory"}
26
 
27
+ if mode == "replace":
28
+ p.write_text(content)
29
+ return {"status": "ok", "action": "replaced", "path": str(p.relative_to(BASE_DIR))}
30
 
31
+ if mode == "line_edit":
32
+ if start_line is None or end_line is None:
33
+ return {"status": "error", "error": "start_line and end_line required"}
34
+ lines = p.read_text().splitlines()
35
+ s = max(1, start_line) - 1
36
+ e = max(0, end_line)
37
+ new_lines = lines[:s] + content.splitlines() + lines[e:]
38
+ p.write_text("\n".join(new_lines) + ("\n" if content.endswith("\n") else ""))
39
+ return {"status": "ok", "action": "line_edit", "path": str(p.relative_to(BASE_DIR))}
40
 
41
+ return {"status": "error", "error": "unknown mode"}
 
42
 
43
+ @mcp.tool(name="exec_command", description="Execute shell commands in BASE_DIR")
44
+ def exec_command(command: List[str]) -> Dict[str, Any]:
45
+ allow = os.getenv("ALLOW_UNRESTRICTED_EXEC", "0") == "1"
46
+ if not allow:
47
+ return {"status": "forbidden", "error": "exec disabled. Set ALLOW_UNRESTRICTED_EXEC=1 to enable"}
48
+ if not command:
49
+ return {"status": "error", "error": "empty command"}
50
  try:
51
+ proc = subprocess.run(command, cwd=str(BASE_DIR), capture_output=True, text=True, timeout=int(os.getenv("EXEC_TIMEOUT", "30")))
52
+ return {"status": "ok", "returncode": proc.returncode, "stdout": proc.stdout, "stderr": proc.stderr}
 
 
 
 
53
  except subprocess.TimeoutExpired:
54
+ return {"status": "error", "error": "timeout"}
55
+ except Exception as e:
56
+ return {"status": "error", "error": str(e)}
57
 
58
+ @mcp.tool(name="disk_usage", description="Report disk usage for BASE_DIR")
59
+ def disk_usage() -> Dict[str, int]:
 
60
  usage = shutil.disk_usage(str(BASE_DIR))
61
  total_size = sum(f.stat().st_size for f in BASE_DIR.rglob('*') if f.is_file())
62
+ return {"fs_total": usage.total, "fs_used": usage.used, "fs_free": usage.free, "dir_size": total_size}
 
 
 
 
 
63
 
64
+ # Keep-alive
65
+ import threading, requests, time
66
+
67
+ def keep_alive():
68
  while True:
69
  try:
70
  requests.get("http://127.0.0.1:7860/disk_usage", timeout=2)
 
72
  pass
73
  time.sleep(300)
74
 
75
+ threading.Thread(target=keep_alive, daemon=True).start()
76
+
77
+ # Run server
78
+ if __name__ == "__main__":
79
+ port = int(os.getenv("PORT", "7860"))
80
+ print(f"Starting MCP server on 0.0.0.0:{port} -- BASE_DIR={BASE_DIR}")
81
+ mcp.run(transport="streamable-http", host="0.0.0.0", port=port)