Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 10 additions & 14 deletions examples/agent_feature_store/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,12 @@ async def _discover_feast_tools() -> dict[str, str]:


def _parse_online_features(data: dict) -> dict[str, Any]:
"""Parse the response from get-online-features into a flat dict."""
results = data.get("results", [])
feature_names = data.get("metadata", {}).get("feature_names", [])
features: dict[str, Any] = {}
for i, name in enumerate(feature_names):
values = results[i].get("values", [])
val = values[0] if values else None
"""Parse the MCP to_dict() response into a flat dict of first-row values."""
features = {}
for key, values in data.items():
val = values[0] if isinstance(values, list) and values else values
if val is not None:
features[name] = val
features[key] = val
return features


Expand Down Expand Up @@ -214,15 +211,14 @@ async def tool_search_knowledge_base(query: str) -> list[dict[str, Any]]:
"api_version": 2,
},
)
results = data.get("results", [])
feature_names = data.get("metadata", {}).get("feature_names", [])

num_docs = len(results[0]["values"]) if results else 0
first_col = next(iter(data.values()), [])
num_docs = len(first_col) if isinstance(first_col, list) else 0
docs = []
for doc_idx in range(num_docs):
doc = {}
for feat_idx, name in enumerate(feature_names):
doc[name] = results[feat_idx]["values"][doc_idx]
for key, values in data.items():
if isinstance(values, list) and doc_idx < len(values):
doc[key] = values[doc_idx]
if doc.get("title"):
docs.append(doc)
return docs
Expand Down
8 changes: 7 additions & 1 deletion sdk/python/feast/feature_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,14 @@ async def lifespan(app: FastAPI):

await store.initialize()
async_refresh()

mcp_sm = getattr(app.state, "mcp_session_manager", None)
try:
yield
if mcp_sm:
async with mcp_sm.run():
yield
else:
yield
finally:
stop_refresh()
if offline_batcher is not None:
Expand Down
3 changes: 2 additions & 1 deletion sdk/python/feast/infra/mcp_servers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# MCP (Model Context Protocol) server implementations for Feast

from .mcp_config import McpFeatureServerConfig
from .mcp_server import add_mcp_support_to_app
from .mcp_server import add_mcp_support_to_app, create_mcp_server

__all__ = [
"McpFeatureServerConfig",
"add_mcp_support_to_app",
"create_mcp_server",
]
7 changes: 2 additions & 5 deletions sdk/python/feast/infra/mcp_servers/mcp_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@
class McpFeatureServerConfig(BaseFeatureServerConfig):
"""MCP (Model Context Protocol) Feature Server configuration."""

# Feature server type selector
type: Literal["mcp"] = "mcp"

# Enable MCP server support - defaults to False as requested
mcp_enabled: StrictBool = False

# MCP server name for identification
mcp_server_name: StrictStr = "feast-mcp-server"

# MCP server version
mcp_server_version: StrictStr = "1.0.0"

mcp_transport: Literal["sse", "http"] = "sse"

# The endpoint definition for transformation_service (inherited from base)
mcp_base_path: StrictStr = "/mcp"

transformation_service_endpoint: StrictStr = "localhost:6566"
Loading
Loading