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

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

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
Behavior2/5

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

No annotations are provided, so the description carries the full burden. It states the action is 'Delete', implying a destructive mutation, but fails to disclose behavioral traits such as whether deletion is permanent, reversible, requires specific permissions, or has side effects. This is a significant gap for a mutation tool.

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

Conciseness5/5

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

The description is a single, efficient sentence with zero waste. It is appropriately sized and front-loaded, directly stating the tool's purpose without unnecessary details.

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?

Given the tool has an output schema (which handles return values) but no annotations and low schema coverage, the description is incomplete. It adequately states the basic action but lacks crucial details for a destructive operation, such as behavioral context and parameter explanations, leaving gaps in understanding.

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

Parameters2/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 mentions 'by title or permalink', which clarifies the 'identifier' parameter, but does not explain the 'project' parameter or its optionality. The description adds some meaning but does not fully address the undocumented parameters.

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 ('a note'), specifying it can be done 'by title or permalink'. However, it does not explicitly differentiate from sibling tools like 'delete_project' or 'move_note', which also involve deletion or modification operations.

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 such as 'edit_note' or 'move_note', nor does it mention prerequisites like required permissions or project context. It lacks explicit usage context or exclusions.

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

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