"""Custom Fields tools — add, modify, remove custom fields on any DocType.""" import json from mcp.types import Tool from frappe_mcp.client.frappe_api import FrappeClient _FIELD_TYPES = [ "Data", "Int", "Float", "Currency", "Percent", "Check", "Small Text", "Text", "Long Text", "Text Editor", "HTML", "Date", "Datetime", "Time", "Duration", "Select", "Link", "Dynamic Link", "Table", "Table MultiSelect", "Attach", "Attach Image", "Signature", "Color", "Barcode", "Geolocation", "Rating", "Section Break", "Column Break", "Tab Break", ] _FIELD_SCHEMA = { "type": "object", "required": ["dt", "fieldname", "fieldtype", "label"], "properties": { "dt": {"type": "string", "description": "DocType to add the field to"}, "fieldname": {"type": "string"}, "fieldtype": {"type": "string", "enum": _FIELD_TYPES}, "label": {"type": "string"}, "options": {"type": "string", "description": "For Link: target DocType. For Select: newline-separated options."}, "reqd": {"type": "integer", "default": 0}, "in_list_view": {"type": "integer", "default": 0}, "in_standard_filter": {"type": "integer", "default": 0}, "insert_after": {"type": "string", "description": "Fieldname to insert after"}, "default": {"type": "string"}, "description": {"type": "string"}, "hidden": {"type": "integer", "default": 0}, "read_only": {"type": "integer", "default": 0}, "bold": {"type": "integer", "default": 0}, }, } def tools() -> list[Tool]: return [ Tool( name="frappe_add_custom_field", description="Add a custom field to an existing DocType.", inputSchema=_FIELD_SCHEMA, ), Tool( name="frappe_update_custom_field", description="Update properties of an existing custom field.", inputSchema={ "type": "object", "required": ["dt", "fieldname"], "properties": { "dt": {"type": "string"}, "fieldname": {"type": "string"}, "updates": {"type": "object", "description": "Properties to update"}, }, }, ), Tool( name="frappe_remove_custom_field", description="Remove a custom field from a DocType.", inputSchema={ "type": "object", "required": ["dt", "fieldname"], "properties": { "dt": {"type": "string"}, "fieldname": {"type": "string"}, }, }, ), Tool( name="frappe_list_custom_fields", description="List all custom fields for a DocType.", inputSchema={ "type": "object", "required": ["dt"], "properties": { "dt": {"type": "string"}, }, }, ), ] def handlers() -> dict: return { "frappe_add_custom_field": _add_custom_field, "frappe_update_custom_field": _update_custom_field, "frappe_remove_custom_field": _remove_custom_field, "frappe_list_custom_fields": _list_custom_fields, } async def _add_custom_field(args: dict) -> str: client = FrappeClient() payload = {k: v for k, v in args.items()} result = await client.create_doc("Custom Field", payload) return json.dumps(result, indent=2) async def _update_custom_field(args: dict) -> str: client = FrappeClient() # Custom Field name is "{DocType}-{fieldname}" cf_name = f"{args['dt']}-{args['fieldname']}" result = await client.update_doc("Custom Field", cf_name, args.get("updates", {})) return json.dumps(result, indent=2) async def _remove_custom_field(args: dict) -> str: client = FrappeClient() cf_name = f"{args['dt']}-{args['fieldname']}" result = await client.delete_doc("Custom Field", cf_name) return json.dumps(result, indent=2) async def _list_custom_fields(args: dict) -> str: client = FrappeClient() result = await client.get_list( "Custom Field", fields=["name", "fieldname", "fieldtype", "label", "reqd", "hidden"], filters=[["dt", "=", args["dt"]]], limit=100, ) return json.dumps(result, indent=2)