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"
|
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():
|
def create_app():
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
mcp_app = mcp.streamable_http_app()
|
mcp_app = mcp.streamable_http_app()
|
||||||
|
|||||||
Reference in New Issue
Block a user