Сервер реализован как 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 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
main.py — Entrypoint и FastMCPFastMCP(name=settings.mcp_server_name).@mcp.tool().lifespan — асинхронный контекстный менеджер для инициализации пула БД и закрытия ресурсов.mcp.run(transport="streamable-http", port=settings.mcp_port).Streamable HTTP transport:
GET /mcp — SSE-стрим для сервер→клиент сообщений.POST /mcp — приём сообщений клиент→сервер.tools.py — SQL-конструкторы и бизнес-логикаКаждый инструмент — async-функция, которая:
SearchSimilarInput, SearchMetadataInput …)._build_where_clause() (параметризованный).embedder.get_embedding(query).COUNT(*) (для total) и SELECT … LIMIT/OFFSET.SearchResult → JSON.Ключевая функция _build_where_clause(filters):
None.$N.models.py — Pydantic v2ListingResult, SearchResult — сериализация asyncpg.Record.BaseModel с чёткими Field(description=…).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 ….embedder.py — Ollama клиентhttpx.AsyncClient с base_url и таймаутом.get_embedding(text) → list[float] длины OLLAMA_EMBED_DIMENSIONS.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) |
ef_construction, m настраиваются при создании).vector_cosine_ops?nomic-embed-text генерирует нормализованные векторы.<=>) эквивалентен Euclidean distance для нормализованных векторов, но явно выражает семантическую близость.┌─────────────────────────────────────────────────────────────┐ │ Уровень 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 мс |
COUNT(*) с фильтрами на больших таблицах может требовать pg_stat_statements анализа и частичных индексов.ef_search — при недостаточной точности можно увеличить SET hnsw.ef_search = 100 (по умолчанию обычно 40).Сервер использует стандартный logging из stdlib.
| Уровень | Что логируется |
|---|---|
WARNING |
Ошибки Ollama, БД, валидации (кратко) |
ERROR / EXCEPTION |
Неожиданные ошибки в инструментах (с трейсбеком) |
INFO |
Старт/остановка сервера, lifespan события |
Пример:
WARNING vmk_data_mcp.tools: Ollama error: ConnectError(...)