feat: 导入 MemRelay 初始源码
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user