Newer
Older
vmk-360_data_mcp / docs / ARCHITECTURE.md

Архитектура VMK Data MCP Server


Обзор системы

Сервер реализован как MCP (Model Context Protocol) Server на базе FastMCP (Python SDK v1.x) с транспортом Streamable HTTP. Он выступает безопасным посредником между AI-агентом и PostgreSQL-базой недвижимости, предоставляя агенту инструменты поиска без прямого доступа к БД.


Компоненты

┌─────────────────────────────────────────────────────────────────────────────┐
│                            AI Агент (Claude / GPT)                          │
│                              MCP Client                                     │
└───────────────────────────────┬─────────────────────────────────────────────┘
                                │ HTTP SSE + POST
                                ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                         VMK Data MCP Server                                 │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐ │
│  │   main.py   │  │   tools.py  │  │   models.py │  │      embedder.py    │ │
│  │ FastMCP app │  │ 4 tools     │  │  Pydantic   │  │  httpx → Ollama     │ │
│  │ HTTP transport│  │ SQL builders│  │ validation  │  │  /api/embed         │ │
│  └──────┬──────┘  └──────┬──────┘  └─────────────┘  └─────────────────────┘ │
│         │                │                                                    │
│         └────────────────┼────────────────────────────────────────────────────┘
│                          │
│  ┌───────────────────────┴───────────────────────┐
│  │                  db.py                        │
│  │  asyncpg pool + read-only safety            │
│  │  USER_COLUMNS whitelist                       │
│  └───────────────────────┬───────────────────────┘
│                          │ asyncpg
└──────────────────────────┼───────────────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                        PostgreSQL (vmk_data)                                │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────┐ │
│  │  property_      │  │   embedding     │  │   search_vector             │ │
│  │  listings       │  │   vector(768)   │  │   tsvector (ukrainian)      │ │
│  │                 │  │   HNSW index    │  │   GIN index                 │ │
│  └─────────────────┘  └─────────────────┘  └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘

1. main.py — Entrypoint и FastMCP

  • Создаёт экземпляр FastMCP(name=settings.mcp_server_name).
  • Регистрирует 4 инструмента через декоратор @mcp.tool().
  • Определяет lifespan — асинхронный контекстный менеджер для инициализации пула БД и закрытия ресурсов.
  • Запускает mcp.run(transport="streamable-http", port=settings.mcp_port).

Streamable HTTP transport:

  • GET /mcp — SSE-стрим для сервер→клиент сообщений.
  • POST /mcp — приём сообщений клиент→сервер.

2. tools.py — SQL-конструкторы и бизнес-логика

Каждый инструмент — async-функция, которая:

  1. Принимает Pydantic-модель (SearchSimilarInput, SearchMetadataInput …).
  2. Строит SQL через _build_where_clause() (параметризованный).
  3. Для векторного поиска — вызывает embedder.get_embedding(query).
  4. Выполняет 2 запроса: COUNT(*) (для total) и SELECT … LIMIT/OFFSET.
  5. Сериализует результат в SearchResult → JSON.

Ключевая функция _build_where_clause(filters):

  • Проверяет, что значение фильтра не None.
  • Использует strictly allowed columns — только известные имена колонок.
  • Генерирует SQL-условия с нумерованными параметрами $N.

3. models.py — Pydantic v2

  • Входные модели: валидация типов, границ, enum-значений.
  • Выходные модели: ListingResult, SearchResult — сериализация asyncpg.Record.
  • Все модели наследуют BaseModel с чёткими Field(description=…).

4. db.py — Безопасный доступ к БД

Пул asyncpg:

  • create_pool(..., init=_init_conn) — каждое новое соединение получает SET default_transaction_read_only = on.
  • Мин/макс размер пула и таймаут запросов из .env.

Read-only защита _is_safe_query(sql):

  • Разрешённые префиксы: select, with, values, explain.
  • Запрет ; внутри строки — блокировка multi-statement инъекций (DROP TABLE;).

USER_COLUMNS:

  • Белый список колонок property_listings.
  • build_select_columns() — динамически строит SELECT col1, col2 ….

5. embedder.py — Ollama клиент

  • Singleton httpx.AsyncClient с base_url и таймаутом.
  • get_embedding(text)list[float] длины OLLAMA_EMBED_DIMENSIONS.
  • Валидация размерности ответа.

6. config.py — Настройки

pydantic-settings загружает .env с extra="ignore". Все секреты и URL вынесены в переменные окружения.


Потоки данных

Векторный поиск (search_similar_listings)

