"""Translation, Assignment Rules, and User Permission Restriction tools.""" import json from mcp.types import Tool from frappe_mcp.client.frappe_api import FrappeClient def tools() -> list[Tool]: return [ # --- Translations --- Tool( name="frappe_list_translations", description="List custom translations for a language.", inputSchema={ "type": "object", "properties": { "language": {"type": "string", "description": "Language code e.g. 'ar', 'fr', 'de'"}, "source_text": {"type": "string"}, "limit": {"type": "integer", "default": 50}, }, }, ), Tool( name="frappe_add_translation", description="Add or update a custom translation string.", inputSchema={ "type": "object", "required": ["language", "source_text", "translated_text"], "properties": { "language": {"type": "string"}, "source_text": {"type": "string"}, "translated_text": {"type": "string"}, "context": {"type": "string"}, }, }, ), Tool( name="frappe_remove_translation", description="Remove a custom translation entry.", inputSchema={ "type": "object", "required": ["name"], "properties": {"name": {"type": "string"}}, }, ), # --- Assignment Rules --- Tool( name="frappe_list_assignment_rules", description="List all Assignment Rules.", inputSchema={ "type": "object", "properties": { "document_type": {"type": "string"}, "disabled": {"type": "integer"}, "limit": {"type": "integer", "default": 20}, }, }, ), Tool( name="frappe_create_assignment_rule", description=( "Create an Assignment Rule — automatically assigns documents to users " "based on conditions (load balancing or rule-based)." ), inputSchema={ "type": "object", "required": ["name", "document_type", "assign_condition"], "properties": { "name": {"type": "string"}, "document_type": {"type": "string"}, "assign_condition": {"type": "string", "description": "Python expression e.g. doc.status == 'Open'"}, "unassign_condition": {"type": "string"}, "close_condition": {"type": "string"}, "assignment_days": { "type": "array", "items": {"type": "object"}, "description": "Days config for round-robin assignment", }, "users": { "type": "array", "items": {"type": "object"}, "description": "List of {user} objects", }, "due_date_based_on": {"type": "string"}, "priority": {"type": "string", "enum": ["Low", "Medium", "High"]}, "disabled": {"type": "integer", "default": 0}, }, }, ), # --- User Permissions (Row-Level Security) --- Tool( name="frappe_list_user_permissions", description="List User Permissions — row-level security rules that restrict which documents a user can see.", inputSchema={ "type": "object", "properties": { "user": {"type": "string"}, "allow": {"type": "string", "description": "DocType being restricted e.g. 'Company'"}, "limit": {"type": "integer", "default": 30}, }, }, ), Tool( name="frappe_add_user_permission", description=( "Add a User Permission — restricts a user to only see documents " "linked to a specific value. e.g. restrict user to one Company." ), inputSchema={ "type": "object", "required": ["user", "allow", "for_value"], "properties": { "user": {"type": "string"}, "allow": {"type": "string", "description": "DocType e.g. 'Company'"}, "for_value": {"type": "string", "description": "Specific document name"}, "apply_to_all_doctypes": {"type": "integer", "default": 1}, "applicable_for": {"type": "string", "description": "Restrict to specific DocType if not global"}, "hide_descendants": {"type": "integer", "default": 0}, }, }, ), Tool( name="frappe_remove_user_permission", description="Remove a User Permission.", inputSchema={ "type": "object", "required": ["name"], "properties": {"name": {"type": "string"}}, }, ), # --- Role Permission for Page/Report --- Tool( name="frappe_set_role_permission", description="Grant or revoke a role's access to a Page or Report.", inputSchema={ "type": "object", "required": ["doctype", "name", "role", "action"], "properties": { "doctype": {"type": "string", "enum": ["Page", "Report"]}, "name": {"type": "string"}, "role": {"type": "string"}, "action": {"type": "string", "enum": ["add", "remove"]}, }, }, ), ] def handlers() -> dict: return { "frappe_list_translations": _list_translations, "frappe_add_translation": _add_translation, "frappe_remove_translation": _remove_translation, "frappe_list_assignment_rules": _list_assignment_rules, "frappe_create_assignment_rule": _create_assignment_rule, "frappe_list_user_permissions": _list_user_permissions, "frappe_add_user_permission": _add_user_permission, "frappe_remove_user_permission": _remove_user_permission, "frappe_set_role_permission": _set_role_permission, } async def _list_translations(args: dict) -> str: client = FrappeClient() filters = [] if lang := args.get("language"): filters.append(["language", "=", lang]) if src := args.get("source_text"): filters.append(["source_text", "like", f"%{src}%"]) result = await client.get_list( "Translation", fields=["name", "language", "source_text", "translated_text", "context"], filters=filters if filters else None, limit=args.get("limit", 50), ) return json.dumps(result, indent=2) async def _add_translation(args: dict) -> str: client = FrappeClient() result = await client.create_doc("Translation", args) return json.dumps(result, indent=2) async def _remove_translation(args: dict) -> str: client = FrappeClient() result = await client.delete_doc("Translation", args["name"]) return json.dumps(result, indent=2) async def _list_assignment_rules(args: dict) -> str: client = FrappeClient() filters = [] if dt := args.get("document_type"): filters.append(["document_type", "=", dt]) if "disabled" in args: filters.append(["disabled", "=", args["disabled"]]) result = await client.get_list( "Assignment Rule", fields=["name", "document_type", "disabled", "priority"], filters=filters if filters else None, limit=args.get("limit", 20), ) return json.dumps(result, indent=2) async def _create_assignment_rule(args: dict) -> str: client = FrappeClient() result = await client.create_doc("Assignment Rule", args) return json.dumps(result, indent=2) async def _list_user_permissions(args: dict) -> str: client = FrappeClient() filters = [] if user := args.get("user"): filters.append(["user", "=", user]) if allow := args.get("allow"): filters.append(["allow", "=", allow]) result = await client.get_list( "User Permission", fields=["name", "user", "allow", "for_value", "applicable_for", "apply_to_all_doctypes"], filters=filters if filters else None, limit=args.get("limit", 30), ) return json.dumps(result, indent=2) async def _add_user_permission(args: dict) -> str: client = FrappeClient() result = await client.create_doc("User Permission", args) return json.dumps(result, indent=2) async def _remove_user_permission(args: dict) -> str: client = FrappeClient() result = await client.delete_doc("User Permission", args["name"]) return json.dumps(result, indent=2) async def _set_role_permission(args: dict) -> str: client = FrappeClient() method = "frappe.desk.doctype.desktop_icon.desktop_icon.set_hidden" doc = await client.get_doc(args["doctype"], args["name"]) roles = doc.get("roles", []) if args["action"] == "add": if args["role"] not in [r.get("role") for r in roles]: roles.append({"role": args["role"]}) else: roles = [r for r in roles if r.get("role") != args["role"]] result = await client.update_doc(args["doctype"], args["name"], {"roles": roles}) return json.dumps(result, indent=2)