feat: 导入 MemRelay 初始源码

This commit is contained in:
Nixevol
2026-09-23 21:40:46 +08:00
commit 0ade3bb318
167 changed files with 53511 additions and 0 deletions
+199
View File
@@ -0,0 +1,199 @@
"""Validate the Basic Memory MCP contract used by MemRelay."""
from __future__ import annotations
import argparse
import asyncio
import json
from typing import Any
from fastmcp import Client
REQUIRED_TOOLS = {
"delete_note",
"list_directory",
"move_note",
"read_note",
"search_notes",
"write_note",
}
async def call(client: Client, name: str, arguments: dict[str, Any]) -> Any:
result = await client.call_tool(name, arguments)
if result.data is not None:
return result.data
return [getattr(item, "text", str(item)) for item in result.content]
async def delete_fixture(client: Client, identifier: str) -> None:
await call(
client,
"delete_note",
{"identifier": identifier, "output_format": "json"},
)
async def validate(endpoint: str, semantic: bool) -> dict[str, Any]:
async with Client(endpoint) as client:
tools = {tool.name for tool in await client.list_tools()}
missing = REQUIRED_TOOLS - tools
if missing:
raise RuntimeError(f"Basic Memory is missing required tools: {sorted(missing)}")
await delete_fixture(client, "Stage Zero Note")
for index in range(5):
await delete_fixture(client, f"Concurrent Note {index}")
await delete_fixture(client, "Credential Memory")
await delete_fixture(client, "Weather Memory")
written = await call(
client,
"write_note",
{
"title": "Stage Zero Note",
"content": (
"# Stage Zero Note\n\n"
"中文语义与 English context.\n\n"
"- [decision] Markdown remains authoritative."
),
"directory": "global",
"tags": ["stage-zero", "multilingual"],
"note_type": "decision",
"metadata": {
"stable_id": "stage-zero-001",
"scope": "global",
"revision": 1,
"status": "active",
},
"overwrite": True,
"output_format": "json",
},
)
read = await call(
client,
"read_note",
{
"identifier": "Stage Zero Note",
"output_format": "json",
"include_frontmatter": True,
},
)
search = await call(
client,
"search_notes",
{
"query": "Markdown authoritative",
"search_type": "text",
"output_format": "json",
},
)
concurrent = await asyncio.gather(
*(
call(
client,
"write_note",
{
"title": f"Concurrent Note {index}",
"content": f"Concurrent content {index}",
"directory": "projects/stage-zero",
"metadata": {"request_id": f"stage0-{index}"},
"overwrite": True,
"output_format": "json",
},
)
for index in range(5)
)
)
moved = await call(
client,
"move_note",
{
"identifier": "Stage Zero Note",
"destination_path": "archive/stage-zero-note.md",
"output_format": "json",
},
)
archived = await call(
client,
"read_note",
{
"identifier": "archive/stage-zero-note",
"output_format": "json",
"include_frontmatter": True,
},
)
semantic_result = None
if semantic:
await call(
client,
"write_note",
{
"title": "Credential Memory",
"content": "团队账号密码和访问令牌应统一保存在密码库中。",
"directory": "projects/stage-zero",
"overwrite": True,
"output_format": "json",
},
)
await call(
client,
"write_note",
{
"title": "Weather Memory",
"content": "今天的天气预报是晴天,午后温度较高。",
"directory": "projects/stage-zero",
"overwrite": True,
"output_format": "json",
},
)
semantic_result = await call(
client,
"search_notes",
{
"query": "Where should team passwords and access tokens be stored?",
"search_type": "vector",
"min_similarity": 0,
"output_format": "json",
},
)
titles = [item["title"] for item in semantic_result.get("results", [])]
if "Credential Memory" not in titles:
raise RuntimeError("Cross-language semantic search did not return the target note")
await delete_fixture(client, "Stage Zero Note")
for index in range(5):
await delete_fixture(client, f"Concurrent Note {index}")
await delete_fixture(client, "Credential Memory")
await delete_fixture(client, "Weather Memory")
return {
"available_tool_count": len(tools),
"write": written,
"read": read,
"search": search,
"concurrent_write_count": len(concurrent),
"move": moved,
"archive_read": archived,
"semantic_search": semantic_result,
}
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--endpoint", default="http://127.0.0.1:8000/mcp")
parser.add_argument("--semantic", action="store_true")
args = parser.parse_args()
print(
json.dumps(
asyncio.run(validate(args.endpoint, args.semantic)),
ensure_ascii=False,
indent=2,
)
)
if __name__ == "__main__":
main()