delete_document
Remove documents from the Frappe Framework by specifying DocType and document name to manage data and maintain system integrity.
Instructions
Delete a document from Frappe.
Args:
doctype: DocType name
name: Document name (case-sensitive)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| doctype | Yes | ||
| name | Yes |
Implementation Reference
- src/tools/documents.py:180-348 (handler)The primary handler implementation for the 'delete_document' tool. It deletes Frappe documents via API, pre-checks docstatus, handles various errors (linked docs, permissions, submitted status) with user-friendly messages, and uses helpers for parsing.@mcp.tool() async def delete_document( doctype: str, name: str ) -> str: """ Delete a document from Frappe. This tool handles document deletion with comprehensive error handling, providing detailed feedback about why deletions fail and actionable guidance for resolving common deletion blockers. Args: doctype: DocType name name: Document name (case-sensitive) Returns: Success message if deleted, or detailed error information with corrective actions for linked documents, permissions, or constraints. """ try: client = get_client() # First, get the current document to check its status and understand constraints 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." # Provide guidance based on document status if current_docstatus == 1: return ( f"⚠️ Document {doctype} '{name}' is submitted (docstatus=1). " f"Submitted documents cannot be deleted. You must cancel it first using cancel_document, " f"then delete the cancelled document." ) elif current_docstatus == 2: # Cancelled documents can usually be deleted, but may have constraints pass # Continue with deletion attempt elif current_docstatus == 0: # Draft documents should be deletable, but may have linked documents pass # Continue with deletion attempt except Exception as get_error: # Document might not exist, which is fine for deletion pass # Attempt to delete the document response = await client.delete(f"api/resource/{doctype}/{name}") if response.get("message") == "ok": return f"✅ Document {doctype} '{name}' successfully deleted." else: return f"⚠️ Deletion 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 deletion validation errors if "linked" in str(exception_msg).lower() or "referenced" in str(exception_msg).lower(): # Extract linked document information directly from the error message linked_docs = _extract_linked_docs_from_error(str(exception_msg)) if linked_docs: linked_info = "\n".join([f" - {doc['doctype']} '{doc['name']}'" for doc in linked_docs]) return ( f"❌ Deletion failed: Document {doctype} '{name}' cannot be deleted because it is linked to other documents.\n" f"Blocking document(s):\n{linked_info}\n" f"To resolve: Delete, cancel, or unlink these documents first, then retry deletion." ) else: return ( f"❌ Deletion failed: Document {doctype} '{name}' cannot be deleted because it is linked to other documents. " f"Error details: {exception_msg}. " f"To resolve: Find and delete/cancel the documents that reference this one, or remove the links first." ) elif "Cannot delete" in str(exception_msg): return ( f"❌ Deletion failed: {exception_msg}. " f"This may be due to business rules, data integrity constraints, or linked transactions. " f"Check for related documents that need to be handled first." ) elif "submitted" in str(exception_msg).lower(): return ( f"❌ Deletion failed: Cannot delete submitted documents. " f"Use cancel_document first to cancel {doctype} '{name}', then try deleting the cancelled document." ) else: # Generic validation error return f"❌ Validation error: {exception_msg}. Please resolve the validation issues before deleting." elif "PermissionError" in str(exception_msg): return ( f"❌ Permission denied: You don't have sufficient permissions to delete {doctype} documents. " f"Contact your system administrator to request Delete permission for {doctype}." ) elif "IntegrityError" in str(exception_msg) or "foreign key" in str(exception_msg).lower(): return ( f"❌ Database constraint violation: Document {doctype} '{name}' cannot be deleted due to foreign key constraints. " f"Error: {exception_msg}. " f"This usually means other records reference this document. Find and handle those references first." ) else: # Other exceptions with helpful context return f"❌ Deletion 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") # Parse common server messages for actionable guidance if "linked" in user_message.lower(): return ( f"❌ Deletion failed: {user_message}. " f"To resolve: Identify and handle the linked documents first, then retry deletion." ) else: return f"❌ Deletion failed: {user_message}" except (json.JSONDecodeError, KeyError, IndexError): pass # Handle HTTP status codes for additional context if api_error.status_code == 403: return ( f"❌ Access forbidden: You don't have permission to delete {doctype} '{name}'. " f"Contact your administrator for Delete permissions on {doctype}." ) elif api_error.status_code == 404: return f"✅ Document {doctype} '{name}' not found - it may already be deleted or never existed." elif api_error.status_code == 417: # HTTP 417 errors may contain linked document information in the response error_text = str(api_error) if api_error.response_data: error_text += " " + str(api_error.response_data) linked_docs = _extract_linked_docs_from_error(error_text) if linked_docs: linked_info = "\n".join([f" - {doc['doctype']} '{doc['name']}'" for doc in linked_docs]) return ( f"❌ Deletion failed with HTTP 417 error. Found blocking document(s):\n{linked_info}\n" f"These documents may be preventing deletion. Try deleting, cancelling, or unlinking them first." ) else: return ( f"❌ Deletion failed with HTTP 417 error. This may be due to server configuration issues " f"or complex validation constraints. Error: {api_error}. " f"Check for business rule violations, permissions, or linked documents." ) else: return f"❌ Deletion failed: {api_error}" except Exception as error: return _format_error_response(error, "delete_document")
- src/server.py:40-40 (registration)Invocation of documents.register_tools(mcp) during MCP server initialization, which registers the delete_document tool among others.documents.register_tools(mcp)
- src/tools/documents.py:16-49 (helper)Helper function used exclusively in delete_document to parse Frappe deletion error messages and extract specific linked documents (doctype, name) that block deletion.def _extract_linked_docs_from_error(error_message: str) -> List[Dict[str, str]]: """ Extract linked document information from Frappe error messages. Frappe's LinkExistsError and similar messages often contain specific information about which documents are linked, which we can parse directly. """ import re linked_docs = [] # Common patterns in Frappe error messages for linked documents patterns = [ # "is linked with DocType <a href="...">Name</a>" r'is linked with (\w+(?:\s+\w+)*)\s+<a[^>]*>([^<]+)</a>', # "Cannot delete ... because ... is linked with DocType Name" r'is linked with (\w+(?:\s+\w+)*)\s+([A-Z0-9-]+)', # "referenced by DocType: Name" r'referenced by (\w+(?:\s+\w+)*):?\s+([A-Z0-9-]+)', ] for pattern in patterns: matches = re.findall(pattern, error_message, re.IGNORECASE) for match in matches: doctype, name = match # Clean up doctype (remove extra spaces, standardize) doctype = ' '.join(doctype.split()) linked_docs.append({ "doctype": doctype, "name": name, "source": "error_message" }) return linked_docs
- src/tools/documents.py:51-79 (helper)General helper for formatting errors, invoked specifically for delete_document errors.def _format_error_response(error: Exception, operation: str) -> str: """Format error response with detailed information.""" credentials_check = validate_api_credentials() # Build diagnostic information diagnostics = [ f"Error in {operation}", f"Error type: {type(error).__name__}", f"Is FrappeApiError: {isinstance(error, FrappeApiError)}", f"API Key available: {credentials_check['details']['api_key_available']}", f"API Secret available: {credentials_check['details']['api_secret_available']}" ] # Check for missing credentials first if not credentials_check["valid"]: error_msg = f"Authentication failed: {credentials_check['message']}. " error_msg += "API key/secret is the only supported authentication method." return error_msg # Handle FrappeApiError if isinstance(error, FrappeApiError): error_msg = f"Frappe API error: {error}" if error.status_code in (401, 403): error_msg += " Please check your API key and secret." return error_msg # Default error handling return f"Error in {operation}: {str(error)}"
- src/tools/documents.py:81-81 (registration)The register_tools function in documents.py defines and registers the delete_document tool using @mcp.tool() decorator.def register_tools(mcp: Any) -> None: