mcp_feast
mcp_feast
MCP-сервер поверх хранилища признаков Feast для модели обнаружения мошенничества при оплате картой. Работает полностью локально: Parquet-офлайн-хранилище, SQLite-онлайн-хранилище, без облака и без брокера.
Архитектура системы
Четыре уровня
flowchart TB
subgraph H["HOST — decides which tools to call"]
direction LR
H1["host.py<br/><i>local LLM, qwen2.5:7b</i>"]
H2["Claude Code<br/><i>.mcp.json</i>"]
H3["mcp_cli.py<br/><i>manual, for testing</i>"]
end
subgraph M["MCP SERVER — no Feast import, no credentials"]
M1["12 read tools<br/>+ 2 gated write tools"]
end
subgraph A["FEATURE API — holds the Feast SDK"]
A1["catalog"]
A2["lineage"]
A3["health"]
A4["values"]
end
subgraph S["STORAGE"]
direction LR
S1[("registry.db<br/><i>metadata</i>")]
S2[("online_store.db<br/><i>SQLite, serving</i>")]
S3[("data/*.parquet<br/><i>offline</i>")]
end
H1 -->|"stdio"| M1
H2 -->|"stdio"| M1
H3 -->|"stdio"| M1
M1 ==>|"HTTP / JSON"| A1
A1 -->|"Feast SDK"| S1
A2 --> S1
A3 --> S3
A4 --> S2Эта толстая стрелка и есть вся архитектура. Всё, что специфично для Feast, находится ниже неё. MCP-серверу выше неё не нужен установленный Feast, драйверы хранилищ или учётные данные для хранилища данных — он является HTTP-клиентом и ничем более.
Это даёт три преимущества. Замена SQLite на Redis становится изменением в feature_store.yaml, которое MCP-уровень никогда не увидит. Ноутбуку, на котором работает MCP-сервер, нужен один доступный URL вместо сетевого маршрута к продакшен-Redis. И тот же API может обслуживать второго потребителя — сервер моделей, который здесь не создавался, но вызывал бы POST /features/online точно так же, как это делает MCP-уровень.
Related MCP server: tecton-mcp
Что реально запускается
Процесс | Запускается с помощью | Владеет | Порт |
Feature API |
| синглтоном | 8000 |
MCP-сервер | хостом через stdio | клиентом | — |
Ollama |
| qwen2.5:7b | 11434 |
Хост |
| циклом диалога | — |
Только API импортирует Feast. Проверьте это:
python3 -c "import mcp_server.server, sys; print('feast' in sys.modules)" # FalseОдин запрос от начала до конца
Вопрос «почему карта C-4471 была помечена?» пересекает каждый уровень дважды:
sequenceDiagram
autonumber
participant L as Model
participant M as MCP server
participant A as Feature API
participant F as Feast SDK
participant D as SQLite
L->>M: resolve_card("C-4471")
M->>A: GET /cards/C-4471
A-->>M: CU-8842
M-->>L: C-4471 is owned by CU-8842
Note over L: the model spans two entities,<br/>so both join keys are needed
L->>M: explain_features_for_entity(card + customer)
M->>A: POST /features/explain
A->>F: get_online_features(fraud_model_v2)
F->>D: read 7 values
A->>F: provider.online_read(...)
F->>D: read per-entity event_ts
Note over A: joins values against TTL<br/>to classify each feature
A-->>M: values + age + is_stale + reasons
M-->>L: FRESH 6 / STALE 0 / MISSING 1Этот второй вызов SDK — та часть, которую Feast не даёт бесплатно — см. ниже.
Как данные попадают в онлайн-хранилище
flowchart LR
P[("data/*.parquet<br/>offline store")]
O[("online_store.db<br/>online store")]
W["live swipe"]
R["serving<br/><i>milliseconds</i>"]
T["training set"]
P -->|"feast materialize — batch, scheduled"| O
W -->|"feast push — real time, no broker"| O
O -->|"get_online_features"| R
P -.->|"get_historical_features — not exposed"| TПунктирный путь — это обучающая половина хранилища признаков. Он намеренно опущен: он выполняет запрос, который длится минуты и возвращает миллионы строк, что не подходит для чат-инструмента. Именно поэтому генератор не записывает метки мошенничества.
Почему API не является прокси-передачей
get_online_features() возвращает только значения и ничего больше. Голый null не может сказать, в какой из четырёх ситуаций вы находитесь — а Feast без жалоб выдаёт просроченное значение:
flowchart LR
B["get_online_features<br/><b>txn_count_1h: null</b>"]
B --> C1["<b>ENTITY_NOT_FOUND</b><br/>no row for this card"]
B --> C2["<b>NULL_IN_SOURCE</b><br/>feature genuinely absent"]
B --> C3["<b>STALE</b><br/>6h58m old, TTL is 2h"]
B --> C4["<b>a real zero</b><br/>the card had no swipes"]POST /features/explain разделяет их, восстанавливая event_timestamp для каждой сущности через online_read провайдера — тот же вызов, который get_online_features делает внутри, но который показывает временную метку — и сопоставляя её с TTL представления.
Три факта, которые сырой SDK не даст:
Endpoint | Выводит |
| свежесть по каждому признаку и причину отсутствия значения |
| источник → представление → потребляющие сервисы |
| радиус поражения перед изменением |
Ловушка, для избежания которой это построено
Свежесть — по сущности, а не по представлению. Оба вопроса реальны и имеют разные ответы, и их путаница — самая опасная ошибка, доступная здесь:
flowchart TB
V["<b>card_velocity</b><br/>materialized 52 seconds ago<br/>check_feature_freshness reports OK"]
V -->|"source had a row from 58m ago"| E1["<b>C-4471</b><br/>age 58m<br/>FRESH"]
V -->|"source's newest row is 6h58m old"| E2["<b>C-7788</b><br/>age 6h58m<br/>STALE"]
style E1 stroke:#2a9d4a,stroke-width:2px
style E2 stroke:#d1443c,stroke-width:3pxМатериализация записывает то, что содержит источник. Для карты без недавних строк это старое значение — поэтому сущность может быть на часы устаревшей внутри представления, которое материализовалось секунды назад. Обновление представления не может это исправить; только push может.
Вопрос | Инструмент | Область |
«Мёртв ли конвейер?» |
| все сущности |
«Актуальна ли эта карта?» |
| одна сущность |
Небольшая модель стабильно путает эти понятия. Что исправило это — не системный промпт, а добавление предупреждения в вывод check_feature_freshness. Модель, которая пропускает описание инструмента, всё равно читает результат, на который она только что действовала.
Инструменты сопоставляются с эндпоинтами один к одному
flowchart LR
T1["list_feature_views<br/>describe_feature_view<br/>list_feature_services<br/>search_features<br/>list_entities<br/>resolve_card"] --> E1["/entities · /data-sources<br/>/feature-views · /feature-services<br/>/features/search · /cards"]
T2["get_feature_lineage<br/>get_feature_consumers"] --> E2["/features/../lineage<br/>/feature-views/../consumers"]
T3["check_feature_freshness"] --> E3["/health/materialization"]
T4["get_online_features<br/>explain_features_for_entity"] --> E4["/features/online<br/>/features/explain"]
T5["push_swipe<br/>trigger_materialization"] -.->|"only when FEAST_MCP_READONLY=false"| E5["/features/push<br/>/feature-views/../materialize"]api/routers/ и mcp_server/tools/ зеркально отражают друг друга файл за файлом — каталог, происхождение, здоровье, значения — так что навигация очевидна.
Две идеи, которые определили дизайн
Ошибки записаны как инструкции. 404 возвращает Available: [...], а строка с неверной сущностью называет ключи соединения, которые ей нужны. Наблюдалось многократно: модель 7B ошибается, читает ошибку и исправляется на следующем шаге, а не угадывает снова.
Руководство передаётся через вывод, а не только через описания. Описания инструментов пропускаются; результаты — нет. И предупреждение о области свежести, и «вызовите check_feature_freshness для подтверждения» в trigger_materialization находятся в возвращаемом тексте, и оба изменили поведение модели, когда только формулировка промпта не сработала.
Быстрый старт
Python 3.11. Feast объявляет >=3.10, но классифицирует только 3.10, и его транзитивный стек — обычный источник проблем на более новых интерпретаторах.
python3.11 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
./setup.sh # preflight + data + apply + materialize
./run_api.sh # API on :8000, docs at /docsОба скрипта учитывают переменную PYTHON, если зависимости находятся в другом месте:
PYTHON=/opt/miniconda3/envs/myenv/bin/python3 ./setup.shMCP-сервер запускается хостом через .mcp.json, который фиксирует абсолютный путь к интерпретатору по причине, указанной в разделе «Устранение неполадок» ниже. ./run_mcp.sh запускает его вручную для отладки.
Устранение неполадок: неверный интерпретатор
Два симптома, одна причина — другой Python, чем тот, в котором находятся зависимости:
ModuleNotFoundError: No module named 'feast'
ImportError: cannot import name 'MCPServer' from 'mcp.server'Второй — более коварный: mcp 1.x импортируется нормально, но предоставляет mcp.server.fastmcp.FastMCP, а не mcp.server.MCPServer 2.x, который используется в этом проекте. Приглашение оболочки с активной средой conda — не доказательство; проверьте PATH:
which python3 && python3 -V
echo $PATH | tr ":" "\n" | head -3Если системный Python или Python фреймворка находится перед вашей средой, каждый вызов python3 выходит из среды, независимо от того, что говорит приглашение. Диагностируйте правильно с помощью:
python3 preflight.pyОн импортирует точный символ, который нужен каждой части кода — не только модуль — так что зависимость с неверной мажорной версией обнаруживается по имени, и он предупреждает, когда uvicorn или feast в вашем PATH принадлежат другой среде.
Каждая точка входа принимает переопределение PYTHON, так что вам никогда не придётся бороться с PATH:
PYTHON=/opt/miniconda3/envs/myenv/bin/python3 ./setup.sh
PYTHON=/opt/miniconda3/envs/myenv/bin/python3 ./run_api.sh
PYTHON=/opt/miniconda3/envs/myenv/bin/python3 python3 mcp_cli.py toolsДва правила полностью избегают этого:
Запускайте API с помощью
./run_api.shилиpython3 -m uvicorn api.main:app. Никогда не запускайте голыйuvicorn api.main:app— это разрешает uvicorn из PATH, который может принадлежать другому Python, чем тот, в котором находится Feast, и ошибка всплывает на сорок кадров глубже в цепочке импорта.Держите
commandв.mcp.jsonкак абсолютный путь к интерпретатору."python3"там разрешается относительно PATH, который был у хост-процесса.
Что находится в реестре
Сущности — card (card_id), customer (customer_id)
Представления признаков
Представление | Сущность | Тип | Признаки | TTL |
| card | push |
| 2h |
| customer | batch |
| 7d |
Сервис признаков — fraud_model_v2, связывающий все 7 признаков.
Разделение TTL 2h / 7d намеренно: оно заставляет инструменты проверки свежести давать реальные ответы, а не постоянное всё-зелёное.
Тестовые данные
data_gen/generate_swipes.py записывает 15 000 снимков клиентов (500 клиентов × 30 дней) и 14 394 строки скорости (600 карт × 24 часа, минус 6 удалённых для создания случая устаревания). Всё привязано к времени выполнения, поэтому повторная генерация всегда даёт данные, которые чисто материализуются.
Шесть персон зафиксированы, чтобы демонстрации были детерминированными:
Карта / Клиент | Настройка | Демонстрирует |
| 7 свайпов/час, $2 140 против средних $58,20, chargebacks null | Случай мошенничества и нулевой признак |
| Всё медианное | Контроль |
| Самая новая строка скорости — 6 часов назад | Устаревание за пределами TTL 2h |
| Никогда не генерировалась | Неизвестная сущность |
| Профиль есть, карты нет | Частичное покрытие |
| 4 chargebacks, нормальная скорость | Риск, который не является скоростью |
API
Группа | Эндпоинты |
Каталог |
|
Происхождение |
|
Здоровье |
|
Значения |
|
Интерактивная документация на http://localhost:8000/docs.
API не является прокси-передачей. Он делает три вещи, которые сырой SDK не делает: объединяет метаданные реестра с временными метками онлайн-хранилища для вычисления свежести, проходит по цепочке источник → представление → сервис для вычисления происхождения и уплощает proto-формы Feast в простые именованные объекты.
MCP-инструменты
12 инструментов только для чтения, плюс 2 инструмента записи, которые регистрируются только при включённой записи.
list_feature_views · describe_feature_view · list_feature_services ·
describe_feature_service · search_features · list_entities · resolve_card ·
get_feature_lineage · get_feature_consumers · check_feature_freshness ·
get_online_features · explain_features_for_entity ·
push_swipe ⚠ · trigger_materialization ⚠
Два вида свежести
Они отвечают на разные вопросы, и их путаница — самая опасная ошибка, доступная здесь:
Инструмент | Отвечает | Область |
| «Мёртв ли конвейер?» | Все сущности, уровень представления |
| «Актуальны ли данные этой карты?» | Одна сущность |
Отдельная сущность может быть на шесть часов устаревшей внутри представления, которое материализовалось секунды назад — материализация записывает то, что содержал источник, а для карты без недавних строк это старое значение. Поэтому представление, показывающее OK, ничего не доказывает о конкретной карте.
Небольшая модель стабильно путает эти два понятия и отвечает «достаточно актуально, чтобы доверять» на основе метаданных уровня представления. Три уровня защищают от этого: INSTRUCTIONS сервера, описание инструмента check_feature_freshness и примечание, добавленное к выводу этого инструмента — последнее сработало на самом деле, поскольку модель, пропустившая описание, всё равно читает результат, на который она действовала.
Зачем существует explain_features_for_entity
get_online_features возвращает голые значения. Голый null не может различить четыре разные ситуации, и Feast без жалоб выдаёт просроченное значение:
настоящий ноль
представление, которое никогда не материализовалось
сущность, которая не существует
значение, которое просрочено по TTL
explain_features_for_entity разделяет их, используя event_ts для каждой сущности, восстановленный из онлайн-хранилища. Поэтому это предпочтительный инструмент извлечения.
FEAST_MCP_READONLY
Читается обоими процессами. Если true (по умолчанию), MCP-сервер вообще не регистрирует push_swipe или trigger_materialization — инструмент, который модель не видит, она и не попытается вызвать, — а API независимо возвращает 403 на этих маршрутах, так что прямой запрос через curl тоже отклоняется.
Локальный LLM-хост
host.py — это настоящий MCP-хост, управляемый локальной open-source моделью — без API-ключа, ничего внешнего. Модель сама решает, какие инструменты вызывать; mcp_cli.py вызывает только те инструменты, которые вы назвали.
ollama/qwen2.5:7b -> host.py -> MCP server -> Feature API -> Feast -> SQLiteollama serve & # if not already running
ollama pull qwen2.5:7b # any tool-calling model works
python3 host.py "Why would card C-4471 be flagged?"
python3 host.py --trace --quiet "Is anything stale?"
python3 host.py # interactiveСистемный промпт не прописан в host.py. Он приходит из собственных instructions MCP-сервера, возвращаемых во время initialize() — сервер сообщает модели, как следует использовать его инструменты, а хост просто передаёт это дальше. Изменение INSTRUCTIONS в mcp_server/server.py меняет поведение модели без правки хоста.
Выбор модели имеет значение: нужна поддержка вызова инструментов. qwen2.5:7b работает; у Gemma нет tool-шаблона в Ollama, и она не заработает.
Защитные механизмы хоста
Модель на 7B — ненадёжный планировщик, поэтому цикл защищается от трёх сбоев, которые она реально демонстрирует:
Сбой | Защитный механизм |
Повторяет уже сделанный вызов, иногда до упора в лимит шагов | Результаты кэшируются по (tool, args); повтор обслуживается из кэша с пометкой «ты это уже делал» вместо второго круга запросов |
Описывает следующий вызов прозой («Next, let's call describe_feature_view») вместо того, чтобы его эмитировать | Распознаётся, один раз подталкивается к эмиссии вызова, а не его описанию (макс. 2) |
Уходит за бюджет шагов без ответа | На последнем шаге — или после 3 повторов — инструменты отзываются, так что модель обязана ответить по собранным данным |
Каждый случай печатает строку HOST |, так что вмешательство цикла видно.
Даже так, на открытых промптах стоит ждать блужданий. Практическое решение — ограничить набор инструментов:
python3 host.py --tools resolve_card,explain_features_for_entity,check_feature_freshness \
"Why would card C-4471 be flagged?"Наблюдаем, как MCP вызывает API
mcp_cli.py говорит на том же stdio-протоколе, что и хост, так что цепочку MCP → API можно наблюдать из шелла:
python3 mcp_cli.py tools # what is registered
python3 mcp_cli.py --trace demo # 11-step walkthrough, with HTTP calls
python3 mcp_cli.py --trace call resolve_card '{"card_id": "C-4471"}'--trace печатает эндпоинт, который задевает каждый инструмент:
http | HTTP Request: GET http://localhost:8000/cards/C-4471 "HTTP/1.1 200 OK"
C-4471 is owned by CU-8842Попробуйте
Разобрать отклонение
«Почему карта C-4471 получила отказ?»
list_feature_services → resolve_card → explain_features_for_entity. Возвращает 7 свайпов за последний час на сумму $2,140 при среднем $58.20, причём история чарджбэков явно недоступна, а не принята за ноль.
Поймать мёртвый пайплайн
«Есть ли что-то устаревшее для карты C-7788?»
explain_features_for_entity помечает card_velocity как устаревший на 6ч46м при TTL в 2ч. Значения всё равно возвращаются — ничто не блокирует чтение, — и именно поэтому флаг так нужен.
Круговой прогон push (нужны включённые записи)
«Запиши свайп по C-7788, затем проверь снова.»
push_swipe → та же карта читается свежей. trigger_materialization на card_velocity сбрасывает её к строке батча 6-часовой давности, так что демо можно повторять.
Структура
requirements.txt pinned, verified working set
preflight.py interpreter + dependency check, run by both scripts
setup.sh data + apply + materialize
run_api.sh starts the API on the right interpreter
run_mcp.sh starts the MCP server by hand (debugging)
mcp_cli.py drives the MCP server from a shell, with --trace
host.py local-LLM MCP host -- the model picks the tools
feature_repo/ Feast definitions + feature_store.yaml (the only Feast config)
data_gen/ mock data generator
api/ FastAPI + the Feast SDK <- the API boundary
routers/ catalog | lineage | health | values
mcp_server/ MCP tools, HTTP client only <- no Feast import
tools/ catalog | lineage | health | values | adminapi/routers/ и mcp_server/tools/ зеркально соответствуют друг другу один-в-один.
Примечания
chargebacks_lifetime— этоFloat64, а неInt64. Фича действительно nullable, а у null-целого нет представления в пути Parquet → pandas → Feast.Для настройки используйте
feast materialize, а неmaterialize-incremental. Инкрементальный режим берёт TTL вьюхи как нижнюю границу, так что при TTL в 2ч он пропустил бы строку 6-часовой давности, на которой держится персона «устаревшие данные».Кэширование реестра.
cache_ttl_seconds: 30вfeature_store.yamlозначает, чтоfeast applyиз другого шелла подхватывается в течение 30с.POST /admin/reloadфорсирует это немедленно, а также переоткрывает online store — чего голое обновление реестра не делает.Мок-данные привязаны ко времени. У
card_velocityTTL 2ч, так что спустя пару часов после./setup.shкаждая карта читается устаревшей, и персонажи перестают различаться. Перезапустите./setup.sh.Конкурентность SQLite. Запись
feast materializeво время чтения uvicorn может приводить к конфликтам блокировок. Локально это нормально; это не production online store.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables AI models to interact with local CSV and Parquet data through MCP tools, providing summarization and analysis capabilities.1
- FlicenseNot gradedqualityDmaintenanceEnables interaction with Tecton clusters through MCP, allowing management of feature stores, execution of Tecton CLI commands, and retrieval of feature store configurations via natural language.
- AlicenseAqualityDmaintenanceExposes Azure AI Foundry agents, workflows, and AI Search vector-database capabilities as MCP tools, enabling natural language interaction with agents, semantic search, and index management.102MIT
- FlicenseNot gradedqualityCmaintenanceEnables AI agents to manage local project files and Git operations through MCP tools, including file CRUD, search, Git status, recent commits, and project summaries.
Related MCP Connectors
Hosted MCP endpoint with realistic fake data for prototyping agents. 12 tools, no setup.
Shared, governed long-term memory for AI agents across tools and sessions via MCP and REST.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/sidbu546/mcp_feast_dev'
If you have feedback or need assistance with the MCP directory API, please join our Discord server