diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-07-04 14:54:45 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-07-04 14:54:45 +0900 |
| commit | 1efecaef1f867cb218366e2b4ab4b901fdd2835f (patch) | |
| tree | 3080a3d80a1a0671e8e82819d3f785465aace162 /tests/mcp_protocol_test.py | |
| parent | 34e00404e9561dcaf8d2d99df7470757a4fee915 (diff) | |
途中経過コミット
Diffstat (limited to 'tests/mcp_protocol_test.py')
| -rw-r--r-- | tests/mcp_protocol_test.py | 172 |
1 files changed, 167 insertions, 5 deletions
diff --git a/tests/mcp_protocol_test.py b/tests/mcp_protocol_test.py index 2dc70ff..1873c52 100644 --- a/tests/mcp_protocol_test.py +++ b/tests/mcp_protocol_test.py @@ -85,10 +85,16 @@ def tool_payload(response: Mapping[str, Any], expect_error: bool = False) -> dic def assert_stable_recency_order(messages: list[dict[str, Any]]) -> None: for left, right in zip(messages, messages[1:]): - left_time = int(left["timestamp"]) - right_time = int(right["timestamp"]) + left_time = int(left.get("updated_at", left["timestamp"])) + right_time = int(right.get("updated_at", right["timestamp"])) + left_created = int(left["timestamp"]) + right_created = int(right["timestamp"]) assert left_time > right_time or ( - left_time == right_time and left["message_id"] <= right["message_id"] + left_time == right_time + and ( + left_created > right_created + or (left_created == right_created and left["message_id"] <= right["message_id"]) + ) ), (left, right) @@ -124,6 +130,7 @@ def main() -> None: "write_message", "read_messages", "search_messages", + "update_message", "delete_message", "rebuild_index", "get_system_health", @@ -132,6 +139,7 @@ def main() -> None: "write_message": "Append and durably persist a conversation message. Use it to store information that should remain available after server restarts.", "read_messages": "Read persisted messages from one known session in chronological order. Returns a paginated history for reconstructing that conversation.", "search_messages": "Search persisted message content and tags by text relevance. Use it to recall matching information globally or within one session.", + "update_message": "Update message content or metadata while preserving identity, session, role, and creation time.", "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.", @@ -142,22 +150,36 @@ def main() -> None: "session_id": "Conversation session that owns the message.", "role": "Message author role: user, assistant, or system.", "content": "UTF-8 message text to persist.", - "metadata_json": "Optional JSON object string containing tool_call_id, tags, and/or memory_tier; defaults to {}.", + "metadata_json": "Optional JSON object string containing tool_call_id, tags, memory_tier, memory_kind, importance, summary, relations, and/or source_message_ids; defaults to {}.", }, "read_messages": { + "view": "Read view: history (default), context, sessions, or export.", "session_id": "Conversation session whose chronological history to read.", + "message_id": "Message ULID required by the context view.", + "before": "Context messages before the target, from 0 to 10.", + "after": "Context messages after the target, from 0 to 10.", "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.", }, "search_messages": { "query": "UTF-8 text to match against persisted message content and tags.", "session_id": "Conversation session to search; omit to search across all sessions.", + "memory_kind": "Optional exact memory kind filter.", + "memory_tier": "Optional exact memory tier filter.", + "tag": "Optional exact tag filter.", + "min_importance": "Minimum importance from 0 to 100; defaults to 0.", "limit": "Maximum ranked matches to return, from 1 to 100; defaults to 20.", }, "delete_message": { "message_id": "Canonical 26-character ULID of the message to delete.", "session_id": "Optional session guard; when provided, delete only if the message belongs to this session.", }, + "update_message": { + "message_id": "ULID of the live message to update.", + "content": "Replacement UTF-8 content; omit to retain content.", + "metadata_json": "Replacement metadata JSON; omit to retain metadata.", + "expected_revision": "Optional positive revision for optimistic concurrency.", + }, } for tool_name, descriptions in expected_property_descriptions.items(): properties = tools[tool_name]["inputSchema"]["properties"] @@ -167,12 +189,13 @@ def main() -> None: "role", "content", ] - assert tools["read_messages"]["inputSchema"]["required"] == ["session_id"] + assert tools["read_messages"]["inputSchema"].get("required", []) == [] assert tools["read_messages"]["inputSchema"]["properties"]["limit"]["type"] == "integer" assert tools["read_messages"]["inputSchema"]["properties"]["cursor"]["type"] == "string" assert tools["search_messages"]["inputSchema"]["required"] == ["query"] assert tools["search_messages"]["inputSchema"]["properties"]["limit"]["type"] == "integer" assert tools["delete_message"]["inputSchema"]["required"] == ["message_id"] + assert tools["update_message"]["inputSchema"]["required"] == ["message_id"] message_ids: list[str] = [] for index in range(20): @@ -186,6 +209,145 @@ def main() -> None: ) next_id += 1 message_ids.append(payload["message"]["message_id"]) + updated = tool_payload( + tool_call( + server, + next_id, + "update_message", + { + "message_id": message_ids[0], + "content": "needle target revised", + "expected_revision": 1, + }, + ) + ) + next_id += 1 + assert updated["changed"] is True + assert updated["message"]["revision"] == 2 + assert updated["message"]["content"] == "needle target revised" + unchanged = tool_payload( + tool_call( + server, + next_id, + "update_message", + { + "message_id": message_ids[0], + "content": "needle target revised", + "expected_revision": 2, + }, + ) + ) + next_id += 1 + assert unchanged["changed"] is False + assert unchanged["message"]["revision"] == 2 + io_before = tool_payload(tool_call(server, next_id, "get_system_health", {}))["io"] + next_id += 1 + context = tool_payload( + tool_call( + server, + next_id, + "read_messages", + {"view": "context", "message_id": message_ids[1], "before": 1, "after": 1}, + ) + ) + next_id += 1 + assert context["message"]["message_id"] == message_ids[1] + assert [item["message_id"] for item in context["chronological_context"]["before"]] == [ + message_ids[0] + ] + assert [item["message_id"] for item in context["chronological_context"]["after"]] == [ + message_ids[2] + ] + io_after = tool_payload(tool_call(server, next_id, "get_system_health", {}))["io"] + next_id += 1 + assert io_after == io_before + sessions_view = tool_payload( + tool_call(server, next_id, "read_messages", {"view": "sessions", "limit": 10}) + ) + next_id += 1 + assert any(item["session_id"] == "target" for item in sessions_view["sessions"]) + exported = tool_payload( + tool_call( + server, + next_id, + "read_messages", + {"view": "export", "session_id": "target", "limit": 100}, + ) + ) + next_id += 1 + export_lines = exported["jsonl"].splitlines() + assert json.loads(export_lines[0])["format_version"] == 2 + assert len(export_lines) == 21 + assert all(json.loads(line)["session_id"] == "target" for line in export_lines[1:]) + io_after_views = tool_payload(tool_call(server, next_id, "get_system_health", {}))["io"] + next_id += 1 + assert io_after_views == io_before + relation_message = tool_payload( + tool_call( + server, + next_id, + "write_message", + { + "session_id": "relations", + "role": "assistant", + "content": "summary of the revised record", + "metadata_json": json.dumps( + { + "memory_kind": "summary", + "importance": 90, + "summary": "record summary", + "relations": [ + {"type": "derived_from", "message_id": message_ids[0]} + ], + "source_message_ids": [message_ids[0]], + } + ), + }, + ) + )["message"] + next_id += 1 + relation_context = tool_payload( + tool_call( + server, + next_id, + "read_messages", + {"view": "context", "message_id": relation_message["message_id"]}, + ) + ) + next_id += 1 + assert relation_context["relations"][0]["type"] == "derived_from" + assert relation_context["relations"][0]["message"]["message_id"] == message_ids[0] + incoming_context = tool_payload( + tool_call( + server, + next_id, + "read_messages", + {"view": "context", "message_id": message_ids[0]}, + ) + ) + next_id += 1 + assert any( + relation["direction"] == "incoming" + and relation["message"]["message_id"] == relation_message["message_id"] + for relation in incoming_context["relations"] + ) + explained = tool_payload( + tool_call( + server, + next_id, + "search_messages", + { + "query": "record summary", + "memory_kind": "summary", + "min_importance": 80, + "limit": 5, + }, + ) + )["results"] + next_id += 1 + assert explained[0]["message_id"] == relation_message["message_id"] + assert "summary" in explained[0]["matched_fields"] + assert explained[0]["score_breakdown"]["summary"] > 0 for index in range(10): tool_payload( tool_call( |
