- Implemented server and client script management tools in `frappe_mcp/tools/scripts.py` - Added translation and user permission management tools in `frappe_mcp/tools/translations.py` - Created user and role management tools in `frappe_mcp/tools/users.py` - Developed webhook and API key management tools in `frappe_mcp/tools/webhooks.py` - Introduced workflow management tools in `frappe_mcp/tools/workflow_tools.py` - Added `pyproject.toml` for project metadata and dependencies
169 lines
5.9 KiB
Python
169 lines
5.9 KiB
Python
"""File and attachment management tools."""
|
|
|
|
import json
|
|
import base64
|
|
from mcp.types import Tool
|
|
from frappe_mcp.client.frappe_api import FrappeClient
|
|
|
|
|
|
def tools() -> list[Tool]:
|
|
return [
|
|
Tool(
|
|
name="frappe_list_files",
|
|
description="List files in Frappe's file manager, optionally filtered by attached document.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"attached_to_doctype": {"type": "string"},
|
|
"attached_to_name": {"type": "string"},
|
|
"folder": {"type": "string", "description": "e.g. 'Home/Attachments'"},
|
|
"is_private": {"type": "integer", "description": "1 = private, 0 = public"},
|
|
"limit": {"type": "integer", "default": 20},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="frappe_get_file",
|
|
description="Get details of a file by its name/ID.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"required": ["name"],
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="frappe_upload_file_base64",
|
|
description=(
|
|
"Upload a file to Frappe by providing base64-encoded content. "
|
|
"Optionally attach it to a document."
|
|
),
|
|
inputSchema={
|
|
"type": "object",
|
|
"required": ["filename", "content_base64"],
|
|
"properties": {
|
|
"filename": {"type": "string", "description": "File name with extension"},
|
|
"content_base64": {"type": "string", "description": "Base64-encoded file content"},
|
|
"attached_to_doctype": {"type": "string"},
|
|
"attached_to_name": {"type": "string"},
|
|
"attached_to_field": {"type": "string"},
|
|
"is_private": {"type": "integer", "default": 1},
|
|
"folder": {"type": "string", "default": "Home/Attachments"},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="frappe_delete_file",
|
|
description="Delete a file from Frappe.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"required": ["name"],
|
|
"properties": {
|
|
"name": {"type": "string", "description": "File document name"},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="frappe_move_file",
|
|
description="Move a file to a different folder.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"required": ["name", "folder"],
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"folder": {"type": "string", "description": "Target folder path"},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="frappe_create_folder",
|
|
description="Create a new folder in the Frappe file manager.",
|
|
inputSchema={
|
|
"type": "object",
|
|
"required": ["folder_name"],
|
|
"properties": {
|
|
"folder_name": {"type": "string"},
|
|
"parent_folder": {"type": "string", "default": "Home"},
|
|
},
|
|
},
|
|
),
|
|
]
|
|
|
|
|
|
def handlers() -> dict:
|
|
return {
|
|
"frappe_list_files": _list_files,
|
|
"frappe_get_file": _get_file,
|
|
"frappe_upload_file_base64": _upload_file_base64,
|
|
"frappe_delete_file": _delete_file,
|
|
"frappe_move_file": _move_file,
|
|
"frappe_create_folder": _create_folder,
|
|
}
|
|
|
|
|
|
async def _list_files(args: dict) -> str:
|
|
client = FrappeClient()
|
|
filters = []
|
|
if dt := args.get("attached_to_doctype"):
|
|
filters.append(["attached_to_doctype", "=", dt])
|
|
if name := args.get("attached_to_name"):
|
|
filters.append(["attached_to_name", "=", name])
|
|
if folder := args.get("folder"):
|
|
filters.append(["folder", "=", folder])
|
|
if "is_private" in args:
|
|
filters.append(["is_private", "=", args["is_private"]])
|
|
result = await client.get_list(
|
|
"File",
|
|
fields=["name", "file_name", "file_url", "file_size", "is_private", "folder", "attached_to_doctype", "attached_to_name"],
|
|
filters=filters if filters else None,
|
|
limit=args.get("limit", 20),
|
|
)
|
|
return json.dumps(result, indent=2)
|
|
|
|
|
|
async def _get_file(args: dict) -> str:
|
|
client = FrappeClient()
|
|
result = await client.get_doc("File", args["name"])
|
|
return json.dumps(result, indent=2)
|
|
|
|
|
|
async def _upload_file_base64(args: dict) -> str:
|
|
client = FrappeClient()
|
|
payload = {
|
|
"filename": args["filename"],
|
|
"filedata": args["content_base64"],
|
|
"is_private": args.get("is_private", 1),
|
|
"folder": args.get("folder", "Home/Attachments"),
|
|
}
|
|
if dt := args.get("attached_to_doctype"):
|
|
payload["doctype"] = dt
|
|
if name := args.get("attached_to_name"):
|
|
payload["docname"] = name
|
|
if field := args.get("attached_to_field"):
|
|
payload["fieldname"] = field
|
|
result = await client.call_method("frappe.client.attach_file", **payload)
|
|
return json.dumps(result, indent=2)
|
|
|
|
|
|
async def _delete_file(args: dict) -> str:
|
|
client = FrappeClient()
|
|
result = await client.delete_doc("File", args["name"])
|
|
return json.dumps(result, indent=2)
|
|
|
|
|
|
async def _move_file(args: dict) -> str:
|
|
client = FrappeClient()
|
|
result = await client.update_doc("File", args["name"], {"folder": args["folder"]})
|
|
return json.dumps(result, indent=2)
|
|
|
|
|
|
async def _create_folder(args: dict) -> str:
|
|
client = FrappeClient()
|
|
result = await client.call_method(
|
|
"frappe.core.doctype.file.file.create_new_folder",
|
|
file_name=args["folder_name"],
|
|
folder=args.get("parent_folder", "Home"),
|
|
)
|
|
return json.dumps(result, indent=2)
|