mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 951d5d1ac4 |
@@ -135,7 +135,7 @@ async def edit_note(
|
||||
workspace: Optional[str] = None,
|
||||
section: Optional[str] = None,
|
||||
find_text: Optional[str] = None,
|
||||
expected_replacements: int = 1,
|
||||
expected_replacements: Optional[int] = None,
|
||||
output_format: Literal["text", "json"] = "text",
|
||||
context: Context | None = None,
|
||||
) -> str | dict:
|
||||
@@ -219,6 +219,10 @@ async def edit_note(
|
||||
async with get_project_client(project, workspace, context) as (client, active_project):
|
||||
logger.info("MCP tool call", tool="edit_note", identifier=identifier, operation=operation)
|
||||
|
||||
# Null optional fields are equivalent to absent — treat None as the default.
|
||||
# This handles MCP clients that send explicit null values for unused optional fields.
|
||||
effective_replacements = expected_replacements if expected_replacements is not None else 1
|
||||
|
||||
# Validate operation
|
||||
valid_operations = ["append", "prepend", "find_replace", "replace_section"]
|
||||
if operation not in valid_operations:
|
||||
@@ -254,8 +258,8 @@ async def edit_note(
|
||||
edit_data["section"] = section
|
||||
if find_text:
|
||||
edit_data["find_text"] = find_text
|
||||
if expected_replacements != 1: # Only send if different from default
|
||||
edit_data["expected_replacements"] = str(expected_replacements)
|
||||
if effective_replacements != 1: # Only send if different from default
|
||||
edit_data["expected_replacements"] = str(effective_replacements)
|
||||
|
||||
# Call the PATCH endpoint
|
||||
result = await knowledge_client.patch_entity(entity_id, edit_data, fast=False)
|
||||
@@ -339,5 +343,5 @@ async def edit_note(
|
||||
"error": str(e),
|
||||
}
|
||||
return _format_error_response(
|
||||
str(e), operation, identifier, find_text, expected_replacements, active_project.name
|
||||
str(e), operation, identifier, find_text, effective_replacements, active_project.name
|
||||
)
|
||||
|
||||
@@ -456,3 +456,40 @@ async def test_edit_note_preserves_permalink_when_frontmatter_missing(client, te
|
||||
assert f"permalink: {test_project.name}/test/test-note" in second_result
|
||||
assert f"[Session: Using project '{test_project.name}']" in second_result
|
||||
# The edit should succeed without validation errors
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_note_append_with_null_optional_fields(client, test_project):
|
||||
"""Regression test for issue #606: null optional fields should be silently ignored.
|
||||
|
||||
MCP clients may send explicit null values for unused optional fields (e.g.
|
||||
find_text=null, section=null, expected_replacements=null). Before this fix,
|
||||
expected_replacements being typed as `int` caused FastMCP schema validation to
|
||||
reject null, resulting in an "invalid payload" error even for simple append/prepend
|
||||
operations that don't use those fields.
|
||||
"""
|
||||
# Create initial note
|
||||
await write_note(
|
||||
project=test_project.name,
|
||||
title="Null Fields Test",
|
||||
directory="test",
|
||||
content="# Null Fields Test\nOriginal content.",
|
||||
)
|
||||
|
||||
# Simulate an MCP client that sends explicit null for all optional fields.
|
||||
# expected_replacements=None (null), find_text=None, section=None should all be
|
||||
# treated as absent rather than rejected.
|
||||
result = await edit_note(
|
||||
project=test_project.name,
|
||||
identifier="test/null-fields-test",
|
||||
operation="append",
|
||||
content="\n## Appended Section\nContent added via null-field call.",
|
||||
find_text=None,
|
||||
section=None,
|
||||
expected_replacements=None,
|
||||
)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert "Edited note (append)" in result
|
||||
assert f"project: {test_project.name}" in result
|
||||
assert "file_path: test/Null Fields Test.md" in result
|
||||
|
||||
Reference in New Issue
Block a user