MCP-Frappe/frappe_mcp/tools/business_actions.py
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

478 lines
20 KiB
Python

"""
Level 8 — Business action tools (ERPNext shortcuts).
High-value operations: convert documents, approve, transfer stock, etc.
These call ERPNext's built-in mapper/make functions.
"""
import json
from mcp.types import Tool
from frappe_mcp.client.frappe_api import FrappeClient
def tools() -> list[Tool]:
return [
# ── Sales / CRM ──────────────────────────────────────────────────────
Tool(
name="erpnext_create_quotation_from_opportunity",
description="Create a Quotation from an Opportunity.",
inputSchema={
"type": "object",
"required": ["opportunity_name"],
"properties": {"opportunity_name": {"type": "string"}},
},
),
Tool(
name="erpnext_create_sales_order_from_quotation",
description="Create a Sales Order from a Quotation.",
inputSchema={
"type": "object",
"required": ["quotation_name"],
"properties": {"quotation_name": {"type": "string"}},
},
),
Tool(
name="erpnext_create_sales_invoice_from_sales_order",
description="Create a Sales Invoice from a Sales Order.",
inputSchema={
"type": "object",
"required": ["sales_order_name"],
"properties": {"sales_order_name": {"type": "string"}},
},
),
Tool(
name="erpnext_create_delivery_note_from_sales_order",
description="Create a Delivery Note from a Sales Order.",
inputSchema={
"type": "object",
"required": ["sales_order_name"],
"properties": {"sales_order_name": {"type": "string"}},
},
),
Tool(
name="erpnext_create_payment_entry_for_invoice",
description="Create a Payment Entry for a Sales Invoice or Purchase Invoice.",
inputSchema={
"type": "object",
"required": ["invoice_doctype", "invoice_name"],
"properties": {
"invoice_doctype": {"type": "string", "enum": ["Sales Invoice", "Purchase Invoice"]},
"invoice_name": {"type": "string"},
},
},
),
# ── Buying ───────────────────────────────────────────────────────────
Tool(
name="erpnext_create_purchase_order_from_supplier_quotation",
description="Create a Purchase Order from a Supplier Quotation.",
inputSchema={
"type": "object",
"required": ["supplier_quotation_name"],
"properties": {"supplier_quotation_name": {"type": "string"}},
},
),
Tool(
name="erpnext_create_purchase_receipt_from_purchase_order",
description="Create a Purchase Receipt from a Purchase Order.",
inputSchema={
"type": "object",
"required": ["purchase_order_name"],
"properties": {"purchase_order_name": {"type": "string"}},
},
),
Tool(
name="erpnext_create_purchase_invoice_from_purchase_receipt",
description="Create a Purchase Invoice from a Purchase Receipt.",
inputSchema={
"type": "object",
"required": ["purchase_receipt_name"],
"properties": {"purchase_receipt_name": {"type": "string"}},
},
),
# ── Stock ────────────────────────────────────────────────────────────
Tool(
name="erpnext_get_item_stock_balance",
description="Get current stock balance for an item, optionally at a specific warehouse.",
inputSchema={
"type": "object",
"required": ["item_code"],
"properties": {
"item_code": {"type": "string"},
"warehouse": {"type": "string"},
"posting_date": {"type": "string", "description": "YYYY-MM-DD (defaults to today)"},
},
},
),
Tool(
name="erpnext_transfer_stock",
description="Transfer stock between warehouses via a Material Transfer Stock Entry.",
inputSchema={
"type": "object",
"required": ["item_code", "qty", "from_warehouse", "to_warehouse"],
"properties": {
"item_code": {"type": "string"},
"qty": {"type": "number"},
"from_warehouse": {"type": "string"},
"to_warehouse": {"type": "string"},
"posting_date": {"type": "string"},
"remarks": {"type": "string"},
},
},
),
Tool(
name="erpnext_create_stock_entry",
description=(
"Create a Stock Entry of any type: "
"Material Issue, Material Receipt, Material Transfer, Manufacture, Repack."
),
inputSchema={
"type": "object",
"required": ["stock_entry_type", "items"],
"properties": {
"stock_entry_type": {
"type": "string",
"enum": ["Material Issue", "Material Receipt", "Material Transfer",
"Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor"],
},
"items": {
"type": "array",
"description": "List of {item_code, qty, s_warehouse, t_warehouse} objects",
"items": {"type": "object"},
},
"posting_date": {"type": "string"},
"remarks": {"type": "string"},
},
},
),
# ── HR ───────────────────────────────────────────────────────────────
Tool(
name="erpnext_approve_leave_application",
description="Approve a Leave Application.",
inputSchema={
"type": "object",
"required": ["leave_application_name"],
"properties": {"leave_application_name": {"type": "string"}},
},
),
Tool(
name="erpnext_reject_leave_application",
description="Reject a Leave Application.",
inputSchema={
"type": "object",
"required": ["leave_application_name"],
"properties": {
"leave_application_name": {"type": "string"},
"reason": {"type": "string"},
},
},
),
Tool(
name="erpnext_mark_attendance",
description="Mark attendance for an employee on a specific date.",
inputSchema={
"type": "object",
"required": ["employee", "attendance_date", "status"],
"properties": {
"employee": {"type": "string"},
"attendance_date": {"type": "string", "description": "YYYY-MM-DD"},
"status": {"type": "string", "enum": ["Present", "Absent", "Half Day", "Work From Home", "On Leave"]},
"shift": {"type": "string"},
},
},
),
Tool(
name="erpnext_approve_expense_claim",
description="Approve an Expense Claim.",
inputSchema={
"type": "object",
"required": ["expense_claim_name"],
"properties": {"expense_claim_name": {"type": "string"}},
},
),
# ── Support ──────────────────────────────────────────────────────────
Tool(
name="erpnext_create_issue",
description="Create a new Support Issue.",
inputSchema={
"type": "object",
"required": ["subject"],
"properties": {
"subject": {"type": "string"},
"customer": {"type": "string"},
"description": {"type": "string"},
"priority": {"type": "string", "enum": ["Low", "Medium", "High", "Urgent"], "default": "Medium"},
"issue_type": {"type": "string"},
"raised_by": {"type": "string"},
},
},
),
Tool(
name="erpnext_close_issue",
description="Close a Support Issue.",
inputSchema={
"type": "object",
"required": ["issue_name"],
"properties": {
"issue_name": {"type": "string"},
"resolution_details": {"type": "string"},
},
},
),
# ── Projects ─────────────────────────────────────────────────────────
Tool(
name="erpnext_create_task",
description="Create a Task in a Project.",
inputSchema={
"type": "object",
"required": ["subject"],
"properties": {
"subject": {"type": "string"},
"project": {"type": "string"},
"assigned_to": {"type": "string"},
"exp_start_date": {"type": "string"},
"exp_end_date": {"type": "string"},
"priority": {"type": "string", "enum": ["Low", "Medium", "High", "Urgent"]},
"description": {"type": "string"},
},
},
),
Tool(
name="erpnext_update_task_status",
description="Update the status of a Task.",
inputSchema={
"type": "object",
"required": ["task_name", "status"],
"properties": {
"task_name": {"type": "string"},
"status": {"type": "string", "enum": ["Open", "Working", "Pending Review", "Overdue", "Template", "Cancelled", "Completed"]},
},
},
),
Tool(
name="erpnext_log_timesheet_entry",
description="Log a timesheet entry for an employee on a task or project.",
inputSchema={
"type": "object",
"required": ["employee", "from_time", "to_time"],
"properties": {
"employee": {"type": "string"},
"from_time": {"type": "string", "description": "ISO datetime"},
"to_time": {"type": "string", "description": "ISO datetime"},
"project": {"type": "string"},
"task": {"type": "string"},
"activity_type": {"type": "string"},
"description": {"type": "string"},
},
},
),
]
def handlers() -> dict:
return {
"erpnext_create_quotation_from_opportunity": _make_quotation_from_opportunity,
"erpnext_create_sales_order_from_quotation": _make_so_from_quotation,
"erpnext_create_sales_invoice_from_sales_order": _make_si_from_so,
"erpnext_create_delivery_note_from_sales_order": _make_dn_from_so,
"erpnext_create_payment_entry_for_invoice": _make_payment_entry,
"erpnext_create_purchase_order_from_supplier_quotation": _make_po_from_sq,
"erpnext_create_purchase_receipt_from_purchase_order": _make_pr_from_po,
"erpnext_create_purchase_invoice_from_purchase_receipt": _make_pi_from_pr,
"erpnext_get_item_stock_balance": _get_item_stock_balance,
"erpnext_transfer_stock": _transfer_stock,
"erpnext_create_stock_entry": _create_stock_entry,
"erpnext_approve_leave_application": _approve_leave,
"erpnext_reject_leave_application": _reject_leave,
"erpnext_mark_attendance": _mark_attendance,
"erpnext_approve_expense_claim": _approve_expense_claim,
"erpnext_create_issue": _create_issue,
"erpnext_close_issue": _close_issue,
"erpnext_create_task": _create_task,
"erpnext_update_task_status": _update_task_status,
"erpnext_log_timesheet_entry": _log_timesheet,
}
async def _make_quotation_from_opportunity(args: dict) -> str:
client = FrappeClient()
result = await client.call_method(
"erpnext.crm.doctype.opportunity.opportunity.make_quotation",
source_name=args["opportunity_name"],
)
return json.dumps(result, indent=2)
async def _make_so_from_quotation(args: dict) -> str:
client = FrappeClient()
result = await client.call_method(
"erpnext.selling.doctype.quotation.quotation.make_sales_order",
source_name=args["quotation_name"],
)
return json.dumps(result, indent=2)
async def _make_si_from_so(args: dict) -> str:
client = FrappeClient()
result = await client.call_method(
"erpnext.selling.doctype.sales_order.sales_order.make_sales_invoice",
source_name=args["sales_order_name"],
)
return json.dumps(result, indent=2)
async def _make_dn_from_so(args: dict) -> str:
client = FrappeClient()
result = await client.call_method(
"erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
source_name=args["sales_order_name"],
)
return json.dumps(result, indent=2)
async def _make_payment_entry(args: dict) -> str:
client = FrappeClient()
method = (
"erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account"
if args["invoice_doctype"] == "Sales Invoice"
else "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_payment_entry"
)
result = await client.call_method(
"erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry",
dt=args["invoice_doctype"],
dn=args["invoice_name"],
)
return json.dumps(result, indent=2)
async def _make_po_from_sq(args: dict) -> str:
client = FrappeClient()
result = await client.call_method(
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.make_purchase_order",
source_name=args["supplier_quotation_name"],
)
return json.dumps(result, indent=2)
async def _make_pr_from_po(args: dict) -> str:
client = FrappeClient()
result = await client.call_method(
"erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
source_name=args["purchase_order_name"],
)
return json.dumps(result, indent=2)
async def _make_pi_from_pr(args: dict) -> str:
client = FrappeClient()
result = await client.call_method(
"erpnext.buying.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice",
source_name=args["purchase_receipt_name"],
)
return json.dumps(result, indent=2)
async def _get_item_stock_balance(args: dict) -> str:
client = FrappeClient()
result = await client.call_method(
"erpnext.stock.utils.get_stock_balance",
item_code=args["item_code"],
warehouse=args.get("warehouse", ""),
posting_date=args.get("posting_date", ""),
)
return json.dumps({"item_code": args["item_code"], "warehouse": args.get("warehouse"), "balance": result}, indent=2)
async def _transfer_stock(args: dict) -> str:
client = FrappeClient()
payload = {
"stock_entry_type": "Material Transfer",
"posting_date": args.get("posting_date", ""),
"remarks": args.get("remarks", ""),
"items": [{
"item_code": args["item_code"],
"qty": args["qty"],
"s_warehouse": args["from_warehouse"],
"t_warehouse": args["to_warehouse"],
}],
}
result = await client.create_doc("Stock Entry", payload)
return json.dumps(result, indent=2)
async def _create_stock_entry(args: dict) -> str:
client = FrappeClient()
payload = {
"stock_entry_type": args["stock_entry_type"],
"posting_date": args.get("posting_date", ""),
"remarks": args.get("remarks", ""),
"items": args["items"],
}
result = await client.create_doc("Stock Entry", payload)
return json.dumps(result, indent=2)
async def _approve_leave(args: dict) -> str:
client = FrappeClient()
result = await client.update_doc("Leave Application", args["leave_application_name"], {"status": "Approved"})
return json.dumps(result, indent=2)
async def _reject_leave(args: dict) -> str:
client = FrappeClient()
updates = {"status": "Rejected"}
if reason := args.get("reason"):
updates["reason_for_rejection"] = reason
result = await client.update_doc("Leave Application", args["leave_application_name"], updates)
return json.dumps(result, indent=2)
async def _mark_attendance(args: dict) -> str:
client = FrappeClient()
payload = {
"employee": args["employee"],
"attendance_date": args["attendance_date"],
"status": args["status"],
"shift": args.get("shift", ""),
"docstatus": 1,
}
result = await client.create_doc("Attendance", payload)
return json.dumps(result, indent=2)
async def _approve_expense_claim(args: dict) -> str:
client = FrappeClient()
result = await client.update_doc("Expense Claim", args["expense_claim_name"], {"approval_status": "Approved"})
return json.dumps(result, indent=2)
async def _create_issue(args: dict) -> str:
client = FrappeClient()
payload = {
"subject": args["subject"],
"customer": args.get("customer", ""),
"description": args.get("description", ""),
"priority": args.get("priority", "Medium"),
"issue_type": args.get("issue_type", ""),
"raised_by": args.get("raised_by", ""),
}
result = await client.create_doc("Issue", payload)
return json.dumps(result, indent=2)
async def _close_issue(args: dict) -> str:
client = FrappeClient()
updates = {"status": "Closed"}
if res := args.get("resolution_details"):
updates["resolution_details"] = res
result = await client.update_doc("Issue", args["issue_name"], updates)
return json.dumps(result, indent=2)
async def _create_task(args: dict) -> str:
client = FrappeClient()
result = await client.create_doc("Task", args)
return json.dumps(result, indent=2)
async def _update_task_status(args: dict) -> str:
client = FrappeClient()
result = await client.update_doc("Task", args["task_name"], {"status": args["status"]})
return json.dumps(result, indent=2)
async def _log_timesheet(args: dict) -> str:
client = FrappeClient()
payload = {
"employee": args["employee"],
"time_logs": [{
"from_time": args["from_time"],
"to_time": args["to_time"],
"project": args.get("project", ""),
"task": args.get("task", ""),
"activity_type": args.get("activity_type", ""),
"description": args.get("description", ""),
}],
}
result = await client.create_doc("Timesheet", payload)
return json.dumps(result, indent=2)