feat: Add WebDAV resource move/rename functionality
This commit is contained in:
@@ -78,6 +78,7 @@ The server provides integration with multiple Nextcloud apps, enabling LLMs to i
|
|||||||
| `nc_webdav_write_file` | Create or update files in NextCloud |
|
| `nc_webdav_write_file` | Create or update files in NextCloud |
|
||||||
| `nc_webdav_create_directory` | Create new directories |
|
| `nc_webdav_create_directory` | Create new directories |
|
||||||
| `nc_webdav_delete_resource` | Delete files or directories |
|
| `nc_webdav_delete_resource` | Delete files or directories |
|
||||||
|
| `nc_webdav_move_resource` | Move or rename files and directories |
|
||||||
|
|
||||||
## Available Resources
|
## Available Resources
|
||||||
|
|
||||||
@@ -116,6 +117,15 @@ await nc_webdav_write_file("NewProject/docs/notes.md", "# My Notes\n\nContent he
|
|||||||
|
|
||||||
# Delete a file or directory
|
# Delete a file or directory
|
||||||
await nc_webdav_delete_resource("old_file.txt")
|
await nc_webdav_delete_resource("old_file.txt")
|
||||||
|
|
||||||
|
# Move or rename a file
|
||||||
|
await nc_webdav_move_resource("document.txt", "new_name.txt")
|
||||||
|
|
||||||
|
# Move a file to another directory
|
||||||
|
await nc_webdav_move_resource("document.txt", "Archive/document.txt")
|
||||||
|
|
||||||
|
# Move a directory
|
||||||
|
await nc_webdav_move_resource("Projects/OldProject", "Projects/NewProject")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Calendar Integration
|
### Calendar Integration
|
||||||
|
|||||||
@@ -415,3 +415,84 @@ class WebDAVClient(BaseNextcloudClient):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Unexpected error creating directory '{path}': {e}")
|
logger.error(f"Unexpected error creating directory '{path}': {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
async def move_resource(
|
||||||
|
self, source_path: str, destination_path: str, overwrite: bool = False
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Move or rename a resource (file or directory) via WebDAV MOVE.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_path: The path of the file or directory to move
|
||||||
|
destination_path: The new path for the file or directory
|
||||||
|
overwrite: Whether to overwrite the destination if it exists
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with status_code and optional message
|
||||||
|
"""
|
||||||
|
source_webdav_path = f"{self._get_webdav_base_path()}/{source_path.lstrip('/')}"
|
||||||
|
destination_webdav_path = (
|
||||||
|
f"{self._get_webdav_base_path()}/{destination_path.lstrip('/')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure paths have consistent trailing slashes for directories
|
||||||
|
if source_path.endswith("/") and not destination_path.endswith("/"):
|
||||||
|
destination_webdav_path += "/"
|
||||||
|
elif not source_path.endswith("/") and destination_path.endswith("/"):
|
||||||
|
source_webdav_path += "/"
|
||||||
|
|
||||||
|
logger.debug(f"Moving resource from '{source_path}' to '{destination_path}'")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"OCS-APIRequest": "true",
|
||||||
|
"Destination": destination_webdav_path,
|
||||||
|
"Overwrite": "T" if overwrite else "F",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await self._make_request(
|
||||||
|
"MOVE", source_webdav_path, headers=headers
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Successfully moved resource from '{source_path}' to '{destination_path}'"
|
||||||
|
)
|
||||||
|
return {"status_code": response.status_code}
|
||||||
|
|
||||||
|
except HTTPStatusError as e:
|
||||||
|
if e.response.status_code == 404:
|
||||||
|
logger.debug(f"Source resource '{source_path}' not found")
|
||||||
|
return {"status_code": 404, "message": "Source resource not found"}
|
||||||
|
elif e.response.status_code == 412:
|
||||||
|
logger.debug(
|
||||||
|
f"Destination '{destination_path}' already exists and overwrite is false"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status_code": 412,
|
||||||
|
"message": "Destination already exists and overwrite is false",
|
||||||
|
}
|
||||||
|
elif e.response.status_code == 409:
|
||||||
|
logger.debug(
|
||||||
|
f"Parent directory of destination '{destination_path}' doesn't exist"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status_code": 409,
|
||||||
|
"message": "Parent directory of destination doesn't exist",
|
||||||
|
}
|
||||||
|
logger.debug(
|
||||||
|
f"Parent directory of destination '{destination_path}' doesn't exist"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status_code": 409,
|
||||||
|
"message": "Parent directory of destination doesn't exist",
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
f"HTTP error moving resource from '{source_path}' to '{destination_path}': {e}"
|
||||||
|
)
|
||||||
|
raise e
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"Unexpected error moving resource from '{source_path}' to '{destination_path}': {e}"
|
||||||
|
)
|
||||||
|
raise e
|
||||||
|
|||||||
@@ -86,3 +86,13 @@ class DeleteResourceResponse(StatusResponse):
|
|||||||
items_deleted: Optional[int] = Field(
|
items_deleted: Optional[int] = Field(
|
||||||
None, description="Number of items deleted (for directories)"
|
None, description="Number of items deleted (for directories)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MoveResourceResponse(StatusResponse):
|
||||||
|
"""Response model for resource move/rename operations."""
|
||||||
|
|
||||||
|
source_path: str = Field(description="Original path of the resource")
|
||||||
|
destination_path: str = Field(description="New path of the resource")
|
||||||
|
overwrite: bool = Field(
|
||||||
|
description="Whether the destination was overwritten if it existed"
|
||||||
|
)
|
||||||
|
|||||||
@@ -149,3 +149,35 @@ def configure_webdav_tools(mcp: FastMCP):
|
|||||||
"""
|
"""
|
||||||
client: NextcloudClient = ctx.request_context.lifespan_context.client
|
client: NextcloudClient = ctx.request_context.lifespan_context.client
|
||||||
return await client.webdav.delete_resource(path)
|
return await client.webdav.delete_resource(path)
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
async def nc_webdav_move_resource(
|
||||||
|
source_path: str, destination_path: str, ctx: Context, overwrite: bool = False
|
||||||
|
):
|
||||||
|
"""Move or rename a file or directory in NextCloud.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_path: Full path of the file or directory to move
|
||||||
|
destination_path: New path for the file or directory
|
||||||
|
overwrite: Whether to overwrite the destination if it exists (default: False)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with status_code indicating result (404 if source not found, 412 if destination exists and overwrite is False)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
# Rename a file
|
||||||
|
await nc_webdav_move_resource("document.txt", "new_name.txt")
|
||||||
|
|
||||||
|
# Move a file to another directory
|
||||||
|
await nc_webdav_move_resource("document.txt", "Archive/document.txt")
|
||||||
|
|
||||||
|
# Move a directory
|
||||||
|
await nc_webdav_move_resource("Projects/OldProject", "Projects/NewProject")
|
||||||
|
|
||||||
|
# Move and overwrite if destination exists
|
||||||
|
await nc_webdav_move_resource("document.txt", "Archive/document.txt", overwrite=True)
|
||||||
|
"""
|
||||||
|
client: NextcloudClient = ctx.request_context.lifespan_context.client
|
||||||
|
return await client.webdav.move_resource(
|
||||||
|
source_path, destination_path, overwrite
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user