Files
2026-05-18 11:06:49 +00:00

26 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"task": {
"_id": "69f8e929da860fb4533faa2a",
"title": "MCP-сервер для управления памятью агента",
"description": "# Практическое задание: MCP-сервер для управления памятью агента\n\n## Цель\n\nСоздать **MCP-сервер (Model Context Protocol)** — самостоятельный сервис, который предоставляет другим агентам инструменты для работы с памятью через стандартизированный протокол. Это первый шаг к построению распределённых многоагентных систем, где агенты могут обмениваться данными и координировать работу через единое хранилище.\n\nВ отличие от локальной памяти внутри одного скрипта, MCP-сервер — это независимый процесс с API, доступный для любого клиента (другого агента, CLI-утилиты или другого сервиса).\n\n---\n\n## Стек и установка\n\n- Python 3.10+\n- Пакеты: `fastmcp` (фреймворк MCP), `pydantic` (валидация данных)\n\nУстановка:\n\n```bash\npip install fastmcp pydantic python-dotenv\n```\n\n> **Важно:** `fastmcp` — это библиотека для создания серверов и клиентов MCP. Она позволяет регистрировать инструменты, которые становятся доступны агентам через протокол stdio или SSE.\n\n---\n\n## Что нужно сделать\n\n### 1. Создать MCP-сервер с базовыми инструментами памяти\n\nСоздай файл `memory_server.py`, в котором определишь класс сервера на основе `FastMCP`:\n\n```python\nfrom fastmcp import FastMCP\nimport json\nfrom datetime import datetime\nfrom pathlib import Path\nfrom typing import Any, Optional\n\nclass MemoryServer:\n def __init__(self):\n self.mcp = FastMCP(\"Memory-Server\")\n self.storage_path = Path(\"./memory_data.json\")\n \n def _load_memory(self) -> dict:\n \"\"\"Загружает память из JSON-файла.\"\"\"\n if not self.storage_path.exists():\n return {}\n with open(self.storage_path, 'r', encoding='utf-8') as f:\n return json.load(f)\n \n def _save_memory(self, data: dict):\n \"\"\"Сохраняет память в JSON-файл.\"\"\"\n self.storage_path.parent.mkdir(parents=True, exist_ok=True)\n with open(self.storage_path, 'w', encoding='utf-8') as f:\n json.dump(data, f, indent=2, ensure_ascii=False)\n \n @self.mcp.tool()\n def save(key: str, value: Any) -> bool:\n \"\"\"Сохраняет значение по ключу в память сервера.\n \n Args:\n key: Идентификатор для сохранения (уникальный ключ).\n value: Любое сериализуемое значение.\n \n Returns:\n True при успешном сохранении, False иначе.\n \"\"\"\n # Реализуй здесь...\n \n @self.mcp.tool()\n def get(key: str) -> Optional[dict]:\n \"\"\"Возвращает значение по ключу с метаданными.\n \n Args:\n key: Идентификатор для поиска.\n \n Returns:\n Словарь с полями {\"key\": ..., \"value\": ..., \"timestamp\": ...} \n или None если ключ не найден.\n \"\"\"\n # Реализуй здесь...\n \n @self.mcp.tool()\n def delete(key: str) -> bool:\n \"\"\"Удаляет ключ из памяти сервера.\n \n Args:\n key: Идентификатор для удаления.\n \n Returns:\n True при успешном удалении, False если ключ не найден.\n \"\"\"\n # Реализуй здесь...\n \n @self.mcp.tool()\n def list_keys(pattern: str = \"*\") -> list[str]:\n \"\"\"Возвращает список всех ключей с поддержкой wildcard-паттерна.\n \n Args:\n pattern: Паттерн для фильтрации (поддерживает * и ?).\n \n Returns:\n Список совпадающих ключей.\n \"\"\"\n # Реализуй здесь...\n```\n\n### 2. Добавить инструменты с поддержкой namespaces\n\n**Namespace** — это пространство имён для изоляции данных. Например, один агент может работать в namespace `agent_1`, другой — в `agent_2`. Данные не пересекаются.\n\nДобавь следующие инструменты:\n\n```python\n @self.mcp.tool()\n def save_with_namespace(key: str, value: Any, namespace: str = \"default\") -> bool:\n \"\"\"Сохраняет значение с указанием пространства имён.\n \n Args:\n key: Идентификатор.\n value: Значение для сохранения.\n namespace: Пространство имён (по умолчанию 'default').\n \n Returns:\n True при успехе, False иначе.\n \"\"\"\n # Ключ в хранилище должен формироваться как: f\"{namespace}:{key}\"\n \n @self.mcp.tool()\n def get_by_namespace(namespace: str = \"default\") -> list[dict]:\n \"\"\"Возвращает все ключи из указанного namespace.\n \n Args:\n namespace: Пространство имён для чтения.\n \n Returns:\n Список словарей с метаданными всех ключей namespace.\n \"\"\"\n # Ищи ключи, начинающиеся на f\"{namespace}:\"\n```\n\n### 3. Реализовать сохранение данных\n\nСоздай файловое хранилище в формате JSON. Структура файла:\n\n```json\n{\n \"default:user_name\": {\n \"value\": \"Алексей\",\n \"timestamp\": \"2026-05-15T10:30:00\"\n },\n \"agent_1:session_token\": {\n \"value\": \"abc123xyz\", \n \"timestamp\": \"2026-05-15T10:31:00\"\n }\n}\n```\n\nКаждое значение должно храниться с метаданными (timestamp). Ключи формируются как `namespace:key` для namespace-based инструментов.\n\n### 4. Написать клиент для тестирования\n\nСоздай файл `memory_client.py`, который демонстрирует работу с сервером через MCP-клиент:\n\n```python\nfrom fastmcp import Client\nimport asyncio\n\nasync def main():\n # Подключение к MCP-серверу через stdio\n client = Client(\"python memory_server.py\")\n \n await client.connect()\n \n try:\n # Сохранение данных в namespace \"default\"\n result = await client.call_tool(\n \"save_with_namespace\", \n {\"key\": \"username\", \"value\": \"Алексей\", \"namespace\": \"default\"}\n )\n print(f\"Сохранено: {result}\")\n \n # Чтение данных\n result = await client.call_tool(\n \"get_by_namespace\", \n {\"namespace\": \"default\"}\n )\n print(f\"Данные namespace 'default':\")\n for item in result:\n print(f\" {item['key']}: {item['value']}\")\n \n # Поиск по паттерну\n keys = await client.call_tool(\"list_keys\", {\"pattern\": \"*name\"})\n print(f\"Ключи с 'name': {keys}\")\n \n finally:\n await client.close()\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n```\n\n### 5. Добавить запуск сервера\n\nВ конце файла `memory_server.py` добавь блок запуска:\n\n```python\nif __name__ == \"__main__\":\n server = MemoryServer()\n server.mcp.run(\n transport=\"stdio\",\n show_banner=False,\n log_level='ERROR'\n )\n```\n\n---\n\n## Подсказки\n\n- **Импорты:** понадобятся `FastMCP`, `Client` из `fastmcp`; `json`, `datetime` стандартные библиотеки; `Path` из `pathlib`.\n- **Wildcard паттерн:** используй `fnmatch.fnmatch(key, pattern)` для фильтрации ключей.\n- **Структура данных:** каждый ключ в JSON должен хранить значение + timestamp. Namespace добавляется к ключу как префикс: `namespace:key`.\n- **Асинхронность:** клиент работает асинхронно (`async def`, `await`). Сервер запускается синхронно через `mcp.run()`.\n- **Безопасность:** валидируй ввод — ключи не должны содержать опасных символов (например, пути `/../`).\n\n---\n\n## Ожидаемый результат\n\nПри запуске клиента в консоли должен появиться вывод:\n\n```text\nСохранено: True\nДанные namespace 'default':\n username: Алексей\nКлючи с 'name': ['default:user_name']\n```\n\nСервер работает как отдельный процесс, клиент подключается к нему через stdio и вызывает инструменты удалённо.\n\n---\n\n## Критерии оценки\n\n| Критерий | Баллы |\n|----------|-------|\n| Сервер корректно зарегистрирован с FastMCP и минимум 6 инструментов (save, get, delete, list_keys, save_with_namespace, get_by_namespace) | 2 |\n| Инструменты правильно оформлены через декоратор @mcp.tool() с docstring и типизацией аргументов | 2 |\n| Используется транспорт stdio в mcp.run(), сервер запускается корректно | 1 |\n| Данные сохраняются в JSON-файл с метаданными (timestamp) | 2 |\n| Поддержка namespace для изоляции данных (ключи формируются как namespace:key) | 2 |\n| Реализован клиент для тестирования с примером вызова инструментов | 1 |\n| **Итого** | **10** |\n\n---\n\n## Ссылки\n\n- [FastMCP documentation](https://github.com/jlowin/fastmcp) — создание MCP-серверов и клиентов\n- [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) — спецификация протокола\n- [LangChain AI - MCP Integration](https://docs.langchain.com/oss/python/langchain/mcp) — интеграция MCP с LangChain",
"course": "698b49da77cb6d4d2e43ce78",
"lesson": {
"_id": "69b035ab2a32e307dd6e1fee",
"name": "Implementing Model Context Protocol in LangChain",
"date": "2026-03-10T15:30:00.000Z",
"id": "69b035ab2a32e307dd6e1fee"
},
"gradeType": "passfail",
"maxScore": 100,
"assignmentType": "individual",
"allowSubmissions": true,
"allowRevisions": false,
"creator": {
"sub": "8ae348ab-f32e-46c7-9960-8b7fdf0e4c0e",
"name": "Александр Примаков"
},
"teamGrades": [],
"created": "2026-05-04T18:44:57.314Z",
"id": "69f8e929da860fb4533faa2a"
}
}