MCP-Server mit Python: Wie ich Claude mit lokalen Tools verbunden habe
Ein Wochenendprojekt mit Lernkurve
Seit ein paar Monaten arbeite ich intensiv mit Claude – erst über die Web-Oberfläche, dann mit Claude Code. Irgendwann wollte ich verstehen, wie das unter der Haube funktioniert. Was ist dieses Model Context Protocol, von dem alle reden? Ein Wochenende später hatte ich meinen eigenen MCP-Server am Laufen. Überraschung: Gar nicht so kompliziert.
Stolpersteine am Anfang
Zwei Dinge haben mich anfangs ausgebremst:
stdio vs. HTTP: MCP-Server können über Standard-Input/Output kommunizieren oder als Web-Service laufen. Die meisten Beispiele zeigen HTTP, aber Claude Desktop will stdio. Simpler, aber man muss es erstmal durchschauen.
Claude Desktop unter Linux: Offiziell gibt es das nur für Mac und Windows. Für Linux braucht man einen Workaround – bei mir läuft es in einer Electron-Umgebung. Nicht schön, aber läuft.
Sobald das klar war, ging der Rest flott.
Was MCP macht
Die Idee:
Claude Desktop ←→ MCP Client ←→ MCP Server ←→ Lokale Tools
Der MCP-Server stellt Tools bereit (Dateien lesen, Befehle ausführen, was auch immer). Claude ruft sie auf. Alles läuft lokal – nichts geht in die Cloud.
Mein erster Server
Voraussetzungen: Python 3.10+ und das mcp Package (pip install mcp).
# server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
mcp = Server("my-tools")
@mcp.tool()
def hello(name: str) -> str:
"""Begrüßt eine Person."""
return f"Hallo, {name}!"
async def main():
async with stdio_server() as (read, write):
await mcp.run(read, write)
if __name__ == "__main__":
import asyncio
asyncio.run(main())
Die Registrierung in Claude Desktop erfolgt über ~/.config/Claude/claude_desktop_config.json:
{
"mcpServers": {
"my-tools": {
"command": "python",
"args": ["/pfad/zu/server.py"]
}
}
}
Nach einem Neustart von Claude Desktop taucht das Tool auf. Claude bitten, jemanden zu begrüßen – funktioniert.
Die Tools, die ich tatsächlich nutze
Dateien lesen und schreiben
from pathlib import Path
WORKSPACE = Path.home() / "Workspace"
@mcp.tool()
def read_file(path: str) -> str:
"""Liest eine Datei aus dem Workspace."""
full_path = WORKSPACE / path
if not full_path.exists():
return f"Fehler: {path} nicht gefunden"
return full_path.read_text()
@mcp.tool()
def write_file(path: str, content: str) -> str:
"""Schreibt Inhalt in eine Datei."""
full_path = WORKSPACE / path
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(content)
return f"Geschrieben: {path}"
Shell-Befehle
import subprocess
@mcp.tool()
def shell(command: str, timeout: int = 30) -> str:
"""Führt ein Shell-Kommando aus."""
try:
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=timeout,
cwd=WORKSPACE
)
output = result.stdout
if result.stderr:
output += f"\n[stderr]\n{result.stderr}"
return output or "(keine Ausgabe)"
except subprocess.TimeoutExpired:
return f"Fehler: Timeout nach {timeout}s"
Verzeichnisse auflisten
@mcp.tool()
def list_dir(path: str = ".", pattern: str = "*") -> str:
"""Listet Dateien in einem Verzeichnis."""
full_path = WORKSPACE / path
if not full_path.is_dir():
return f"Fehler: {path} ist kein Verzeichnis"
files = sorted(full_path.glob(pattern))
return "\n".join(f.name for f in files[:50])
Sicherheit – nicht vergessen
Ein MCP-Server mit Shell-Zugriff kann alles, was der Benutzer kann. Ein paar Vorsichtsmaßnahmen:
ALLOWED_PATHS = [
Path.home() / "Workspace",
Path.home() / "Documents",
]
def is_allowed(path: Path) -> bool:
resolved = path.resolve()
return any(
resolved.is_relative_to(allowed)
for allowed in ALLOWED_PATHS
)
Außerdem: Timeouts für alle Befehle. Und Logging – man will wissen, was passiert ist, wenn was schiefgeht.
Was ich damit mache
Der Server läuft bei mir täglich:
- “Zeig mir alle Python-Dateien mit TODO-Kommentaren” – Claude führt grep aus und fasst zusammen
- “Führe die Tests aus und erklär mir die Fehler” – Claude sieht die echte pytest-Ausgabe
- “Erstelle eine README für dieses Projekt” – Claude liest die Struktur und schreibt die Datei
Der Unterschied zu vorher: Kein Copy-Paste mehr. Claude arbeitet direkt im Dateisystem.
Erkenntnisse
- Gute Docstrings – Claude nutzt sie, um zu verstehen, was ein Tool macht
- Kleine, spezialisierte Tools schlagen große generische
- Fehler abfangen – eine unbehandelte Exception bricht die Kommunikation ab
Weiterführendes
Disclaimer: Der Code hier ist ein Proof of Concept. Wer einen MCP-Server mit Shell-Zugriff betreibt, sollte wissen, was er tut.
GitHub: cuber-it/mcp_shell_tools
Aktualisiert: 2025-01-06
Hinweis: Dieses Projekt nutze ich täglich und entwickle es entsprechend weiter. Der Stand auf GitHub kann daher schon etwas weiter sein als hier beschrieben.