diff --git a/src/basic_memory/mcp/tools/notes.py b/src/basic_memory/mcp/tools/notes.py index 5a0d0fb8..a337758e 100644 --- a/src/basic_memory/mcp/tools/notes.py +++ b/src/basic_memory/mcp/tools/notes.py @@ -23,7 +23,8 @@ async def write_note( content: str, folder: str, tags: Optional[List[str]] = None, -) -> str: + verbose: bool = False, +) -> EntityResponse | str: """Write a markdown note to the knowledge base. Args: @@ -31,9 +32,11 @@ async def write_note( content: Markdown content for the note folder: the folder where the file should be saved tags: Optional list of tags to categorize the note + verbose: If True, returns full EntityResponse with semantic info Returns: - Permalink that can be used to reference the note + If verbose=False: Permalink that can be used to reference the note + If verbose=True: EntityResponse with full semantic details Examples: # Create a simple note @@ -48,7 +51,7 @@ async def write_note( title="Security Review", content="# Findings\\n\\n1. Updated auth flow\\n2. Added rate limiting", folder="security", - tags=["security", "development"] + tags=["security", "development"] ) """ logger.info(f"Writing note folder:'{folder}' title: '{title}'") @@ -69,7 +72,7 @@ async def write_note( url = f"/knowledge/entities/{entity.permalink}" response = await call_put(client, url, json=entity.model_dump()) result = EntityResponse.model_validate(response.json()) - return result.permalink + return result if verbose else result.permalink @mcp.tool(description="Read a note's content by its title or permalink") diff --git a/src/basic_memory/schemas/response.py b/src/basic_memory/schemas/response.py index 0362a138..cf99984e 100644 --- a/src/basic_memory/schemas/response.py +++ b/src/basic_memory/schemas/response.py @@ -75,6 +75,15 @@ class RelationResponse(Relation, SQLAlchemyModel): "to_id", ), default=None ) + to_name: Optional[PathId] = Field( + # use the permalink from the associated Entity + # or the to_id value + validation_alias=AliasChoices( + AliasPath("to_entity", "title"), + "to_name", + ), default=None + ) + class EntityResponse(SQLAlchemyModel): diff --git a/tests/mcp/test_tool_notes.py b/tests/mcp/test_tool_notes.py index 1a5d3404..4bc7f056 100644 --- a/tests/mcp/test_tool_notes.py +++ b/tests/mcp/test_tool_notes.py @@ -4,6 +4,7 @@ import pytest from mcp.server.fastmcp.exceptions import ToolError from basic_memory.mcp.tools import notes +from basic_memory.schemas import EntityResponse @pytest.mark.asyncio @@ -187,3 +188,45 @@ async def test_delete_note_doesnt_exist(app): """ deleted = await notes.delete_note("doesnt-exist") assert deleted is False + +@pytest.mark.asyncio +async def test_write_note_verbose(app): + """Test creating a new note. + + Should: + - Create entity with correct type and content + - Save markdown content + - Handle tags correctly + - Return valid permalink + """ + entity = await notes.write_note( + title="Test Note", + folder="test", + content=""" +# Test\nThis is a test note + +- [note] First observation +- relates to [[Knowledge]] + +""", + tags=["test", "documentation"], + verbose=True, + ) + + assert isinstance(entity, EntityResponse) + + assert entity.title == "Test Note" + assert entity.file_path == "test/Test Note.md" + assert entity.entity_type == "note" + assert entity.permalink == "test/test-note" + + assert len(entity.observations) == 1 + assert entity.observations[0].content == "First observation" + + assert len(entity.relations) == 1 + assert entity.relations[0].relation_type == "relates to" + assert entity.relations[0].from_id == "test/test-note" + assert entity.relations[0].to_id is None + assert entity.relations[0].to_name == "Knowledge" + +