Skip to main content
Glama

delete_document

Remove documents from Frappe with detailed error handling and corrective guidance for linked records, permissions, or constraints.

Instructions

    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.
    

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
doctypeYes
nameYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The main execution handler for the 'delete_document' tool. Decorated with @mcp.tool(), it deletes Frappe documents via API with sophisticated error handling for linked docs, permissions, docstatus, extracting actionable insights from errors.
    @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) which defines and registers the delete_document tool via its @mcp.tool() decorator within the register_tools function.
    documents.register_tools(mcp)
  • Utility function called by delete_document to parse error messages and identify specific linked documents blocking deletion, enabling user-friendly feedback.
    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
  • Fallback error formatter used in delete_document's outermost exception handler to provide diagnostic information including credential status.
    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)}"
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively communicates that this is a destructive operation ('Delete') and describes error handling characteristics ('detailed feedback', 'actionable guidance'). However, it lacks information about rate limits, authentication requirements, or what specific 'linked documents, permissions, or constraints' might block deletion.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections (purpose, behavior, parameters, returns) and avoids unnecessary verbiage. The first sentence immediately states the core function. The only minor inefficiency is the slightly verbose phrasing in the behavioral section.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive operation with no annotations, the description does reasonably well by explaining the tool's purpose and error handling behavior. The existence of an output schema means it doesn't need to detail return values. However, given the complexity of document deletion in systems like Frappe (with potential dependencies and permissions), more context about prerequisites and constraints would be valuable.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It provides basic semantic information about the two parameters ('DocType name', 'Document name (case-sensitive)'), which adds value beyond the bare schema. However, it doesn't explain what constitutes valid doctype names or document names, nor does it provide examples or format requirements.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Delete') and resource ('document from Frappe'), making the purpose unambiguous. However, it doesn't explicitly differentiate this deletion tool from sibling tools like 'cancel_document' or 'amend_document', which might also modify document states in Frappe systems.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'cancel_document' or 'amend_document'. It mentions error handling for common deletion blockers, but doesn't specify prerequisites, permissions needed, or contextual factors that should influence tool selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/appliedrelevance/frappe-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server