┌─────────┐    украинский текст    ┌───────────┐
│  Agent  │ ───────────────────>  │ embedder  │
└─────────┘                       └─────┬─────┘
                                      │ HTTP POST /api/embed
                                      ▼
                               ┌──────────────┐
                               │   Ollama     │
                               │ 768d vector  │
                               └──────┬───────┘
                                      │
                                      ▼
┌─────────┐    SQL + $1..$N     ┌──────────┐    vector(768), max_distance
│  Agent  │ <─────────────────── │   db.py  │ <──────────────────────────────┐
└─────────┘    JSON result       └────┬─────┘                                  │
                                      │ asyncpg                               │
                                      ▼                                       │
                               ┌──────────────┐                               │
                               │  PostgreSQL  │                               │
                               │  HNSW scan   │                               │
                               │  embedding   │                               │
                               │  <=> vector  │                               │
                               └──────────────┘                               │
                                                                              │
                                        COSINE DISTANCE  [0, 2]               │
                                        max_distance = 2*(1 - similarity)   │

Полнотекстовый поиск (search_by_metadata)

┌─────────┐    украинский текст    ┌──────────┐
│  Agent  │ ───────────────────>  │  tools   │
└─────────┘                       └────┬─────┘
                                     │
                                     ▼
                              ┌──────────────┐
                              │  PostgreSQL  │
                              │  plainto_ts- │
                              │  query('uk..')│
                              │  @@ search_  │
                              │  vector      │
                              └──────────────┘

Индексы базы данных

Индекс Тип Колонка Оператор / Конфиг
property_listings_embedding_idx HNSW embedding vector(768) vector_cosine_ops
property_listings_search_vector_idx GIN search_vector tsvector (ukrainian)

Почему HNSW, а не IVFFlat?

  • HNSW обеспечивает лучшую точность при высокой селективности фильтров.
  • Не требует перестроения при добавлении данных (ef_construction, m настраиваются при создании).
  • Рекомендуется для production-нагрузки с pgvector ≥ 0.5.0.

Почему vector_cosine_ops?

  • Модель nomic-embed-text генерирует нормализованные векторы.
  • Cosine distance (<=>) эквивалентен Euclidean distance для нормализованных векторов, но явно выражает семантическую близость.

Безопасность (Defense in Depth)

┌─────────────────────────────────────────────────────────────┐
│  Уровень 1: Сеть                                            │
│  — MCP Server доступен только внутри сети / через reverse   │
│    proxy с аутентификацией (не входит в скоуп проекта)      │
├─────────────────────────────────────────────────────────────┤
│  Уровень 2: PostgreSQL роли                               │
│  — Отдельный READ-ONLY пользователь БД (рекомендуется)      │
├─────────────────────────────────────────────────────────────┤
│  Уровень 3: default_transaction_read_only = on             │
│  — Каждая транзакция принудительно read-only                │
├─────────────────────────────────────────────────────────────┤
│  Уровень 4: Валидация SQL (_is_safe_query)                 │
│  — Только SELECT / WITH / VALUES / EXPLAIN                  │
│  — Блокировка `;` (multi-statement)                         │
├─────────────────────────────────────────────────────────────┤
│  Уровень 5: Параметризованные запросы                      │
│  — Никакого f-string / format в SQL                         │
├─────────────────────────────────────────────────────────────┤
│  Уровень 6: USER_COLUMNS whitelist                         │
│  — SELECT только разрешённые колонки                        │
└─────────────────────────────────────────────────────────────┘

Масштабирование и производительность

Параметр Значение Почему
DB_POOL_MAX_SIZE 10 Оптимально для ~50–100 параллельных MCP-запросов
DB_QUERY_TIMEOUT 30 Защита от тяжёлых запросов без LIMIT
LIMIT 1–100 Жёсткий потолок на размер ответа
Ollama timeout 60 Эмбеддинг одного запроса ~300–800 мс

Потенциальные bottleneck'и

  1. Ollama — если запросов много, стоит поднять Ollama на GPU или добавить кеширование эмбеддингов (Redis / in-memory LRU для популярных запросов).
  2. PostgreSQL CPUCOUNT(*) с фильтрами на больших таблицах может требовать pg_stat_statements анализа и частичных индексов.
  3. HNSW ef_search — при недостаточной точности можно увеличить SET hnsw.ef_search = 100 (по умолчанию обычно 40).

Логирование

Сервер использует стандартный logging из stdlib.

Уровень Что логируется
WARNING Ошибки Ollama, БД, валидации (кратко)
ERROR / EXCEPTION Неожиданные ошибки в инструментах (с трейсбеком)
INFO Старт/остановка сервера, lifespan события

Пример:

WARNING  vmk_data_mcp.tools: Ollama error: ConnectError(...)