Skip to main content
Glama

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

./run_api.sh

синглтоном FeatureStore

8000

MCP-сервер

хостом через stdio

клиентом httpx

Ollama

ollama serve

qwen2.5:7b

11434

Хост

python3 host.py

циклом диалога

Только 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

Выводит

/features/explain

свежесть по каждому признаку и причину отсутствия значения

/features/{view}/{feature}/lineage

источник → представление → потребляющие сервисы

/feature-views/{name}/consumers

радиус поражения перед изменением

Ловушка, для избежания которой это построено

Свежесть — по сущности, а не по представлению. Оба вопроса реальны и имеют разные ответы, и их путаница — самая опасная ошибка, доступная здесь:

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

все сущности

«Актуальна ли эта карта

explain_features_for_entity

одна сущность

Небольшая модель стабильно путает эти понятия. Что исправило это — не системный промпт, а добавление предупреждения в вывод 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.sh

MCP-сервер запускается хостом через .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_velocity

card

push

txn_count_1h, txn_count_24h, amount_sum_1h

2h

customer_profile

customer

batch

avg_amount_30d, distinct_merchants_30d, home_country, chargebacks_lifetime

7d

Сервис признаковfraud_model_v2, связывающий все 7 признаков.

Разделение TTL 2h / 7d намеренно: оно заставляет инструменты проверки свежести давать реальные ответы, а не постоянное всё-зелёное.

Тестовые данные

data_gen/generate_swipes.py записывает 15 000 снимков клиентов (500 клиентов × 30 дней) и 14 394 строки скорости (600 карт × 24 часа, минус 6 удалённых для создания случая устаревания). Всё привязано к времени выполнения, поэтому повторная генерация всегда даёт данные, которые чисто материализуются.

Шесть персон зафиксированы, чтобы демонстрации были детерминированными:

Карта / Клиент

Настройка

Демонстрирует

C-4471 / CU-8842

7 свайпов/час, $2 140 против средних $58,20, chargebacks null

Случай мошенничества и нулевой признак

C-1002 / CU-1002

Всё медианное

Контроль

C-7788 / CU-3310

Самая новая строка скорости — 6 часов назад

Устаревание за пределами TTL 2h

C-9999

Никогда не генерировалась

Неизвестная сущность

CU-5150

Профиль есть, карты нет

Частичное покрытие

C-3355 / CU-4402

4 chargebacks, нормальная скорость

Риск, который не является скоростью

API

Группа

Эндпоинты

Каталог

/entities /data-sources /feature-views /feature-views/{n} /feature-services /feature-services/{n} /features/search /cards/{id}

Происхождение

/features/{view}/{feature}/lineage /feature-views/{n}/consumers

Здоровье

/feature-views/{n}/freshness /health/materialization /feature-views/{n}/materialize

Значения

/features/online /features/explain /features/push

Интерактивная документация на 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

Два вида свежести

Они отвечают на разные вопросы, и их путаница — самая опасная ошибка, доступная здесь:

Инструмент

Отвечает

Область

check_feature_freshness

«Мёртв ли конвейер?»

Все сущности, уровень представления

explain_features_for_entity

«Актуальны ли данные этой карты

Одна сущность

Отдельная сущность может быть на шесть часов устаревшей внутри представления, которое материализовалось секунды назад — материализация записывает то, что содержал источник, а для карты без недавних строк это старое значение. Поэтому представление, показывающее 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  ->  SQLite
ollama 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_servicesresolve_cardexplain_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 | admin

api/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_velocity TTL 2ч, так что спустя пару часов после ./setup.sh каждая карта читается устаревшей, и персонажи перестают различаться. Перезапустите ./setup.sh.

  • Конкурентность SQLite. Запись feast materialize во время чтения uvicorn может приводить к конфликтам блокировок. Локально это нормально; это не production online store.

F
license - not found
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI models to interact with local CSV and Parquet data through MCP tools, providing summarization and analysis capabilities.
    1
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables 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.
  • A
    license
    A
    quality
    D
    maintenance
    Exposes 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.
    10
    2
    MIT

View all related MCP servers

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…

View all MCP Connectors

Latest Blog Posts

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