- 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
338 lines
12 KiB
Python
338 lines
12 KiB
Python
"""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)
|