This guide explains how to run the standalone Feast MCP server (feast mcp) against a local feature store, and how to call its tools from an MCP client.
Unlike the MCP feature store example, which enables MCP inside the feature server process, feast mcp runs as a separate server. It holds no registry or online store of its own, and instead proxies to a running Feast deployment, exposing two tool namespaces:
| Namespace | Proxies | Mounted when |
|---|---|---|
features_* |
The feature server (feast serve) |
features.url is set |
registry_* |
The REST registry server (feast serve_registry --rest-api) |
registry.url is set |
See the Standalone MCP server reference for the full list of options.
- feast_mcp.yaml: MCP server configuration, covering transport, upstream URLs, authentication, and logging.
- mcp_client_demo.py: A minimal MCP client that lists the available tools and calls one from each namespace.
- kubernetes/featurestore-mcpserver.yaml: The same deployment on Kubernetes, using the Feast Operator.
- Python 3.10+ environment
- Feast with the MCP server extra:
pip install 'feast[mcp-server]'.
The MCP server needs a running Feast deployment to proxy to, so start by creating a local feature store. From this directory:
feast init -t local feast_demo-
Apply the feature definitions to register the
driver_hourly_statsfeature view that the demo client queries:cd feast_demo/feature_repo && feast apply && cd ../..
-
Load the online store, so that the feature retrieval returns actual values rather than nulls:
cd feast_demo/feature_repo && feast materialize-incremental "$(date -u +%Y-%m-%dT%H:%M:%S)" && cd ../..
Note:
feast initnames the project after the directory, so the project here isfeast_demo, notmy_project.
Both upstream servers run from the feature repository directory. In two separate terminals, from feast_demo/feature_repo:
-
Start the feature server:
feast serve --host 0.0.0.0 --port 6566
-
Start the registry server. The registry serves gRPC by default, so
--rest-apiis required here because the MCP server talks to the registry over REST:feast serve_registry --rest-api --rest-port 6572
In a third terminal, from this directory:
feast mcp --config feast_mcp.yaml-
The provided feast_mcp.yaml sets
transport: http, so the MCP endpoint is served athttp://localhost:8000/mcp. The same configuration can be passed entirely on the command line:feast mcp --feast-url http://localhost:6566 --registry-url http://localhost:6572 --transport http --port 8000
-
Verify that the server is running:
curl -s http://localhost:8000/health
Example output:
{"status":"healthy","service":"mcp-server"}
Note: Settings are resolved in the order CLI options, environment variables, config file, defaults. Because environment variables outrank the file, an exported
FEAST_MCP_TRANSPORTwill overrideserver.transportinfeast_mcp.yaml.
The mcp_client_demo.py script connects over HTTP, lists the tools that the server mounted, and then calls one tool from each namespace:
python mcp_client_demo.pyExample output:
22 tools mounted:
- features_get_online_features
- features_get_vector_store
- features_health
- features_list_vector_stores
- features_materialize
- features_materialize_incremental
- features_push
- features_search
- features_vector_store_search
- registry_get_data_source
- registry_get_entity
- registry_get_feature_service
- registry_get_feature_view
- registry_get_lineage
- registry_get_project
- registry_list_data_sources
- registry_list_entities
- registry_list_feature_services
- registry_list_feature_views
- registry_list_features
- registry_list_projects
- registry_search_registry
registry_list_projects: ['feast_demo']
registry_list_feature_views (project=feast_demo): ['driver_hourly_stats', 'driver_hourly_stats_fresh', 'transformed_conv_rate', 'transformed_conv_rate_fresh']
features_get_online_features:
{'results': [{'values': [1001, 1002], 'statuses': ['PRESENT', 'PRESENT'], ...}, ...], 'metadata': {'feature_names': ['driver_id', 'acc_rate', 'conv_rate']}}
- The
PRESENTstatuses confirm that the materialization step in stage 1 succeeded. If it is skipped, the statuses come back asNOT_FOUNDwith null values, while the registry tools continue to work. - If only one namespace appears, the other upstream URL was not configured. Each sub-server is mounted only when its URL is set.
For an HTTP transport, point the MCP client at http://localhost:8000/mcp.
For a stdio transport, let the client spawn the process instead. Since feast mcp reads feast_mcp.yaml from the working directory, pass --config with an absolute path:
{
"mcpServers": {
"feast": {
"command": "feast",
"args": ["mcp", "--config", "/absolute/path/to/feast_mcp.yaml", "--transport", "stdio"]
}
}
}-
Stop the three servers, then remove the generated feature repository:
rm -rf feast_demo
This example runs with auth.mode: passthrough, the default, which accepts connections without a token. The MCP server does not enforce its own RBAC. A token supplied by the client is forwarded to Feast, which applies its own permission model.
To give IDE clients a browser login flow, switch to OIDC. This is usually configured against the same provider already set as auth.oidc_discovery_url in feature_store.yaml:
feast mcp --config feast_mcp.yaml --auth-mode oidc --oidc-discovery-url https://keycloak.example.com/realms/feast/.well-known/openid-configuration --oidc-client-id feast-mcp --base-url http://localhost:8000Note: Run a single replica with
oidc. The OAuth state store is per-node and on disk, so a callback routed to a replica that did not handle the authorize request will fail.
This example uses --auth-mode passthrough: the client sends its Service Account or user token as a bearer token, the MCP server forwards it unchanged, and the upstream Feast servers validate it using the TokenReview API. For an in-cluster MCP deployment, --auth-mode kubernetes also validates the token at the MCP server before forwarding it. See the Kubernetes authentication requirements for the required dependencies and Service Account permissions.
The demo client sends whatever is in the MCP_TOKEN environment variable as its bearer token:
MCP_TOKEN=$(kubectl create token my-service-account) python mcp_client_demo.pyThis matters when running on a cluster, as described in the next section.
The kubernetes/featurestore-mcpserver.yaml manifest deploys the same setup with the Feast Operator. Setting spec.services.mcpServer adds a feast mcp container to the FeatureStore deployment, exposed on its own Service on port 8100.
The Feast Operator enables Kubernetes authentication by default, so the online store and registry in this manifest both require a bearer token. Because the MCP server only relays the caller's token and never supplies one of its own, a client that connects without a token will see 401 Unauthorized errors raised from upstream.
-
Apply the manifest:
kubectl apply -f kubernetes/featurestore-mcpserver.yaml
-
Wait for the MCP server to become ready:
kubectl wait --for=condition=McpServer featurestore/sample-mcpserver --timeout=300s -
Forward the Service port and run the same demo client against it:
kubectl port-forward svc/feast-sample-mcpserver-mcpserver 8000:8100
python mcp_client_demo.py
Since Kubernetes auth is on, pass a token so that the demo client can reach the upstream servers:
MCP_TOKEN=$(kubectl create token default) python mcp_client_demo.pyAlternatively, for a development cluster, set
spec.authz.noAuth: trueon the FeatureStore to disable authentication entirely. -
To remove the deployment:
kubectl delete -f kubernetes/featurestore-mcpserver.yaml
Two operator-specific behaviors are worth noting:
- The operator sets
--hostand--portso that they match the generated Service. Theserver.transportvalue in the ConfigMap is still honored, butserver.hostandserver.portare not. - A CEL validation rule enforces that at least one upstream is available: either
onlineStoreis not disabled (omitting it is fine — the operator defaults an online feature server), orregistry.local.server.restAPIistrue.