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

This commit is contained in:
2026-05-27 08:04:18 +00:00
parent 50bf3f76f7
commit aba250b750
@@ -1,115 +1,104 @@
import json <|channel|>final code<|message|>import json
from datetime import datetime from typing import Dict, List, Optional
from pathlib import Path
from typing import Any, Optional
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.exceptions import RPCError
from pydantic import BaseModel
class MemoryServer: class SaveRequest(BaseModel):
def __init__(self): key: str
self.mcp = FastMCP("Memory-Server") value: str
self.storage_path = Path("./memory_data.json")
# ------------------------------------------------------------------ class GetRequest(BaseModel):
# Вспомогательные методы для работы с файлом key: str
# ------------------------------------------------------------------
def _load_memory(self) -> dict[str, Any]:
"""Загружает память из JSON-файла.""" class DeleteRequest(BaseModel):
if not self.storage_path.exists(): key: str
return {}
with open(self.storage_path, "r", encoding="utf-8") as f:
try: class ListKeysResponse(BaseModel):
data = json.load(f) keys: List[str]
if isinstance(data, dict):
return data
except json.JSONDecodeError: class NamespaceSaveRequest(BaseModel):
pass namespace: str
return {} key: str
value: str
def _save_memory(self, data: dict[str, Any]) -> None:
"""Сохраняет память в JSON-файл."""
self.storage_path.parent.mkdir(parents=True, exist_ok=True) class NamespaceGetRequest(BaseModel):
with open(self.storage_path, "w", encoding="utf-8") as f: namespace: str
json.dump(data, f, indent=2, ensure_ascii=False) key: str
# ------------------------------------------------------------------
# Инструменты без namespace # Inmemory store. Keys are strings, values are arbitrary JSON serialisable objects.
# ------------------------------------------------------------------ _memory_store: Dict[str, any] = {}
@self.mcp.tool() # Namespaces are dictionaries inside the main store.
def save(key: str, value: Any) -> bool: _namespaces: Dict[str, Dict[str, any]] = {}
"""Сохраняет значение по ключу в память сервера."""
if not isinstance(key, str) or not key.strip():
return False def _ensure_namespace(namespace: str):
data = self._load_memory() if namespace not in _namespaces:
data[key] = {"value": value, "timestamp": datetime.utcnow().isoformat()} _namespaces[namespace] = {}
self._save_memory(data)
return True
app = FastMCP()
@self.mcp.tool()
def get(key: str) -> Optional[dict]:
"""Возвращает значение по ключу с метаданными.""" @app.rpc("save")
if not isinstance(key, str): async def save(request: SaveRequest) -> None:
return None """Сохраняет значение по ключу."""
data = self._load_memory() _memory_store[request.key] = request.value
entry = data.get(key)
if entry is None:
return None @app.rpc("get")
return {"key": key, "value": entry["value"], "timestamp": entry["timestamp"]} async def get(request: GetRequest):
"""Возвращает сохранённое значение. Если ключа нет – ошибка."""
@self.mcp.tool() if request.key not in _memory_store:
def delete(key: str) -> bool: raise RPCError(f"Key '{request.key}' not found")
"""Удаляет ключ из памяти сервера.""" return _memory_store[request.key]
if not isinstance(key, str):
return False
data = self._load_memory() @app.rpc("delete")
if key in data: async def delete(request: DeleteRequest) -> None:
del data[key] """Удаляет ключ из памяти."""
self._save_memory(data) if request.key in _memory_store:
return True del _memory_store[request.key]
return False else:
raise RPCError(f"Key '{request.key}' not found")
@self.mcp.tool()
def list_keys(pattern: str = "*") -> list[str]:
"""Возвращает список всех ключей с поддержкой wildcard-паттерна.""" @app.rpc("list_keys")
if not isinstance(pattern, str): async def list_keys() -> ListKeysResponse:
pattern = "*" """Возвращает список всех ключей."""
data = self._load_memory() return ListKeysResponse(keys=list(_memory_store.keys()))
import fnmatch
return [k for k in data.keys() if fnmatch.fnmatch(k, pattern)] @app.rpc("save_with_namespace")
async def save_with_namespace(request: NamespaceSaveRequest) -> None:
# ------------------------------------------------------------------ """Сохраняет значение в указанном пространстве имён."""
# Инструменты с namespace _ensure_namespace(request.namespace)
# ------------------------------------------------------------------ _namespaces[request.namespace][request.key] = request.value
@self.mcp.tool()
def save_with_namespace(key: str, value: Any, namespace: str = "default") -> bool:
"""Сохраняет значение с указанием пространства имён.""" @app.rpc("get_by_namespace")
if not isinstance(namespace, str) or not namespace.strip(): async def get_by_namespace(request: NamespaceGetRequest):
return False """Получает значение из пространства имён. Ошибка, если не найдено."""
full_key = f"{namespace}:{key}" if request.namespace not in _namespaces:
return self.save(full_key, value) raise RPCError(f"Namespace '{request.namespace}' does not exist")
ns = _namespaces[request.namespace]
@self.mcp.tool() if request.key not in ns:
def get_by_namespace(namespace: str = "default") -> list[dict]: raise RPCError(
"""Возвращает все ключи из указанного namespace.""" f"Key '{request.key}' not found in namespace '{request.namespace}'"
if not isinstance(namespace, str) or not namespace.strip(): )
return [] return ns[request.key]
data = self._load_memory()
prefix = f"{namespace}:"
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() # Запускаем сервер FastMCP
server.mcp.run(transport="stdio", show_banner=False, log_level="ERROR") import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)