Add write_file, create_folder, delete_file, move_file to Files server
Full CRUD for oCIS files: - write_file: text or base64 binary content - create_folder: MKCOL - delete_file: DELETE (files and folders) - move_file: MOVE (rename or relocate) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -162,6 +162,81 @@ def search_files(
|
||||
return "\n".join(lines) if lines else "Keine Dateien gefunden"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def write_file(
|
||||
path: Annotated[str, Field(description="Full file path, e.g. '/Documents/notes.txt', '/todo.md'. Creates parent dirs automatically.")],
|
||||
content: Annotated[str, Field(description="File content as text (for text files) or base64-encoded data (for binary, set is_base64=true)")],
|
||||
is_base64: Annotated[bool, Field(description="True if content is base64-encoded binary data")] = False,
|
||||
) -> str:
|
||||
"""Write or overwrite a file. Use text content for txt/md/json etc. Use base64 for binary files (images, documents)."""
|
||||
user = get_current_user()
|
||||
if not user: return "Error: not authenticated"
|
||||
if is_base64:
|
||||
data = base64.b64decode(content)
|
||||
else:
|
||||
data = content.encode("utf-8")
|
||||
ct = _guess_mime(path, None)
|
||||
r = httpx.put(_dav(user, path), content=data, auth=_auth(user),
|
||||
headers={"Content-Type": ct}, timeout=60)
|
||||
if r.status_code in (200, 201, 204):
|
||||
return f"Datei geschrieben: {path} ({len(data):,} bytes)"
|
||||
if r.status_code == 409:
|
||||
return f"Fehler: Uebergeordnetes Verzeichnis existiert nicht. Erstelle es zuerst mit create_folder."
|
||||
return f"Fehler: HTTP {r.status_code}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def create_folder(
|
||||
path: Annotated[str, Field(description="Folder path to create, e.g. '/Documents/Projekte', '/Fotos/2026'")],
|
||||
) -> str:
|
||||
"""Create a new folder/directory."""
|
||||
user = get_current_user()
|
||||
if not user: return "Error: not authenticated"
|
||||
r = httpx.request("MKCOL", _dav(user, path), auth=_auth(user), timeout=15)
|
||||
if r.status_code in (200, 201):
|
||||
return f"Ordner erstellt: {path}"
|
||||
if r.status_code == 405:
|
||||
return f"Ordner existiert bereits: {path}"
|
||||
if r.status_code == 409:
|
||||
return f"Fehler: Uebergeordnetes Verzeichnis existiert nicht."
|
||||
return f"Fehler: HTTP {r.status_code}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def delete_file(
|
||||
path: Annotated[str, Field(description="Path to file or folder to delete, e.g. '/Documents/old.txt', '/temp/'")],
|
||||
) -> str:
|
||||
"""Delete a file or folder (including all contents). This cannot be undone."""
|
||||
user = get_current_user()
|
||||
if not user: return "Error: not authenticated"
|
||||
r = httpx.request("DELETE", _dav(user, path), auth=_auth(user), timeout=30)
|
||||
if r.status_code in (200, 204):
|
||||
return f"Geloescht: {path}"
|
||||
if r.status_code == 404:
|
||||
return f"Nicht gefunden: {path}"
|
||||
return f"Fehler: HTTP {r.status_code}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def move_file(
|
||||
source: Annotated[str, Field(description="Current path, e.g. '/Documents/old-name.txt'")],
|
||||
destination: Annotated[str, Field(description="New path, e.g. '/Documents/new-name.txt' or '/Archive/file.txt'")],
|
||||
) -> str:
|
||||
"""Move or rename a file or folder."""
|
||||
user = get_current_user()
|
||||
if not user: return "Error: not authenticated"
|
||||
dest_url = _dav(user, destination)
|
||||
r = httpx.request("MOVE", _dav(user, source), auth=_auth(user),
|
||||
headers={"Destination": dest_url}, timeout=30)
|
||||
if r.status_code in (200, 201, 204):
|
||||
return f"Verschoben: {source} -> {destination}"
|
||||
if r.status_code == 404:
|
||||
return f"Quelle nicht gefunden: {source}"
|
||||
if r.status_code == 409:
|
||||
return f"Fehler: Zielverzeichnis existiert nicht."
|
||||
return f"Fehler: HTTP {r.status_code}"
|
||||
|
||||
|
||||
def create_app():
|
||||
from contextlib import asynccontextmanager
|
||||
mcp_app = mcp.streamable_http_app()
|
||||
|
||||
Reference in New Issue
Block a user