mcp.json
{
"mcpServers": {
"my-local-tools": {
"command": "python",
"args": [
"C:\\Users\\manuel.zarat\\Desktop\\openai\\mcpserver.py"
],
"env": {
"MCP_ALLOWED_ROOT": "C:\\Users\\manuel.zarat",
"MCP_ENABLE_DANGEROUS": "1"
}
}
}
}
mcpserver.py
import os
import re
import subprocess
from typing import Optional, List
import fitz # PyMuPDF
import requests
from bs4 import BeautifulSoup
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-local-tools", json_response=True)
# --- Safety rails ---
ALLOWED_ROOT = os.path.abspath(os.environ.get("MCP_ALLOWED_ROOT", os.getcwd()))
ENABLE_DANGEROUS = os.environ.get("MCP_ENABLE_DANGEROUS", "0") == "1"
def _abspath_safe(path: str) -> str:
ap = os.path.abspath(path)
# nur innerhalb allowed root erlauben
if os.path.commonpath([ALLOWED_ROOT, ap]) != ALLOWED_ROOT:
raise ValueError(f"Path not allowed. Allowed root: {ALLOWED_ROOT}")
return ap
@mcp.tool()
def list_files(directory: str) -> List[str]:
"""Listet Dateien und Ordner in einem Verzeichnis (innerhalb MCP_ALLOWED_ROOT)."""
d = _abspath_safe(directory)
items = os.listdir(d)
return sorted(items)
@mcp.tool()
def read_file(path: str, start_line: int = 1, max_lines: int = 400, tail_lines: Optional[int] = None) -> str:
"""Liest Textdatei teilweise (zeilenbasiert), um Kontext zu sparen."""
p = _abspath_safe(path)
with open(p, "r", encoding="utf-8", errors="replace") as f:
lines = f.readlines()
if tail_lines and tail_lines > 0:
sel = lines[-tail_lines:]
start_idx = max(0, len(lines) - tail_lines)
else:
start_idx = max(0, start_line - 1)
sel = lines[start_idx : start_idx + max(1, max_lines)]
header = (
f"[read_file] path={p}\n"
f"[read_file] total_lines={len(lines)} selected_lines={len(sel)} "
f"range={start_idx+1}-{start_idx+len(sel)}\n\n"
)
return header + "".join(sel)
@mcp.tool()
def read_pdf_text(path: str, start_page: int = 1, max_pages: int = 5, max_chars: int = 12000) -> str:
"""Extrahiert Text aus einem PDF (Text-Layer)."""
p = _abspath_safe(path)
doc = fitz.open(p)
n = doc.page_count
sp = max(1, start_page)
ep = min(n, sp + max_pages - 1)
chunks = []
for pno in range(sp - 1, ep):
page = doc.load_page(pno)
txt = page.get_text("text")
chunks.append(f"\n--- Seite {pno+1}/{n} ---\n{txt}")
out = "".join(chunks).strip()
if len(out) > max_chars:
out = out[:max_chars] + "\n…[gekürzt]"
return f"[read_pdf_text] path={p} pages={sp}-{ep}/{n}\n\n{out}"
@mcp.tool()
def fetch_html(url: str, max_chars: int = 8000) -> str:
"""Gibt sichtbaren Text einer http(s)-Seite zurück."""
if not re.match(r"^https?://", url, flags=re.IGNORECASE):
raise ValueError("Only http(s) URLs allowed")
headers = {"User-Agent": "Mozilla/5.0 (compatible; MCP-Tools/1.0)"}
resp = requests.get(url, headers=headers, timeout=25, allow_redirects=True)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
for tag in soup(["script", "style", "noscript", "template"]):
tag.decompose()
text = soup.get_text(separator="\n", strip=True)
text = re.sub(r"\n{3,}", "\n\n", text)
if len(text) > max_chars:
text = text[:max_chars] + "\n…[gekürzt]"
return text
@mcp.tool()
def execute_cmd(command: str) -> str:
"""Führt einen Shell-Befehl aus (standardmäßig deaktiviert!)."""
if not ENABLE_DANGEROUS:
return "execute_cmd ist deaktiviert. Setze MCP_ENABLE_DANGEROUS=1 wenn du das wirklich willst."
r = subprocess.run(command, shell=True, capture_output=True, text=True, encoding="utf-8", errors="replace")
return r.stdout if r.returncode == 0 else f"Fehler: {r.stderr or r.stdout}"
@mcp.tool()
def execute_powershell(command: str) -> str:
"""Führt PowerShell aus (standardmäßig deaktiviert!)."""
if not ENABLE_DANGEROUS:
return "execute_powershell ist deaktiviert. Setze MCP_ENABLE_DANGEROUS=1 wenn du das wirklich willst."
ps = ["powershell", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", command]
r = subprocess.run(ps, capture_output=True, text=True, encoding="utf-8", errors="replace")
return r.stdout if r.returncode == 0 else f"Fehler: {r.stderr or r.stdout}"
if __name__ == "__main__":
# Für LM Studio als "command server" ist stdio typischerweise am einfachsten
mcp.run(transport="stdio")