From eb5e9d9f37bda7d78f69614da70402357794168a Mon Sep 17 00:00:00 2001 From: tanaka takashi Date: Sat, 4 Jul 2026 16:22:35 +0900 Subject: feat(P1-002): add offline JSONL import via import_messages MCP tool - implement `import_messages` MCP tool parsing manifest + PUT/DEL JSONL - atomic store replacement under lock with overwrite/reject conflict policies - add `include_deleted` to read_messages export view for full round-trip - register new tool in MCP registry and update protocol test expectations - update docs/development-plan.md and docs/implementation-status.md Debug/Release/ASan 15/15 CTest pass --- tests/mcp_protocol_test.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'tests') diff --git a/tests/mcp_protocol_test.py b/tests/mcp_protocol_test.py index 2d239fb..ec4f350 100644 --- a/tests/mcp_protocol_test.py +++ b/tests/mcp_protocol_test.py @@ -134,6 +134,7 @@ def main() -> None: "delete_message", "rebuild_index", "get_system_health", + "import_messages", } expected_descriptions = { "write_message": "Append and durably persist a conversation message. Use it to store information that should remain available after server restarts.", @@ -143,6 +144,7 @@ def main() -> None: "delete_message": "Delete a persisted message so it is immediately excluded from reads and searches. Physical removal occurs after rebuild_index compacts the store.", "rebuild_index": "Compact the store and rebuild its snapshot and in-memory indexes. Use it for maintenance or to physically remove logically deleted records; it may block other requests while running.", "get_system_health": "Inspect storage health, recovery state, message counts, and file sizes. Use it to diagnose persistence or recovery problems.", + "import_messages": "Atomically import a JSONL export from read_messages view=export. The manifest line is required; subsequent lines are PUT messages or DEL tombstones. Conflicting message IDs are resolved by the policy argument.", } assert {name: tool["description"] for name, tool in tools.items()} == expected_descriptions expected_property_descriptions = { @@ -162,6 +164,7 @@ def main() -> None: "relation_types_json": "Optional JSON array string of relation types to follow.", "limit": "Maximum messages to return in this page, from 1 to 1000; defaults to 100.", "cursor": "Opaque nextCursor returned by a previous read_messages call; omit for the first page.", + "include_deleted": "When true, export view includes deleted records as DEL lines; other views ignore this option.", }, "search_messages": { "query": "UTF-8 text to match against persisted message content and tags.", @@ -181,6 +184,10 @@ def main() -> None: "metadata_json": "Replacement metadata JSON; omit to retain metadata.", "expected_revision": "Optional positive revision for optimistic concurrency.", }, + "import_messages": { + "jsonl": "JSONL string containing a manifest line followed by PUT message lines and optional DEL tombstone lines.", + "policy": "Conflict policy: overwrite (default) replaces existing message IDs, reject refuses duplicate IDs.", + }, } for tool_name, descriptions in expected_property_descriptions.items(): properties = tools[tool_name]["inputSchema"]["properties"] -- cgit v1.2.3