MCP-сервер для управления памятью агента: memory_server.py

This commit is contained in:
2026-05-27 11:31:28 +00:00
parent e71047aa97
commit 42afba3130
@@ -1,183 +1,36 @@
# memory_server.py # memory_client.py
from fastmcp import FastMCP from fastmcp import Client
import json import asyncio
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional, List, Dict
import fnmatch
class MemoryServer: async def main():
""" # Подключаемся к серверу через stdio (запускаем его как subprocess)
MCP-сервер для управления памятью. Хранит данные в JSON‑файле, client = Client("python memory_server.py")
поддерживает namespaces и wildcard‑поиск ключей.
"""
def __init__(self): await client.connect()
self.mcp = FastMCP("Memory-Server")
self.storage_path = Path("./memory_data.json")
# ------------------------------------------------------------------ try:
# Вспомогательные методы работы с файлом # Сохраняем данные в namespace "default"
# ------------------------------------------------------------------ result = await client.call_tool(
def _load_memory(self) -> Dict[str, Any]: "save_with_namespace",
"""Загружает память из JSON‑файла.""" {"key": "username", "value": "Алексей", "namespace": "default"},
if not self.storage_path.exists(): )
return {} print(f"Сохранено: {result}")
with open(self.storage_path, "r", encoding="utf-8") as f:
try:
data = json.load(f)
# Приводим к dict[str, dict]
return {k: v for k, v in data.items()}
except json.JSONDecodeError:
return {}
def _save_memory(self, data: Dict[str, Any]) -> None: # Читаем все ключи из namespace "default"
"""Сохраняет память в JSON‑файл.""" result = await client.call_tool("get_by_namespace", {"namespace": "default"})
self.storage_path.parent.mkdir(parents=True, exist_ok=True) print("Данные namespace 'default':")
with open(self.storage_path, "w", encoding="utf-8") as f: for item in result:
json.dump(data, f, indent=2, ensure_ascii=False) print(f" {item['key']}: {item['value']}")
# ------------------------------------------------------------------ # Поиск по паттерну
# Инструменты без namespace keys = await client.call_tool("list_keys", {"pattern": "*name"})
# ------------------------------------------------------------------ print(f"Ключи с 'name': {keys}")
@self.mcp.tool()
def save(self, key: str, value: Any) -> bool:
"""
Сохраняет значение по ключу в память сервера.
Args: finally:
key: Идентификатор для сохранения (уникальный ключ). await client.close()
value: Любое сериализуемое значение.
Returns:
True при успешном сохранении, False иначе.
"""
data = self._load_memory()
timestamp = datetime.now(timezone.utc).isoformat()
data[key] = {"value": value, "timestamp": timestamp}
try:
self._save_memory(data)
return True
except Exception:
return False
@self.mcp.tool()
def get(self, key: str) -> Optional[Dict[str, Any]]:
"""
Возвращает значение по ключу с метаданными.
Args:
key: Идентификатор для поиска.
Returns:
Словарь с полями {"key": ..., "value": ..., "timestamp": ...}
или None если ключ не найден.
"""
data = self._load_memory()
entry = data.get(key)
if entry is None:
return None
return {
"key": key,
"value": entry["value"],
"timestamp": entry["timestamp"],
}
@self.mcp.tool()
def delete(self, key: str) -> bool:
"""
Удаляет ключ из памяти сервера.
Args:
key: Идентификатор для удаления.
Returns:
True при успешном удалении, False если ключ не найден.
"""
data = self._load_memory()
if key not in data:
return False
del data[key]
try:
self._save_memory(data)
return True
except Exception:
return False
@self.mcp.tool()
def list_keys(self, pattern: str = "*") -> List[str]:
"""
Возвращает список всех ключей с поддержкой wildcard‑паттерна.
Args:
pattern: Паттерн для фильтрации (поддерживает * и ?).
Returns:
Список совпадающих ключей.
"""
data = self._load_memory()
return [k for k in data.keys() if fnmatch.fnmatch(k, pattern)]
# ------------------------------------------------------------------
# Инструменты с namespace
# ------------------------------------------------------------------
@self.mcp.tool()
def save_with_namespace(
self,
key: str,
value: Any,
namespace: str = "default",
) -> bool:
"""
Сохраняет значение с указанием пространства имён.
Args:
key: Идентификатор.
value: Значение для сохранения.
namespace: Пространство имён (по умолчанию 'default').
Returns:
True при успехе, False иначе.
"""
full_key = f"{namespace}:{key}"
return self.save(full_key, value)
@self.mcp.tool()
def get_by_namespace(self, namespace: str = "default") -> List[Dict[str, Any]]:
"""
Возвращает все ключи из указанного namespace.
Args:
namespace: Пространство имён для чтения.
Returns:
Список словарей с метаданными всех ключей namespace.
"""
prefix = f"{namespace}:"
data = self._load_memory()
result = []
for k, v in data.items():
if k.startswith(prefix):
key_part = k[len(prefix) :]
result.append(
{
"key": key_part,
"value": v["value"],
"timestamp": v["timestamp"],
}
)
return result
# ----------------------------------------------------------------------
# Запуск сервера
# ----------------------------------------------------------------------
if __name__ == "__main__": if __name__ == "__main__":
server = MemoryServer() asyncio.run(main())
server.mcp.run(
transport="stdio",
show_banner=False,
log_level="ERROR",
)