Benutzer-Werkzeuge

Webseiten-Werkzeuge


lmstudio

Dies ist eine alte Version des Dokuments!


#mcp.json
# 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")
lmstudio.1765810457.txt.gz · Zuletzt geändert: 2025/12/15 15:54 von jango