mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
add delete_note tool
This commit is contained in:
@@ -9,7 +9,8 @@ from basic_memory.deps import (
|
||||
EntityServiceDep,
|
||||
get_search_service,
|
||||
RelationServiceDep,
|
||||
ObservationServiceDep, SearchServiceDep,
|
||||
ObservationServiceDep,
|
||||
SearchServiceDep, LinkResolverDep,
|
||||
)
|
||||
from basic_memory.schemas import (
|
||||
CreateEntityRequest,
|
||||
@@ -21,7 +22,6 @@ from basic_memory.schemas import (
|
||||
DeleteObservationsRequest,
|
||||
DeleteRelationsRequest,
|
||||
DeleteEntitiesRequest,
|
||||
UpdateEntityRequest,
|
||||
)
|
||||
from basic_memory.schemas.base import PathId, Entity
|
||||
from basic_memory.services.exceptions import EntityNotFoundError
|
||||
@@ -30,33 +30,31 @@ router = APIRouter(prefix="/knowledge", tags=["knowledge"])
|
||||
|
||||
## Create endpoints
|
||||
|
||||
|
||||
@router.put("/entities/{permalink:path}", response_model=EntityResponse)
|
||||
async def create_or_update_entity(
|
||||
permalink: PathId,
|
||||
data: Entity,
|
||||
response: Response,
|
||||
background_tasks: BackgroundTasks,
|
||||
entity_service: EntityServiceDep,
|
||||
search_service: SearchServiceDep,
|
||||
permalink: PathId,
|
||||
data: Entity,
|
||||
response: Response,
|
||||
background_tasks: BackgroundTasks,
|
||||
entity_service: EntityServiceDep,
|
||||
search_service: SearchServiceDep,
|
||||
) -> EntityResponse:
|
||||
"""Create or update an entity. If entity exists, it will be updated, otherwise created."""
|
||||
# Validate permalink matches
|
||||
if data.permalink != permalink:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Entity permalink must match URL path"
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="Entity permalink must match URL path")
|
||||
|
||||
# Try create_or_update operation
|
||||
entity, created = await entity_service.create_or_update_entity(data)
|
||||
response.status_code = 201 if created else 200
|
||||
|
||||
|
||||
# Always reindex since content has changed
|
||||
await search_service.index_entity(entity, background_tasks=background_tasks)
|
||||
|
||||
return EntityResponse.model_validate(entity)
|
||||
|
||||
|
||||
|
||||
@router.post("/entities", response_model=EntityListResponse)
|
||||
async def create_entities(
|
||||
data: CreateEntityRequest,
|
||||
@@ -76,7 +74,6 @@ async def create_entities(
|
||||
)
|
||||
|
||||
|
||||
|
||||
@router.post("/relations", response_model=EntityListResponse)
|
||||
async def create_relations(
|
||||
data: CreateRelationsRequest,
|
||||
@@ -154,6 +151,29 @@ async def get_entities(
|
||||
## Delete endpoints
|
||||
|
||||
|
||||
@router.delete("/entities/{identifier:path}", response_model=DeleteEntitiesResponse)
|
||||
async def delete_entity(
|
||||
identifier: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
entity_service: EntityServiceDep,
|
||||
link_resolver: LinkResolverDep,
|
||||
search_service=Depends(get_search_service),
|
||||
) -> DeleteEntitiesResponse:
|
||||
"""Delete a single entity and remove from search index."""
|
||||
|
||||
entity = await link_resolver.resolve_link(identifier)
|
||||
if entity is None:
|
||||
return DeleteEntitiesResponse(deleted=False)
|
||||
|
||||
# Delete the entity
|
||||
deleted = await entity_service.delete_entity(entity.permalink)
|
||||
|
||||
# Remove from search index
|
||||
background_tasks.add_task(search_service.delete_by_permalink, entity.permalink)
|
||||
|
||||
return DeleteEntitiesResponse(deleted=deleted)
|
||||
|
||||
|
||||
@router.post("/entities/delete", response_model=DeleteEntitiesResponse)
|
||||
async def delete_entities(
|
||||
data: DeleteEntitiesRequest,
|
||||
|
||||
@@ -10,12 +10,11 @@ from loguru import logger
|
||||
|
||||
from basic_memory.mcp.server import mcp
|
||||
from basic_memory.mcp.async_client import client
|
||||
from basic_memory.schemas import EntityResponse
|
||||
from basic_memory.schemas.request import CreateEntityRequest
|
||||
from basic_memory.schemas import EntityResponse, DeleteEntitiesResponse
|
||||
from basic_memory.schemas.base import Entity, Relation
|
||||
from basic_memory.schemas.request import CreateRelationsRequest
|
||||
from basic_memory.mcp.tools.knowledge import create_entities, create_relations
|
||||
from basic_memory.mcp.tools.utils import call_get, call_put
|
||||
from basic_memory.mcp.tools.knowledge import create_relations
|
||||
from basic_memory.mcp.tools.utils import call_get, call_put, call_delete
|
||||
|
||||
|
||||
@mcp.tool(
|
||||
@@ -55,18 +54,18 @@ async def write_note(
|
||||
# Create the entity request
|
||||
metadata = {"tags": [f"#{tag}" for tag in tags]} if tags else None
|
||||
entity = Entity(
|
||||
title=title,
|
||||
entity_type="note",
|
||||
content_type="text/markdown",
|
||||
content=content,
|
||||
entity_metadata=metadata,
|
||||
)
|
||||
title=title,
|
||||
entity_type="note",
|
||||
content_type="text/markdown",
|
||||
content=content,
|
||||
entity_metadata=metadata,
|
||||
)
|
||||
|
||||
# Use existing knowledge tool
|
||||
logger.info(f"Creating {entity.permalink}")
|
||||
url = f"/knowledge/entities/{entity.permalink}"
|
||||
response = await call_put(client, url, json=entity.model_dump())
|
||||
result = EntityResponse.model_validate(response.json())
|
||||
result = EntityResponse.model_validate(response.json())
|
||||
return result.permalink
|
||||
|
||||
|
||||
@@ -137,3 +136,25 @@ async def link_notes(
|
||||
)
|
||||
response = await create_relations(request)
|
||||
return response.entities[0].permalink
|
||||
|
||||
|
||||
@mcp.tool(description="Delete a note by title or permalink")
|
||||
async def delete_note(identifier: str) -> bool:
|
||||
"""Delete a note from the knowledge base.
|
||||
|
||||
Args:
|
||||
identifier: Note title or permalink
|
||||
|
||||
Returns:
|
||||
True if note was deleted, False otherwise
|
||||
|
||||
Examples:
|
||||
# Delete by title
|
||||
delete_note("Meeting Notes: Project Planning")
|
||||
|
||||
# Delete by permalink
|
||||
delete_note("notes/project-planning")
|
||||
"""
|
||||
response = await call_delete(client, f"/knowledge/entities/{identifier}")
|
||||
result = DeleteEntitiesResponse.model_validate(response.json())
|
||||
return result.deleted
|
||||
|
||||
@@ -29,7 +29,6 @@ async def call_get(
|
||||
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
||||
extensions: RequestExtensions | None = None,
|
||||
) -> Response:
|
||||
|
||||
logger.debug(f"Calling GET '{url}' params: '{params}'")
|
||||
try:
|
||||
response = await client.get(
|
||||
@@ -123,3 +122,33 @@ async def call_post(
|
||||
except HTTPStatusError as e:
|
||||
logger.error(f"Error calling POST {url}: {e}")
|
||||
raise ToolError(f"Error calling tool: {e}") from e
|
||||
|
||||
|
||||
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:
|
||||
try:
|
||||
response = await client.delete(
|
||||
url=url,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
auth=auth,
|
||||
follow_redirects=follow_redirects,
|
||||
timeout=timeout,
|
||||
extensions=extensions,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except HTTPStatusError as e:
|
||||
logger.error(f"Error calling DELETE {url}: {e}")
|
||||
raise ToolError(f"Error calling tool: {e}") from e
|
||||
|
||||
Reference in New Issue
Block a user