18 KiB
MCP: описание работы, структуры и тулзов
Документ описывает MCP endpoint сервиса miem-employees по текущей реализации в app/mcp.py.
Где находится MCP
- FastAPI router:
app.mcp.router - Подключение к приложению:
app/main.py - HTTP endpoint:
POST /mcp - Локально при обычном запуске API:
http://localhost:8000/mcp - В Docker Compose через отдельный сервис
mcp:http://localhost:8001/mcp - Авторизация на уровне приложения: отсутствует. Заголовок
Authorizationне проверяется и не влияет на ответ.
Если доступ к MCP нужно ограничить, это должно делаться внешним контуром: bind на localhost, VPN, firewall, reverse proxy или отдельная сетевая политика.
Протокол
Endpoint принимает JSON-RPC 2.0 over HTTP.
Общий формат запроса:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
Общий формат успешного ответа:
{
"jsonrpc": "2.0",
"id": 1,
"result": {}
}
Общий формат ошибки:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32601,
"message": "Method not found"
}
}
Поддерживаемая версия MCP-протокола:
2024-11-05
Имя сервиса:
miem-employees
Версия сервера берется из app.version.BACKEND_VERSION.
Поддерживаемые JSON-RPC методы
initialize
Возвращает метаданные MCP-сервера и capabilities.
Запрос:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {}
}
Ответ:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"serverInfo": {
"name": "miem-employees",
"version": "0.5.0"
},
"capabilities": {
"tools": {}
}
}
}
tools/list
Возвращает список доступных tools с JSON Schema для аргументов.
Запрос:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
Ответ содержит массив result.tools.
tools/call
Вызывает один tool по имени.
Запрос:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_employees",
"arguments": {
"query": "Сергеев",
"limit": 20
}
}
}
Ответ tool всегда заворачивается в MCP content-массив:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\"items\":[]}"
}
]
}
}
Поле text содержит сериализованный JSON с ensure_ascii=false. Клиент должен распарсить это поле как JSON, если ему нужна структурированная нагрузка.
Ошибки
- Неизвестный JSON-RPC метод:
code = -32601,message = "Method not found". - Исключения при обработке tool:
code = -32000,messageсодержит текст исключения. - Если сущность не найдена внутри отдельных tools, HTTP и JSON-RPC ответ остаются успешными, а полезная нагрузка содержит
{"error": "not_found"}.
Источники данных
MCP читает данные из основной базы через SQLAlchemy session из app.db.get_db.
Основные таблицы и модели:
employees: текущая карточка сотрудника, статус, профиль,current_data, checksum.employee_publications: нормализованные публикации сотрудников с авторами, DOI, аннотацией, описанием, citation text и raw JSON из HSE Publications.crawl_runs: история запусков парсинга.crawl_run_employee_changes: детальные изменения сотрудников в рамках запуска.crawl_errors: ошибки парсинга в рамках запуска.dataset_versions: версии полного набора сотрудников.dataset_version_items: состав конкретной версии набора сотрудников.
Общая структура employee payload
Краткая карточка сотрудника:
{
"profile_key": "staff:avsergeev",
"profile_id": "avsergeev",
"full_name": "Сергеев Алексей Викторович",
"status": "active",
"canonical_url": "https://www.hse.ru/staff/avsergeev",
"last_seen_at": "2026-05-14T10:00:00+00:00",
"dismissed_at": null
}
В sync payload дополнительно отдается checksum.
Полная карточка дополнительно содержит:
{
"data": {
"contacts": {},
"sections": []
}
}
data соответствует распарсенному JSON профиля сотрудника. Внутри sections могут быть секции с публикациями, курсами, ВКР, таблицами, ссылками и произвольными текстовыми блоками.
Tools
get_service_info
Назначение: вернуть метаданные сервиса, список tools и текущую версию набора сотрудников.
Аргументы: отсутствуют.
Возвращает:
{
"service_name": "miem-employees",
"backend_version": "0.5.0",
"protocolVersion": "2024-11-05",
"tools": [],
"dataset": {
"hash": "sha256",
"previous_hash": "sha256 или null",
"created_at": "2026-05-14T10:00:00+00:00",
"crawl_run_id": 123,
"employee_count": 100,
"active_count": 95,
"dismissed_count": 5
}
}
Особенность: перед ответом сервис создает актуальную dataset_version, если текущий набор сотрудников еще не имеет версии.
sync_employees
Назначение: синхронизировать клиентский кэш сотрудников по hash набора данных.
Аргументы:
{
"client_hash": "sha256 или null",
"include_data": true
}
client_hash: hash версии, которая уже есть у клиента. Если не передан, отдается полный snapshot.include_data: управляет включением полногоdataв карточки сотрудников. По умолчаниюtrue.
Полный ответ без client_hash:
{
"mode": "full",
"from_hash": null,
"to_hash": "current-sha256",
"dataset": {},
"items": []
}
Если клиентский hash совпадает с текущим:
{
"mode": "delta",
"from_hash": "current-sha256",
"to_hash": "current-sha256",
"dataset": {},
"changes": {
"added": [],
"updated": [],
"dismissed": [],
"removed": []
}
}
Если client_hash неизвестен серверу:
{
"mode": "full",
"from_hash": "missing",
"to_hash": "current-sha256",
"dataset": {},
"items": [],
"reason": "unknown_client_hash"
}
Если client_hash найден и отличается от текущего:
{
"mode": "delta",
"from_hash": "old-sha256",
"to_hash": "current-sha256",
"dataset": {},
"changes": {
"added": [],
"updated": [],
"dismissed": [],
"removed": []
}
}
Логика delta:
added: сотрудник появился в новой версии.updated: изменился checksum или статус, и сотрудник активен.dismissed: сотрудник есть в новой версии, но получил статусdismissed.removed:profile_keyбыл в старой версии, но отсутствует в новой.
Hash набора считается по отсортированному списку {profile_key, status, checksum}.
search_employees
Назначение: найти сотрудников по ФИО или canonical URL.
Аргументы:
{
"query": "Сергеев",
"status": "active",
"limit": 20
}
query: обязательный по schema, но в коде пустая строка означает поиск без текстового фильтра.status: опционально, толькоactiveилиdismissed.limit: максимум 100, по умолчанию 20.
Возвращает массив кратких employee payload без data:
[
{
"profile_key": "staff:avsergeev",
"profile_id": "avsergeev",
"full_name": "Сергеев Алексей Викторович",
"status": "active",
"canonical_url": "https://www.hse.ru/staff/avsergeev",
"last_seen_at": "2026-05-14T10:00:00+00:00",
"dismissed_at": null
}
]
get_employee
Назначение: получить одну карточку сотрудника.
Аргументы:
{
"profile_id_or_url": "avsergeev"
}
Поиск выполняется по:
profile_keyprofile_id- точному
canonical_url - частичному совпадению
canonical_url
Возвращает полный employee payload с data.
Если сотрудник не найден:
{
"error": "not_found"
}
list_employee_publications
Назначение: вернуть публикации сотрудника. Если есть нормализованные строки в employee_publications, tool возвращает детальные публикационные данные: авторов, DOI, аннотацию, описание, citation text, год, тип, язык, статус и ссылки. Если детальная таблица еще не заполнена, tool использует старый fallback из employees.current_data.sections[].publications.
Аргументы:
{
"profile_id_or_url": "avsergeev"
}
Поиск сотрудника выполняется так же, как в get_employee: по profile_key, profile_id, точному или частичному canonical_url.
Порядок источников:
- сначала
employee_publications, отсортированные по году, названию и внутреннему id; - если записей нет, секции
current_data.sectionsсtype = "publications"и массивамиpublications.
Ответ:
{
"employee": {
"profile_key": "org_person:803294906",
"profile_id": "803294906",
"full_name": "Борисов Сергей Петрович",
"status": "active",
"canonical_url": "https://www.hse.ru/org/persons/803294906",
"last_seen_at": "2026-05-14T10:00:00+00:00",
"dismissed_at": null
},
"items": [
{
"id": "888959076",
"publication_id": "888959076",
"title": "Название публикации",
"text": "Краткое описание или citation",
"url": "https://publications.hse.ru/view/888959076",
"year": 2023,
"type": "ARTICLE",
"publication_type": "ARTICLE",
"language": "ru",
"status": 1,
"doi_url": "https://doi.org/10.53921/18195822_2023_23_4_624",
"other_url": "https://example.test",
"document_url": "https://example.test/file.pdf",
"citation_text": "Авторы. Название публикации // Журнал. 2023.",
"annotation": {
"ru": "Аннотация",
"en": "Abstract"
},
"description": {
"main": "Авторы. Название публикации // Журнал. 2023."
},
"authors": [
{
"id": "803294906",
"href": "https://www.hse.ru/org/persons/803294906",
"title_ru": "Борисов С. П.",
"title_en": "",
"reverse_title_ru": "С. П. Борисов",
"reverse_title_en": "",
"alt_name": "S. P. Borisov",
"other_name": null,
"is_current_employee": true
}
]
}
]
}
В fallback-режиме из current_data старые элементы могут содержать только базовые поля title, text, url и id.
Если сотрудник не найден:
{
"items": []
}
Если сотрудник найден, но публикаций нет:
{
"employee": {},
"items": []
}
list_employee_courses
Назначение: вернуть курсы преподавания сотрудника из распарсенных секций профиля.
Аргументы:
{
"profile_id_or_url": "avsergeev"
}
Сервис ищет секции current_data.sections с type = "courses_by_year" и объединяет массивы courses.
Ответ:
{
"employee": {},
"items": [
{
"title": "Название курса",
"url": "https://..."
}
]
}
Если сотрудник или данные профиля отсутствуют:
{
"items": []
}
get_crawl_status
Назначение: вернуть последний запуск парсинга.
Аргументы: отсутствуют.
Ответ:
{
"id": 123,
"status": "completed",
"source_url": "https://miem.hse.ru/persons",
"started_at": "2026-05-14T10:00:00+00:00",
"finished_at": "2026-05-14T10:10:00+00:00",
"found_count": 100,
"parsed_count": 98,
"error_count": 2,
"dismissed_count": 1
}
Если запусков еще не было:
{
"status": "never_run"
}
get_crawl_run_details
Назначение: вернуть детальную информацию по конкретному запуску парсинга: summary, изменения сотрудников и ошибки.
Аргументы:
{
"run_id": 123
}
Ответ:
{
"id": 123,
"source_url": "https://miem.hse.ru/persons",
"status": "completed",
"status_display": "Завершен",
"started_at": "2026-05-14T10:00:00+00:00",
"finished_at": "2026-05-14T10:10:00+00:00",
"started_display": "14.05.2026 13:00",
"finished_display": "14.05.2026 13:10",
"found_count": 100,
"parsed_count": 98,
"new_count": 3,
"error_count": 2,
"dismissed_count": 1,
"processed_count": 100,
"progress_percent": 100.0,
"message": null,
"changes_detail_available": true,
"changes": {
"new": [],
"missing_from_source": [],
"dismissed": []
},
"errors": []
}
Если запуск не найден:
{
"error": "not_found"
}
Примеры curl
Список tools:
curl http://localhost:8001/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Поиск сотрудника:
curl http://localhost:8001/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_employees","arguments":{"query":"Сергеев","limit":5}}}'
Полная синхронизация:
curl http://localhost:8001/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"sync_employees","arguments":{"include_data":false}}}'
Delta-синхронизация:
curl http://localhost:8001/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"sync_employees","arguments":{"client_hash":"known-sha256","include_data":true}}}'
Как MCP используется клиентом
- Клиент вызывает
initializeи проверяетprotocolVersion. - Клиент вызывает
tools/list, чтобы получить актуальный список tools и input schemas. - Для поиска и точечных запросов клиент вызывает
tools/callсsearch_employees,get_employee,list_employee_publications,list_employee_courses,get_crawl_statusилиget_crawl_run_details. - Для локального кэша клиент вызывает
get_service_infoилиsync_employees. - Клиент хранит последний
dataset.hash. - При следующей синхронизации клиент передает hash как
client_hash. - Сервер возвращает пустую delta, delta с изменениями или полный snapshot, если hash неизвестен.
Важные особенности реализации
- MCP endpoint read-only: tools не запускают парсинг и не меняют сотрудников напрямую.
get_service_infoиsync_employeesмогут создать новую записьdataset_versions, если состояние сотрудников изменилось и новой версии еще нет.- Все tool payloads возвращаются как JSON-строка внутри
content[0].text. search_employeesищет черезilikeпоfull_nameиcanonical_url.get_employeeдопускает частичный URL, поэтому строка133709486может найтиhttps://www.hse.ru/org/persons/133709486.- Временные значения сериализуются через
isoformat(), display-поля для админских payload формируются в часовом поясеEurope/Moscow.