Skip to main content
Glama

delete_note

Remove notes from your knowledge management system by specifying their title or permalink, helping you maintain an organized semantic graph of stored conversations.

Instructions

Delete a note by title or permalink

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
identifierYes
projectNo

Implementation Reference

  • The core handler function for the 'delete_note' MCP tool. Resolves the note identifier to an entity ID, performs the delete API call, handles various errors, and returns user-friendly formatted error messages or success indicators.
    @mcp.tool(description="Delete a note by title or permalink")
    async def delete_note(
        identifier: str, project: Optional[str] = None, context: Context | None = None
    ) -> bool | str:
        """Delete a note from the knowledge base.
    
        Permanently removes a note from the specified project. The note is identified
        by title or permalink. If the note doesn't exist, the operation returns False
        without error. If deletion fails due to other issues, helpful error messages are provided.
    
        Project Resolution:
        Server resolves projects in this order: Single Project Mode → project parameter → default project.
        If project unknown, use list_memory_projects() or recent_activity() first.
    
        Args:
            project: Project name to delete from. Optional - server will resolve using hierarchy.
                    If unknown, use list_memory_projects() to discover available projects.
            identifier: Note title or permalink to delete
                       Can be a title like "Meeting Notes" or permalink like "notes/meeting-notes"
            context: Optional FastMCP context for performance caching.
    
        Returns:
            True if note was successfully deleted, False if note was not found.
            On errors, returns a formatted string with helpful troubleshooting guidance.
    
        Examples:
            # Delete by title
            delete_note("my-project", "Meeting Notes: Project Planning")
    
            # Delete by permalink
            delete_note("work-docs", "notes/project-planning")
    
            # Delete with exact path
            delete_note("research", "experiments/ml-model-results")
    
            # Common usage pattern
            if delete_note("my-project", "old-draft"):
                print("Note deleted successfully")
            else:
                print("Note not found or already deleted")
    
        Raises:
            HTTPError: If project doesn't exist or is inaccessible
            SecurityError: If identifier attempts path traversal
    
        Warning:
            This operation is permanent and cannot be undone. The note file
            will be removed from the filesystem and all references will be lost.
    
        Note:
            If the note is not found, this function provides helpful error messages
            with suggestions for finding the correct identifier, including search
            commands and alternative formats to try.
        """
        track_mcp_tool("delete_note")
        async with get_client() as client:
            active_project = await get_active_project(client, project, context)
    
            try:
                # Resolve identifier to entity ID
                entity_id = await resolve_entity_id(client, active_project.external_id, identifier)
            except ToolError as e:
                # If entity not found, return False (note doesn't exist)
                if "Entity not found" in str(e) or "not found" in str(e).lower():
                    logger.warning(f"Note not found for deletion: {identifier}")
                    return False
                # For other resolution errors, return formatted error message
                logger.error(  # pragma: no cover
                    f"Delete failed for '{identifier}': {e}, project: {active_project.name}"
                )
                return _format_delete_error_response(  # pragma: no cover
                    active_project.name, str(e), identifier
                )
    
            try:
                # Call the DELETE endpoint
                response = await call_delete(
                    client, f"/v2/projects/{active_project.external_id}/knowledge/entities/{entity_id}"
                )
                result = DeleteEntitiesResponse.model_validate(response.json())
    
                if result.deleted:
                    logger.info(
                        f"Successfully deleted note: {identifier} in project: {active_project.name}"
                    )
                    return True
                else:
                    logger.warning(  # pragma: no cover
                        f"Delete operation completed but note was not deleted: {identifier}"
                    )
                    return False  # pragma: no cover
    
            except Exception as e:  # pragma: no cover
                logger.error(f"Delete failed for '{identifier}': {e}, project: {active_project.name}")
                # Return formatted error message for better user experience
                return _format_delete_error_response(active_project.name, str(e), identifier)
  • Import statement that loads and registers the delete_note tool function via its @mcp.tool decorator when the tools package is imported.
    from basic_memory.mcp.tools.delete_note import delete_note
  • Local helper function that generates detailed, user-friendly error messages for delete operations, covering not found, permissions, system, and database errors.
    def _format_delete_error_response(project: str, error_message: str, identifier: str) -> str:
        """Format helpful error responses for delete failures that guide users to successful deletions."""
    
        # Note not found errors
        if "entity not found" in error_message.lower() or "not found" in error_message.lower():
            search_term = identifier.split("/")[-1] if "/" in identifier else identifier
            title_format = (
                identifier.split("/")[-1].replace("-", " ").title() if "/" in identifier else identifier
            )
            permalink_format = identifier.lower().replace(" ", "-")
    
            return dedent(f"""
                # Delete Failed - Note Not Found
    
                The note '{identifier}' could not be found for deletion in {project}.
    
                ## This might mean:
                1. **Already deleted**: The note may have been deleted previously
                2. **Wrong identifier**: The identifier format might be incorrect
                3. **Different project**: The note might be in a different project
    
                ## How to verify:
                1. **Search for the note**: Use `search_notes("{project}", "{search_term}")` to find it
                2. **Try different formats**:
                   - If you used a permalink like "folder/note-title", try just the title: "{title_format}"
                   - If you used a title, try the permalink format: "{permalink_format}"
    
                3. **Check if already deleted**: Use `list_directory("/")` to see what notes exist
                4. **List notes in project**: Use `list_directory("/")` to see what notes exist in the current project
    
                ## If the note actually exists:
                ```
                # First, find the correct identifier:
                search_notes("{project}", "{identifier}")
    
                # Then delete using the correct identifier:
                delete_note("{project}", "correct-identifier-from-search")
                ```
    
                ## If you want to delete multiple similar notes:
                Use search to find all related notes and delete them one by one.
                """).strip()
    
        # Permission/access errors
        if (
            "permission" in error_message.lower()
            or "access" in error_message.lower()
            or "forbidden" in error_message.lower()
        ):
            return f"""# Delete Failed - Permission Error
    
    You don't have permission to delete '{identifier}': {error_message}
    
    ## How to resolve:
    1. **Check permissions**: Verify you have delete/write access to this project
    2. **File locks**: The note might be open in another application
    3. **Project access**: Ensure you're in the correct project with proper permissions
    
    ## Alternative actions:
    - List available projects: `list_memory_projects()`
    - Specify the correct project: `delete_note("{identifier}", project="project-name")`
    - Verify note exists first: `read_note("{identifier}", project="project-name")`
    
    ## If you have read-only access:
    Ask someone with write access to delete the note."""
    
        # Server/filesystem errors
        if (
            "server error" in error_message.lower()
            or "filesystem" in error_message.lower()
            or "disk" in error_message.lower()
        ):
            return f"""# Delete Failed - System Error
    
    A system error occurred while deleting '{identifier}': {error_message}
    
    ## Immediate steps:
    1. **Try again**: The error might be temporary
    2. **Check file status**: Verify the file isn't locked or in use
    3. **Check disk space**: Ensure the system has adequate storage
    
    ## Troubleshooting:
    - Verify note exists: `read_note("{project}","{identifier}")`
    - Try again in a few moments
    
    ## If problem persists:
    Send a message to support@basicmachines.co - there may be a filesystem or database issue."""
    
        # Database/sync errors
        if "database" in error_message.lower() or "sync" in error_message.lower():
            return f"""# Delete Failed - Database Error
    
    A database error occurred while deleting '{identifier}': {error_message}
    
    ## This usually means:
    1. **Sync conflict**: The file system and database are out of sync
    2. **Database lock**: Another operation is accessing the database
    3. **Corrupted entry**: The database entry might be corrupted
    
    ## Steps to resolve:
    1. **Try again**: Wait a moment and retry the deletion
    2. **Check note status**: `read_note("{project}","{identifier}")` to see current state
    3. **Manual verification**: Use `list_directory()` to see if file still exists
    
    ## If the note appears gone but database shows it exists:
    Send a message to support@basicmachines.co - a manual database cleanup may be needed."""
    
        # Generic fallback
        return f"""# Delete Failed
    
    Error deleting note '{identifier}': {error_message}
    
    ## General troubleshooting:
    1. **Verify the note exists**: `read_note("{project}", "{identifier}")` or `search_notes("{project}", "{identifier}")`
    2. **Check permissions**: Ensure you can edit/delete files in this project
    3. **Try again**: The error might be temporary
    4. **Check project**: Make sure you're in the correct project
    
    ## Step-by-step approach:
    ```
    # 1. Confirm note exists and get correct identifier
    search_notes("{project}", "{identifier}")
    
    # 2. Read the note to verify access
    read_note("{project}", "correct-identifier-from-search")
    
    # 3. Try deletion with correct identifier
    delete_note("{project}", "correct-identifier-from-search")
    ```
    
    ## Alternative approaches:
    - Check what notes exist: `list_directory("{project}", "/")`
    
    ## Need help?
    If the note should be deleted but the operation keeps failing, send a message to support@basicmemory.com."""
  • Helper utility to resolve a note identifier (title/permalink) to the internal entity external_id via the API.
    async def resolve_entity_id(client: AsyncClient, project_external_id: str, identifier: str) -> str:
        """Resolve a string identifier to an entity external_id using the v2 API.
    
        Args:
            client: HTTP client for API calls
            project_external_id: Project external ID (UUID)
            identifier: The identifier to resolve (permalink, title, or path)
    
        Returns:
            The resolved entity external_id (UUID)
    
        Raises:
            ToolError: If the identifier cannot be resolved
        """
        try:
            response = await call_post(
                client, f"/v2/projects/{project_external_id}/knowledge/resolve", json={"identifier": identifier}
            )
            data = response.json()
            return data["external_id"]
        except HTTPStatusError as e:
            if e.response.status_code == 404:  # pragma: no cover
                raise ToolError(f"Entity not found: '{identifier}'")  # pragma: no cover
            raise ToolError(f"Error resolving identifier '{identifier}': {e}")  # pragma: no cover
        except Exception as e:
            raise ToolError(f"Unexpected error resolving identifier '{identifier}': {e}")  # pragma: no cover
  • Helper utility to perform the actual DELETE HTTP request to the API endpoint with comprehensive error handling and logging.
    async def call_delete(
        client: AsyncClient,
        url: URL | str,
        *,
        params: QueryParamTypes | None = None,
        headers: HeaderTypes | None = None,
        cookies: CookieTypes | None = None,
        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
        extensions: RequestExtensions | None = None,
    ) -> Response:
        """Make a DELETE request and handle errors appropriately.
    
        Args:
            client: The HTTPX AsyncClient to use
            url: The URL to request
            params: Query parameters
            headers: HTTP headers
            cookies: HTTP cookies
            auth: Authentication
            follow_redirects: Whether to follow redirects
            timeout: Request timeout
            extensions: HTTPX extensions
    
        Returns:
            The HTTP response
    
        Raises:
            ToolError: If the request fails with an appropriate error message
        """
        logger.debug(f"Calling DELETE '{url}'")
        error_message = None
    
        try:
            response = await client.delete(
                url=url,
                params=params,
                headers=headers,
                cookies=cookies,
                auth=auth,
                follow_redirects=follow_redirects,
                timeout=timeout,
                extensions=extensions,
            )
    
            if response.is_success:
                return response
    
            # Handle different status codes differently
            status_code = response.status_code
            # get the message if available
            response_data = response.json()
            if isinstance(response_data, dict) and "detail" in response_data:
                error_message = response_data["detail"]  # pragma: no cover
            else:
                error_message = get_error_message(status_code, url, "DELETE")
    
            # Log at appropriate level based on status code
            if 400 <= status_code < 500:
                # Client errors: log as info except for 429 (Too Many Requests)
                if status_code == 429:  # pragma: no cover
                    logger.warning(f"Rate limit exceeded: DELETE {url}: {error_message}")
                else:
                    logger.info(f"Client error: DELETE {url}: {error_message}")
            else:  # pragma: no cover
                # Server errors: log as error
                logger.error(f"Server error: DELETE {url}: {error_message}")
    
            # Raise a tool error with the friendly message
            response.raise_for_status()  # Will always raise since we're in the error case
            return response  # This line will never execute, but it satisfies the type checker  # pragma: no cover
    
        except HTTPStatusError as e:
            raise ToolError(error_message) from e

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/basicmachines-co/basic-memory'

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