amend_document
Create amended versions of cancelled documents in Frappe by generating new documents with updated naming and proper field linkage.
Instructions
Amend a document in Frappe (create a new amended version of a cancelled document).
This tool handles document amendment by creating a new document with an amended name
(e.g., DOC-001-1, DOC-001-2) and copying all relevant field values from the original
cancelled document, establishing proper linkage via the 'amended_from' field.
Args:
doctype: DocType name
name: Document name (case-sensitive) - must be a cancelled document
Returns:
Success message with new amended document name if successful, or detailed
error information if amendment fails due to validation errors or constraints.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| doctype | Yes | ||
| name | Yes |
Implementation Reference
- src/tools/documents.py:762-919 (handler)The main handler function for the 'amend_document' tool. It checks if the document is cancelled (docstatus=2), generates a new amended document name (e.g., DOC-001-1), copies fields from the original excluding system fields, sets 'amended_from', clears child names, and creates the new draft document via Frappe API.@mcp.tool() async def amend_document( doctype: str, name: str ) -> str: """ Amend a document in Frappe (create a new amended version of a cancelled document). This tool handles document amendment by creating a new document with an amended name (e.g., DOC-001-1, DOC-001-2) and copying all relevant field values from the original cancelled document, establishing proper linkage via the 'amended_from' field. Args: doctype: DocType name name: Document name (case-sensitive) - must be a cancelled document Returns: Success message with new amended document name if successful, or detailed error information if amendment fails due to validation errors or constraints. """ try: client = get_client() # First, get the current document to check its status and get full data try: doc_response = await client.get(f"api/resource/{doctype}/{name}") doc_data = doc_response.get("data", {}) current_docstatus = doc_data.get("docstatus", None) if current_docstatus is None: return f"Error: Could not retrieve document {doctype} '{name}'. Document may not exist." if current_docstatus == 0: return f"Document {doctype} '{name}' is in Draft status. Only cancelled documents (docstatus=2) can be amended." if current_docstatus == 1: return f"Document {doctype} '{name}' is submitted. You must cancel it first before amending." if current_docstatus != 2: return f"Document {doctype} '{name}' has unexpected status (docstatus={current_docstatus}). Only cancelled documents (docstatus=2) can be amended." except Exception as get_error: return f"Error retrieving document for amendment: {get_error}" # Generate amended document name base_name = name amended_counter = 1 # Check if this document is already an amendment (contains dash and number) if '-' in name: parts = name.rsplit('-', 1) if len(parts) == 2 and parts[1].isdigit(): base_name = parts[0] amended_counter = int(parts[1]) + 1 # Find the next available amended name amended_name = f"{base_name}-{amended_counter}" while True: try: # Check if amended name already exists check_response = await client.get(f"api/resource/{doctype}/{amended_name}") if "data" in check_response: # Name exists, try next number amended_counter += 1 amended_name = f"{base_name}-{amended_counter}" else: # Name doesn't exist, we can use it break except FrappeApiError as e: # If we get 404, the name doesn't exist and we can use it if e.status_code == 404: break else: # Some other error, we should handle it raise e # Prepare amended document data amended_doc = doc_data.copy() # Clear system fields that should not be copied system_fields = [ 'name', 'creation', 'modified', 'modified_by', 'owner', 'docstatus', 'idx', '_user_tags', '_comments', '_assign', '_liked_by' ] for field in system_fields: amended_doc.pop(field, None) # Set amendment fields amended_doc['name'] = amended_name amended_doc['amended_from'] = name amended_doc['docstatus'] = 0 # New document starts as draft # Clear any child table names to let Frappe generate new ones for key, value in amended_doc.items(): if isinstance(value, list): for item in value: if isinstance(item, dict): item.pop('name', None) # Clear child table row names item['parent'] = amended_name # Update parent reference # Create the amended document response = await client.post( f"api/resource/{doctype}", json_data=amended_doc ) if "data" in response: created_doc = response["data"] created_name = created_doc.get('name', amended_name) return f"✅ Document successfully amended: {doctype} '{created_name}' created from cancelled document '{name}'. The amended document is in Draft status and ready for editing." else: return f"⚠️ Amendment may have succeeded but response format unexpected: {json.dumps(response, indent=2)}" except FrappeApiError as api_error: # Handle specific Frappe API errors with detailed information if api_error.response_data: error_data = api_error.response_data # Check for validation errors in the response if "exception" in error_data: exception_msg = error_data["exception"] # Extract user-friendly error messages if "ValidationError" in str(exception_msg): # Common amendment validation errors if "amended_from" in str(exception_msg).lower(): return ( f"❌ Amendment failed: {doctype} does not have an 'amended_from' field configured. " f"This DocType may not support amendments. Contact your system administrator to enable amendment functionality." ) elif "DuplicateEntryError" in str(exception_msg) or "duplicate" in str(exception_msg).lower(): return f"❌ Amendment failed: Document name conflict. The amended name may already exist. Please try again." else: # Generic validation error return f"❌ Validation error: {exception_msg}. Please fix the validation issues before amending." elif "PermissionError" in str(exception_msg): return f"❌ Permission denied: You don't have sufficient permissions to amend {doctype} documents." else: # Other exceptions return f"❌ Amendment failed: {exception_msg}" # Check for server messages with more details if "_server_messages" in error_data: try: messages = json.loads(error_data["_server_messages"]) if messages: msg_data = json.loads(messages[0]) user_message = msg_data.get("message", "Unknown error") return f"❌ Amendment failed: {user_message}" except (json.JSONDecodeError, KeyError, IndexError): pass return f"❌ Amendment failed: {api_error}" except Exception as error: return _format_error_response(error, "amend_document")
- src/server.py:40-40 (registration)Invocation of documents.register_tools(mcp), which registers the amend_document tool (and other document tools) with the MCP server instance.documents.register_tools(mcp)