"""Bulk operations — bulk create, update, delete documents and data import/export.""" import json from mcp.types import Tool from frappe_mcp.client.frappe_api import FrappeClient from frappe_mcp.config import get_settings def tools() -> list[Tool]: return [ Tool( name="frappe_bulk_create_documents", description="Create multiple documents of the same DocType in one call.", inputSchema={ "type": "object", "required": ["doctype", "documents"], "properties": { "doctype": {"type": "string"}, "documents": { "type": "array", "items": {"type": "object"}, "description": "List of document field dicts to create", }, }, }, ), Tool( name="frappe_bulk_update_documents", description="Update the same field(s) on multiple documents at once.", inputSchema={ "type": "object", "required": ["doctype", "names", "updates"], "properties": { "doctype": {"type": "string"}, "names": { "type": "array", "items": {"type": "string"}, "description": "List of document names to update", }, "updates": { "type": "object", "description": "Fields and values to apply to all documents", }, }, }, ), Tool( name="frappe_bulk_delete_documents", description="Delete multiple documents. Blocked in read-only mode.", inputSchema={ "type": "object", "required": ["doctype", "names"], "properties": { "doctype": {"type": "string"}, "names": {"type": "array", "items": {"type": "string"}}, }, }, ), Tool( name="frappe_bulk_submit_documents", description="Submit multiple documents in one call.", inputSchema={ "type": "object", "required": ["doctype", "names"], "properties": { "doctype": {"type": "string"}, "names": {"type": "array", "items": {"type": "string"}}, }, }, ), Tool( name="frappe_import_data", description=( "Import documents via JSON array. Equivalent to Frappe's Data Import tool. " "Use 'Insert' to create new records or 'Update' to update existing ones." ), inputSchema={ "type": "object", "required": ["doctype", "data"], "properties": { "doctype": {"type": "string"}, "data": { "type": "array", "items": {"type": "object"}, "description": "Array of document objects to import", }, "import_type": { "type": "string", "enum": ["Insert New Records", "Update Existing Records"], "default": "Insert New Records", }, }, }, ), Tool( name="frappe_export_data", description="Export documents from a DocType as a JSON list.", inputSchema={ "type": "object", "required": ["doctype"], "properties": { "doctype": {"type": "string"}, "fields": { "type": "array", "items": {"type": "string"}, "description": "Fields to export. Empty = all fields.", }, "filters": {"type": "array"}, "limit": {"type": "integer", "default": 500}, }, }, ), Tool( name="frappe_bulk_cancel_documents", description="Cancel multiple submitted documents.", inputSchema={ "type": "object", "required": ["doctype", "names"], "properties": { "doctype": {"type": "string"}, "names": {"type": "array", "items": {"type": "string"}}, }, }, ), Tool( name="frappe_bulk_assign_documents", description="Assign multiple documents to a user.", inputSchema={ "type": "object", "required": ["doctype", "names", "assign_to"], "properties": { "doctype": {"type": "string"}, "names": {"type": "array", "items": {"type": "string"}}, "assign_to": {"type": "array", "items": {"type": "string"}, "description": "List of user emails"}, "description": {"type": "string"}, "due_date": {"type": "string"}, "priority": {"type": "string", "enum": ["Low", "Medium", "High"], "default": "Medium"}, }, }, ), Tool( name="frappe_bulk_add_tags", description="Add the same tag to multiple documents.", inputSchema={ "type": "object", "required": ["doctype", "names", "tag"], "properties": { "doctype": {"type": "string"}, "names": {"type": "array", "items": {"type": "string"}}, "tag": {"type": "string"}, }, }, ), Tool( name="frappe_bulk_add_comment", description="Add the same comment to multiple documents.", inputSchema={ "type": "object", "required": ["doctype", "names", "content"], "properties": { "doctype": {"type": "string"}, "names": {"type": "array", "items": {"type": "string"}}, "content": {"type": "string"}, "comment_type": {"type": "string", "default": "Comment"}, }, }, ), ] def handlers() -> dict: return { "frappe_bulk_create_documents": _bulk_create, "frappe_bulk_update_documents": _bulk_update, "frappe_bulk_delete_documents": _bulk_delete, "frappe_bulk_submit_documents": _bulk_submit, "frappe_import_data": _import_data, "frappe_export_data": _export_data, "frappe_bulk_cancel_documents": _bulk_cancel, "frappe_bulk_assign_documents": _bulk_assign, "frappe_bulk_add_tags": _bulk_add_tags, "frappe_bulk_add_comment": _bulk_add_comment, } async def _bulk_create(args: dict) -> str: client = FrappeClient() results = [] errors = [] for doc in args["documents"]: try: data = {"doctype": args["doctype"], **doc} result = await client.create_doc(args["doctype"], data) results.append(result.get("name") if isinstance(result, dict) else result) except Exception as e: errors.append({"doc": doc, "error": str(e)}) return json.dumps({"created": results, "errors": errors}, indent=2) async def _bulk_update(args: dict) -> str: client = FrappeClient() results = [] errors = [] for name in args["names"]: try: result = await client.update_doc(args["doctype"], name, args["updates"]) results.append(name) except Exception as e: errors.append({"name": name, "error": str(e)}) return json.dumps({"updated": results, "errors": errors}, indent=2) async def _bulk_delete(args: dict) -> str: if get_settings().read_only_mode: return "Error: read_only_mode is enabled. Deletion blocked." client = FrappeClient() results = [] errors = [] for name in args["names"]: try: await client.delete_doc(args["doctype"], name) results.append(name) except Exception as e: errors.append({"name": name, "error": str(e)}) return json.dumps({"deleted": results, "errors": errors}, indent=2) async def _bulk_submit(args: dict) -> str: client = FrappeClient() results = [] errors = [] for name in args["names"]: try: await client.update_doc(args["doctype"], name, {"docstatus": 1}) results.append(name) except Exception as e: errors.append({"name": name, "error": str(e)}) return json.dumps({"submitted": results, "errors": errors}, indent=2) async def _import_data(args: dict) -> str: client = FrappeClient() results = [] errors = [] is_update = args.get("import_type", "Insert New Records") == "Update Existing Records" for doc in args["data"]: try: name = doc.get("name") if is_update and name: result = await client.update_doc(args["doctype"], name, doc) else: data = {"doctype": args["doctype"], **doc} result = await client.create_doc(args["doctype"], data) results.append(result.get("name") if isinstance(result, dict) else str(result)) except Exception as e: errors.append({"doc": doc.get("name", str(doc)[:40]), "error": str(e)}) return json.dumps({"imported": len(results), "names": results, "errors": errors}, indent=2) async def _export_data(args: dict) -> str: client = FrappeClient() result = await client.get_list( args["doctype"], fields=args.get("fields") or ["*"], filters=args.get("filters"), limit=args.get("limit", 500), ) return json.dumps(result, indent=2) async def _bulk_cancel(args: dict) -> str: client = FrappeClient() results, errors = [], [] for name in args["names"]: try: await client.update_doc(args["doctype"], name, {"docstatus": 2}) results.append(name) except Exception as e: errors.append({"name": name, "error": str(e)}) return json.dumps({"cancelled": results, "errors": errors}, indent=2) async def _bulk_assign(args: dict) -> str: client = FrappeClient() results, errors = [], [] for name in args["names"]: try: await client.call_method( "frappe.desk.form.assign_to.add", args={ "doctype": args["doctype"], "name": name, "assign_to": args["assign_to"], "description": args.get("description", ""), "due_date": args.get("due_date", ""), "priority": args.get("priority", "Medium"), "notify": 0, }, ) results.append(name) except Exception as e: errors.append({"name": name, "error": str(e)}) return json.dumps({"assigned": results, "errors": errors}, indent=2) async def _bulk_add_tags(args: dict) -> str: client = FrappeClient() results, errors = [], [] for name in args["names"]: try: await client.call_method( "frappe.desk.doctype.tag.tag.add_tag", tag=args["tag"], dt=args["doctype"], dn=name, ) results.append(name) except Exception as e: errors.append({"name": name, "error": str(e)}) return json.dumps({"tagged": results, "errors": errors}, indent=2) async def _bulk_add_comment(args: dict) -> str: client = FrappeClient() results, errors = [], [] for name in args["names"]: try: await client.call_method( "frappe.client.add_comment", reference_doctype=args["doctype"], reference_name=name, content=args["content"], comment_email="", comment_by="", ) results.append(name) except Exception as e: errors.append({"name": name, "error": str(e)}) return json.dumps({"commented": results, "errors": errors}, indent=2)