MCP-сервер (Model Context Protocol) для интеллектуального поиска по базе данных недвижимости vmk_data. Предоставляет AI-агентам (Claude, GPT и др.) безопасный набор инструментов для семантического и полнотекстового поиска объявлений с фильтрацией, пагинацией и сортировкой по релевантности.
| Функция | Технология | Описание |
|---|---|---|
| Семантический поиск | pgvector + HNSW + Ollama |
Поиск объявлений «по смыслу» через векторную близость (cosine distance) |
| Полнотекстовый поиск | PostgreSQL FTS (украинский конфиг) | Поиск по ключевым словам с ранжированием по релевантности (ts_rank_cd) |
| Фильтрация метаданных | SQL WHERE с параметрами |
Цена, район, комнаты, метро, тип сделки, статус и др. |
| Пагинация | LIMIT / OFFSET |
Настраиваемый размер страницы (1–100) |
| Read-only безопасность | default_transaction_read_only = on + валидация SQL |
Гарантированная защита от записи/изменения данных |
| Потоковый HTTP | MCP Streamable HTTP | Поддержка SSE-стрима + POST на порту 8080 |
┌─────────────────┐ HTTP (SSE+POST) ┌──────────────────────────────┐
│ AI Агент │ ───────────────────────> │ VMK Data MCP Server │
│ (Claude/GPT) │ port 8080 /mcp │ (FastMCP + Starlette) │
└─────────────────┘ └──────────────────────────────┘
│
┌────────────────────────────┼────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ PostgreSQL │ │ Ollama API │ │ Логирование │
│ vmk_data │ │ nomic-embed- │ │ (structlog) │
│ + pgvector │ │ text 768d │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
/api/embed).vector.embedding <=> $1::vector <= $2.Сервер регистрирует 4 инструмента, доступных через MCP-протокол:
search_similar_listingsВекторный (семантический) поиск — находит объявления, близкие по смыслу к запросу.
Входные параметры:
query (string, обязательный) — текст на украинском для эмбеддингаfilters (object) — фильтры метаданных (см. Фильтры)pagination (object) — limit (1–100, по умолч. 20), offset (≥ 0)min_similarity (float) — порог косинусной близости (0.0–1.0, по умолч. 0.7)Выход: SearchResult — список объявлений с полем similarity_score.
⚠️ Параметр
min_similarityпреобразуется в максимальное косинусное расстояние:max_distance = 2.0 × (1.0 − min_similarity), потому что оператор<=>в pgvector возвращает расстояние в диапазоне [0, 2].
search_by_metadataПолнотекстовый поиск (FTS) — ищет по ключевым словам в search_vector.
Входные параметры:
query (string, обязательный) — текстовый запрос на украинскомfilters (object) — те же фильтры, что и для векторного поискаpagination (object) — пагинацияВыход: SearchResult — список с полем rank_score (релевантность FTS).
get_listing_by_idПолучение объявления по ID — точечная выборка одной записи.
Входные параметры:
listing_id (integer, обязательный) — id объявленияВыход: ListingResult или {"error": "..."} если не найдено.
describe_schemaОписание схемы БД — возвращает структуру таблицы property_listings (колонки, типы, индексы) для подсказок AI-агенту при формировании запросов.
Входных параметров нет.
Выход: JSON-описание схемы.
property_listingsКлючевые колонки, доступные для чтения (белый список USER_COLUMNS):
| Колонка | Тип | Описание |
|---|---|---|
id |
bigint |
Первичный ключ |
title |
text |
Заголовок объявления |
description |
text |
Описание |
generated_description |
text |
AI-сгенерированное описание |
price |
numeric |
Цена |
currency |
varchar(3) |
Валюта: USD, EUR, UAH |
deal_type |
varchar |
Тип сделки: sale, rent_long, rent_short |
city |
varchar |
Город (украинский) |
district |
varchar |
Район (украинский) |
rooms_count |
int |
Количество комнат |
total_area |
float |
Общая площадь, м² |
living_area |
float |
Жилая площадь, м² |
kitchen_area |
float |
Площадь кухни, м² |
floor |
int |
Этаж |
floors_count |
int |
Этажность дома |
building_type |
varchar |
brick, panel, monolith, gas_block, wood |
building_year |
int |
Год постройки |
renovation_status |
varchar |
Статус ремонта |
balcony_count |
int |
Количество балконов |
bathroom_type |
varchar |
Тип санузла |
parking_type |
varchar |
Тип парковки |
heating_type |
varchar |
Тип отопления |
layout_type |
varchar |
Тип планировки |
window_view |
varchar |
Вид из окон |
metro_station |
varchar |
Станция метро |
metro_distance_type |
varchar |
walking, transport |
metro_distance_meters |
int |
Расстояние до метро, м |
url_source |
text |
Ссылка на источник |
publish_date |
date |
Дата публикации |
images_count |
int |
Количество фото |
contact_phone |
varchar |
Телефон контакта |
listing_status |
varchar |
active, sold, rented, removed, archived |
archived_at |
date |
Дата архивации |
created_at / updated_at |
date |
Служебные таймстампы |
embedding |
vector(768) |
Вектор эмбеддинга (pgvector) |
search_vector |
tsvector |
Полнотекстовый индекс (украинский конфиг) |
property_listings_embedding_idx — HNSW на embedding (vector_cosine_ops)property_listings_search_vector_idx — GIN на search_vectorgit clone <repo-url> cd data_mcp python -m venv .venv source .venv/bin/activate # Linux/macOS # .venv\Scripts\activate # Windows pip install -e "."
cp .env.example .env # Отредактируйте .env — укажите DATABASE_URL и OLLAMA_BASE_URL
python -m vmk_data_mcp.main # или uvicorn vmk_data_mcp.main:app --host 0.0.0.0 --port 8080
Сервер стартует на http://localhost:8080/mcp.
Все параметры задаются через .env или переменные окружения:
| Переменная | По умолчанию | Описание |
|---|---|---|
DATABASE_URL |
postgresql://postgres:postgres@localhost:5432/vmk_data |
DSN для asyncpg |
DB_POOL_MIN_SIZE |
2 |
Минимум соединений в пуле |
DB_POOL_MAX_SIZE |
10 |
Максимум соединений в пуле |
DB_QUERY_TIMEOUT |
30 |
Таймаут SQL-запросов, сек |
OLLAMA_BASE_URL |
http://192.168.1.75:11434 |
URL Ollama API |
OLLAMA_EMBED_MODEL |
nomic-embed-text |
Модель эмбеддинга |
OLLAMA_EMBED_DIMENSIONS |
768 |
Размерность вектора |
OLLAMA_REQUEST_TIMEOUT |
60.0 |
Таймаут запроса к Ollama, сек |
MCP_SERVER_NAME |
vmk-data-mcp |
Имя сервера в MCP |
MCP_PORT |
8080 |
Порт HTTP-транспорта |
docker-compose up --build -d
docker-compose.yml использует host.docker.internal для доступа к PostgreSQL и Ollama, запущенным на хост-машине.
Linux:
host.docker.internalтребуетextra_hosts: ["host.docker.internal:host-gateway"](уже прописано вdocker-compose.yml).
CREATE EXTENSION IF NOT EXISTS vector; CREATE EXTENSION IF NOT EXISTS pg_trgm; -- опционально
nomic-embed-text (768 dimensions):
ollama pull nomic-embed-text
search_vector требуется украинская конфигурация FTS (либо simple, если украинский конфиг отсутствует — проверьте pg_ts_config).SET default_transaction_read_only = on._is_safe_query:
select, with, values, explain; внутри строки (блокировка multi-statement атак)USER_COLUMNS участвуют в SELECT; попытка запросить другую колонку вызывает ошибку.Все пользовательские данные передаются через параметризованные запросы ($1, $2 …). Ни один пользовательский параметр не интерполируется в строку SQL.
pytest -q
8 тестов покрывают:
limit / offset)ruff check src tests ruff format src tests
data_mcp/ ├── src/vmk_data_mcp/ │ ├── __init__.py │ ├── main.py # FastMCP сервер + HTTP transport │ ├── tools.py # Реализация 4 инструментов │ ├── models.py # Pydantic-модели входных/выходных данных │ ├── db.py # asyncpg пул, read-only защита, USER_COLUMNS │ ├── embedder.py # Ollama HTTP клиент для эмбеддингов │ └── config.py # Настройки из .env (pydantic-settings) ├── tests/ │ └── test_models.py # Pytest ├── Dockerfile ├── docker-compose.yml ├── pyproject.toml ├── .env.example └── README.md
Сервис эмбеддингов недоступен: ConnectError(...)
OLLAMA_BASE_URL0.0.0.0 (не только 127.0.0.1)min_similarity — слишком высокий (0.95+) может исключить все записи.embedding (не NULL).vectorCREATE EXTENSION vector;
Сервер ожидает OLLAMA_EMBED_DIMENSIONS=768 (модель nomic-embed-text). Если используется другая модель — обновите переменную окружения.
MIT