mcp_feast
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp_feastPull the feature values for card C-4471 and explain why txn_count_1h is high."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp_feast
An MCP server over a Feast feature store, for a card-swipe fraud model. Runs entirely locally: Parquet offline store, SQLite online store, no cloud, no broker.
System design
The four layers
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 --> S2That thick arrow is the whole design. Everything Feast-specific lives below it. The MCP server above it needs no Feast install, no store drivers, and no warehouse credentials — it is an HTTP client and nothing more.
That buys three things. Swapping SQLite for Redis becomes a feature_store.yaml
change the MCP layer never sees. A laptop running the MCP server needs one
reachable URL instead of a network route to production Redis. And the same API
can serve a second consumer — a model server — that was never built here but
would call POST /features/online exactly as the MCP layer does.
Related MCP server: tecton-mcp
What actually runs
Process | Started by | Holds | Port |
Feature API |
| the | 8000 |
MCP server | the host, over stdio | an | — |
Ollama |
| qwen2.5:7b | 11434 |
Host |
| the conversation loop | — |
Only the API imports Feast. Verify it:
python3 -c "import mcp_server.server, sys; print('feast' in sys.modules)" # FalseOne request, end to end
Asking "why would card C-4471 be flagged?" crosses every layer twice:
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 1That second SDK call is the part Feast does not give you for free — see below.
How data reaches the online store
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"| TThe dashed path is the training half of a feature store. It is left out on purpose: it runs a minutes-long query returning millions of rows, which is the wrong shape for a chat tool. That is also why the generator writes no fraud labels.
Why the API is not a passthrough
get_online_features() returns values and nothing else. A bare null cannot
tell you which of four situations you are in — and Feast serves an expired
value without complaint:
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 separates them by recovering the per-entity
event_timestamp through the provider's online_read — the same call
get_online_features makes internally, but one that surfaces the timestamp —
and joining it against the view's TTL.
Three facts the raw SDK will not give you:
Endpoint | Derives |
| per-feature freshness and missing-value reason |
| source → view → consuming services |
| blast radius before a change |
The trap this is built to avoid
Freshness is per entity, not per view. Both are real questions with different answers, and confusing them is the most dangerous mistake available here:
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:3pxMaterialization writes whatever the source holds. For a card with no recent rows that is an old value — so an entity can be hours stale inside a view that materialized seconds ago. Refreshing the view cannot fix it; only a push can.
Question | Tool | Scope |
"Is a pipeline dead?" |
| all entities |
"Is this card current?" |
| one entity |
A small model reliably conflates these. What fixed it was not the system
prompt — it was appending the warning to check_feature_freshness's output.
A model that skips a tool description still reads the result it just acted on.
Tools map to endpoints one to one
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/ and mcp_server/tools/ mirror each other file for file —
catalog, lineage, health, values — so navigation is obvious.
Two ideas that carried the design
Errors are written as instructions. A 404 returns Available: [...], and a
bad entity row names the join keys it needs. Observed repeatedly: a 7B model
gets it wrong, reads the error, and fixes itself on the next step rather than
guessing again.
Guidance rides on output, not just descriptions. Tool descriptions get
skipped; results do not. Both the freshness scope warning and
trigger_materialization's "call check_feature_freshness to confirm" live in
the returned text, and both changed model behaviour when prompt wording alone
had failed.
Quick start
Python 3.11. Feast declares >=3.10 but classifies only 3.10, and its
transitive stack is the usual source of trouble on newer interpreters.
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 /docsBoth scripts honour a PYTHON override if the deps live elsewhere:
PYTHON=/opt/miniconda3/envs/myenv/bin/python3 ./setup.shThe MCP server is launched by the host via .mcp.json, which pins an absolute
interpreter path for the reason in Troubleshooting below. ./run_mcp.sh runs it
by hand for debugging.
Troubleshooting: wrong interpreter
Two symptoms, one cause -- a different Python than the one holding the deps:
ModuleNotFoundError: No module named 'feast'
ImportError: cannot import name 'MCPServer' from 'mcp.server'The second is the sneakier one: mcp 1.x imports fine but exposes
mcp.server.fastmcp.FastMCP, not the 2.x mcp.server.MCPServer this project
uses. A shell prompt showing an active conda env is not proof -- check PATH:
which python3 && python3 -V
echo $PATH | tr ":" "\n" | head -3If a framework or system Python sits ahead of your env, every python3 call
escapes the env regardless of what the prompt says. Diagnose properly with:
python3 preflight.pyIt imports the exact symbol each part of the code needs -- not just the module --
so a wrong-major dependency is caught by name, and it warns when uvicorn or
feast on your PATH belong to a different environment.
Every entry point takes a PYTHON override, so you never have to fight 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 toolsTwo rules avoid this entirely:
Start the API with
./run_api.sh, orpython3 -m uvicorn api.main:app. Never bareuvicorn api.main:app— that resolves uvicorn from PATH, which may belong to a different Python than the one holding Feast, and the failure surfaces forty frames deep in an import chain.Keep
.mcp.json'scommandan absolute interpreter path."python3"there resolves against whatever PATH the host process happened to have.
What is in the registry
Entities — card (card_id), customer (customer_id)
Feature views
View | Entity | Kind | Features | TTL |
| card | push |
| 2h |
| customer | batch |
| 7d |
Feature service — fraud_model_v2, binding all 7 features.
The 2h / 7d TTL split is deliberate: it makes the freshness tooling produce real answers instead of a permanent all-green.
Mock data
data_gen/generate_swipes.py writes 15,000 customer snapshots (500 customers ×
30 days) and 14,394 velocity rows (600 cards × 24 hours, minus 6 dropped to
create the stale case). Everything is anchored to run time, so regenerating
always produces data that materializes cleanly.
Six personas are pinned so demos are deterministic:
Card / Customer | Setup | Demonstrates |
| 7 swipes/hr, $2,140 vs $58.20 average, chargebacks null | The fraud case, and a null feature |
| Everything median | Control |
| Newest velocity row is 6h old | Staleness past a 2h TTL |
| Never generated | Unknown entity |
| Profile but no card | Partial coverage |
| 4 chargebacks, normal velocity | Risk that isn't velocity |
The API
Group | Endpoints |
Catalog |
|
Lineage |
|
Health |
|
Values |
|
Interactive docs at http://localhost:8000/docs.
The API is not a passthrough. It does three things the raw SDK does not: joins registry metadata against online-store timestamps to compute freshness, walks source → view → service to compute lineage, and flattens Feast's proto shapes into plain named objects.
MCP tools
12 read-only tools, plus 2 write tools that only register when writes are enabled.
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 ⚠
Two kinds of freshness
These answer different questions, and confusing them is the most dangerous mistake available here:
Tool | Answers | Scope |
| "Is a pipeline dead?" | All entities, view level |
| "Is this card's data current?" | One entity |
An individual entity can be six hours stale inside a view that materialized seconds ago -- materialization writes whatever the source held, and for a card with no recent rows that is an old value. So a view showing OK proves nothing about any particular card.
A small model reliably conflates the two and answers "current enough to trust"
from view-level metadata. Three layers guard against it: the server
INSTRUCTIONS, the check_feature_freshness tool description, and a note
appended to that tool's output -- the last being the one that actually
worked, since a model that skipped the description still reads the result it
acted on.
Why explain_features_for_entity exists
get_online_features returns bare values. A bare null cannot distinguish four
different situations, and Feast serves an expired value without complaint:
a genuine zero
a view that was never materialized
an entity that does not exist
a value that is past its TTL
explain_features_for_entity separates them, using the per-entity event_ts
recovered from the online store. That is why it is the preferred retrieval tool.
FEAST_MCP_READONLY
Read by both processes. When true (the default), the MCP server does not
register push_swipe or trigger_materialization at all — a tool the model
cannot see is one it will not try — and the API independently returns 403 on
those routes, so curling it directly is also refused.
Local LLM host
host.py is a real MCP host driven by a local open-source model -- no API key,
nothing hosted. The model decides which tools to call; mcp_cli.py only calls
tools you name.
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 # interactiveThe system prompt is not written in host.py. It comes from the MCP server's
own instructions, returned during initialize() -- the server tells the model
how its tools are meant to be used, and the host passes that through. Changing
INSTRUCTIONS in mcp_server/server.py changes how the model behaves, with no
edit to the host.
Model choice matters: it needs tool-calling support. qwen2.5:7b works;
Gemma has no tool template in Ollama and will not.
Host guardrails
A 7B model is an unreliable planner, so the loop defends against three failures it actually exhibits:
Failure | Guardrail |
Repeats a call it already made, sometimes until the step limit | Results cached by (tool, args); a repeat is served from cache with a "you already did this" note instead of a second round trip |
Narrates its next call in prose ( | Detected, nudged once to emit the call rather than describe it (max 2) |
Wanders past the step budget with no answer | On the last step -- or after 3 repeats -- tools are withdrawn, so it must answer from what it gathered |
Each prints a HOST | line, so you can see the loop intervening.
Even so, expect wandering on open-ended prompts. Restricting the toolset is the practical fix:
python3 host.py --tools resolve_card,explain_features_for_entity,check_feature_freshness \
"Why would card C-4471 be flagged?"Watching MCP call the API
mcp_cli.py speaks the same stdio protocol the host does, so the MCP -> API
chain is observable from a shell:
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 prints the endpoint each tool hits:
http | HTTP Request: GET http://localhost:8000/cards/C-4471 "HTTP/1.1 200 OK"
C-4471 is owned by CU-8842Try it
Debug a decline
"Why would card C-4471 get declined?"
list_feature_services → resolve_card → explain_features_for_entity.
Returns 7 swipes in the last hour totalling $2,140 against a $58.20 average,
with chargeback history explicitly unavailable rather than assumed zero.
Catch a dead pipeline
"Is anything stale for card C-7788?"
explain_features_for_entity flags card_velocity as 6h46m old against a 2h
TTL. The values still come back — nothing blocks the read — which is exactly
why the flag is needed.
Push round trip (needs writes enabled)
"Record a swipe on C-7788, then check it again."
push_swipe → the same card reads fresh. trigger_materialization on
card_velocity resets it to the 6h-old batch row, so the demo is repeatable.
Layout
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/ and mcp_server/tools/ mirror each other one-to-one.
Notes
chargebacks_lifetimeisFloat64, notInt64. The feature is genuinely nullable, and a null integer has no representation in the Parquet → pandas → Feast path.Use
feast materialize, notmaterialize-incremental, for setup. Incremental uses the view's TTL as its start bound, so with a 2h TTL it would skip the 6h-old row that makes the stale persona work.Registry caching.
cache_ttl_seconds: 30infeature_store.yamlmeans afeast applyin another shell shows up within 30s.POST /admin/reloadforces it immediately, and also reopens the online store — which a bare registry refresh does not do.The mock data is time-anchored.
card_velocityhas a 2h TTL, so more than a couple of hours after./setup.shevery card reads stale and the personas stop being distinguishable. Re-run./setup.sh.SQLite concurrency.
feast materializewriting while uvicorn reads can hit lock contention. Fine locally; it is not a 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