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

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)