Skip to content

Commit 7789864

Browse files
phernandezclaude[bot]claude
authored
fix: add entity_type parameter to write_note MCP tool (#145)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent c6215fd commit 7789864

File tree

3 files changed

+198
-4
lines changed

3 files changed

+198
-4
lines changed

src/basic_memory/mcp/tools/write_note.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ async def write_note(
2727
content: str,
2828
folder: str,
2929
tags=None, # Remove type hint completely to avoid schema issues
30+
entity_type: str = "note",
3031
project: Optional[str] = None,
3132
) -> str:
3233
"""Write a markdown note to the knowledge base.
@@ -58,6 +59,7 @@ async def write_note(
5859
Use forward slashes (/) as separators. Examples: "notes", "projects/2025", "research/ml"
5960
tags: Tags to categorize the note. Can be a list of strings, a comma-separated string, or None.
6061
Note: If passing from external MCP clients, use a string format (e.g. "tag1,tag2,tag3")
62+
entity_type: Type of entity to create. Defaults to "note". Can be "guide", "report", "config", etc.
6163
project: Optional project name to write to. If not provided, uses current active project.
6264
6365
Returns:
@@ -84,7 +86,7 @@ async def write_note(
8486
entity = Entity(
8587
title=title,
8688
folder=folder,
87-
entity_type="note",
89+
entity_type=entity_type,
8890
content_type="text/markdown",
8991
content=content,
9092
entity_metadata=metadata,

src/basic_memory/services/entity_service.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,15 @@ async def create_entity(self, schema: EntitySchema) -> EntityModel:
117117
f"file for entity {schema.folder}/{schema.title} already exists: {file_path}"
118118
)
119119

120-
# Parse content frontmatter to check for user-specified permalink
120+
# Parse content frontmatter to check for user-specified permalink and entity_type
121121
content_markdown = None
122122
if schema.content and has_frontmatter(schema.content):
123123
content_frontmatter = parse_frontmatter(schema.content)
124+
125+
# If content has entity_type/type, use it to override the schema entity_type
126+
if "type" in content_frontmatter:
127+
schema.entity_type = content_frontmatter["type"]
128+
124129
if "permalink" in content_frontmatter:
125130
# Create a minimal EntityMarkdown object for permalink resolution
126131
from basic_memory.markdown.schemas import EntityFrontmatter
@@ -172,10 +177,15 @@ async def update_entity(self, entity: EntityModel, schema: EntitySchema) -> Enti
172177
# Read existing frontmatter from the file if it exists
173178
existing_markdown = await self.entity_parser.parse_file(file_path)
174179

175-
# Parse content frontmatter to check for user-specified permalink
180+
# Parse content frontmatter to check for user-specified permalink and entity_type
176181
content_markdown = None
177182
if schema.content and has_frontmatter(schema.content):
178183
content_frontmatter = parse_frontmatter(schema.content)
184+
185+
# If content has entity_type/type, use it to override the schema entity_type
186+
if "type" in content_frontmatter:
187+
schema.entity_type = content_frontmatter["type"]
188+
179189
if "permalink" in content_frontmatter:
180190
# Create a minimal EntityMarkdown object for permalink resolution
181191
from basic_memory.markdown.schemas import EntityFrontmatter

tests/mcp/test_tool_write_note.py

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ async def test_write_note_no_tags(app):
6363
content = await read_note.fn("test/simple-note")
6464
assert (
6565
dedent("""
66-
--
66+
---
6767
title: Simple Note
6868
type: note
6969
permalink: test/simple-note
@@ -476,3 +476,185 @@ async def test_write_note_permalink_collision_fix_issue_139(app):
476476
# Original note should still exist
477477
original_content = await read_note.fn("test/note-1")
478478
assert "Original content for note 1" in original_content
479+
480+
481+
@pytest.mark.asyncio
482+
async def test_write_note_with_custom_entity_type(app):
483+
"""Test creating a note with custom entity_type parameter.
484+
485+
This test verifies the fix for Issue #144 where entity_type parameter
486+
was hardcoded to "note" instead of allowing custom types.
487+
"""
488+
result = await write_note.fn(
489+
title="Test Guide",
490+
folder="guides",
491+
content="# Guide Content\nThis is a guide",
492+
tags=["guide", "documentation"],
493+
entity_type="guide",
494+
)
495+
496+
assert result
497+
assert "# Created note" in result
498+
assert "file_path: guides/Test Guide.md" in result
499+
assert "permalink: guides/test-guide" in result
500+
assert "## Tags" in result
501+
assert "- guide, documentation" in result
502+
503+
# Verify the entity type is correctly set in the frontmatter
504+
content = await read_note.fn("guides/test-guide")
505+
assert (
506+
dedent("""
507+
---
508+
title: Test Guide
509+
type: guide
510+
permalink: guides/test-guide
511+
tags:
512+
- guide
513+
- documentation
514+
---
515+
516+
# Guide Content
517+
This is a guide
518+
""").strip()
519+
in content
520+
)
521+
522+
523+
@pytest.mark.asyncio
524+
async def test_write_note_with_report_entity_type(app):
525+
"""Test creating a note with entity_type="report"."""
526+
result = await write_note.fn(
527+
title="Monthly Report",
528+
folder="reports",
529+
content="# Monthly Report\nThis is a monthly report",
530+
tags=["report", "monthly"],
531+
entity_type="report",
532+
)
533+
534+
assert result
535+
assert "# Created note" in result
536+
assert "file_path: reports/Monthly Report.md" in result
537+
assert "permalink: reports/monthly-report" in result
538+
539+
# Verify the entity type is correctly set in the frontmatter
540+
content = await read_note.fn("reports/monthly-report")
541+
assert "type: report" in content
542+
assert "# Monthly Report" in content
543+
544+
545+
@pytest.mark.asyncio
546+
async def test_write_note_with_config_entity_type(app):
547+
"""Test creating a note with entity_type="config"."""
548+
result = await write_note.fn(
549+
title="System Config",
550+
folder="config",
551+
content="# System Configuration\nThis is a config file",
552+
entity_type="config",
553+
)
554+
555+
assert result
556+
assert "# Created note" in result
557+
assert "file_path: config/System Config.md" in result
558+
assert "permalink: config/system-config" in result
559+
560+
# Verify the entity type is correctly set in the frontmatter
561+
content = await read_note.fn("config/system-config")
562+
assert "type: config" in content
563+
assert "# System Configuration" in content
564+
565+
566+
@pytest.mark.asyncio
567+
async def test_write_note_entity_type_default_behavior(app):
568+
"""Test that the entity_type parameter defaults to "note" when not specified.
569+
570+
This ensures backward compatibility - existing code that doesn't specify
571+
entity_type should continue to work as before.
572+
"""
573+
result = await write_note.fn(
574+
title="Default Type Test",
575+
folder="test",
576+
content="# Default Type Test\nThis should be type 'note'",
577+
tags=["test"],
578+
)
579+
580+
assert result
581+
assert "# Created note" in result
582+
assert "file_path: test/Default Type Test.md" in result
583+
assert "permalink: test/default-type-test" in result
584+
585+
# Verify the entity type defaults to "note"
586+
content = await read_note.fn("test/default-type-test")
587+
assert "type: note" in content
588+
assert "# Default Type Test" in content
589+
590+
591+
@pytest.mark.asyncio
592+
async def test_write_note_update_existing_with_different_entity_type(app):
593+
"""Test updating an existing note with a different entity_type."""
594+
# Create initial note as "note" type
595+
result1 = await write_note.fn(
596+
title="Changeable Type",
597+
folder="test",
598+
content="# Initial Content\nThis starts as a note",
599+
tags=["test"],
600+
entity_type="note",
601+
)
602+
603+
assert result1
604+
assert "# Created note" in result1
605+
606+
# Update the same note with a different entity_type
607+
result2 = await write_note.fn(
608+
title="Changeable Type",
609+
folder="test",
610+
content="# Updated Content\nThis is now a guide",
611+
tags=["guide"],
612+
entity_type="guide",
613+
)
614+
615+
assert result2
616+
assert "# Updated note" in result2
617+
618+
# Verify the entity type was updated
619+
content = await read_note.fn("test/changeable-type")
620+
assert "type: guide" in content
621+
assert "# Updated Content" in content
622+
assert "- guide" in content
623+
624+
625+
@pytest.mark.asyncio
626+
async def test_write_note_respects_frontmatter_entity_type(app):
627+
"""Test that entity_type in frontmatter is respected when parameter is not provided.
628+
629+
This verifies that when write_note is called without entity_type parameter,
630+
but the content includes frontmatter with a 'type' field, that type is respected
631+
instead of defaulting to 'note'.
632+
"""
633+
note = dedent("""
634+
---
635+
title: Test Guide
636+
type: guide
637+
permalink: guides/test-guide
638+
tags:
639+
- guide
640+
- documentation
641+
---
642+
643+
# Guide Content
644+
This is a guide
645+
""").strip()
646+
647+
# Call write_note without entity_type parameter - it should respect frontmatter type
648+
result = await write_note.fn(title="Test Guide", folder="guides", content=note)
649+
650+
assert result
651+
assert "# Created note" in result
652+
assert "file_path: guides/Test Guide.md" in result
653+
assert "permalink: guides/test-guide" in result
654+
655+
# Verify the entity type from frontmatter is respected (should be "guide", not "note")
656+
content = await read_note.fn("guides/test-guide")
657+
assert "type: guide" in content
658+
assert "# Guide Content" in content
659+
assert "- guide" in content
660+
assert "- documentation" in content

0 commit comments

Comments
 (0)