MOHAN 2ee93048e1 feat: Add tools for managing server scripts, client scripts, translations, assignment rules, user permissions, webhooks, API keys, and workflows
- 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
2026-04-21 20:26:45 +05:30

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)