-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: Support HTTP in MCP #6109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3c8e090
91a5692
7bb1d19
2f2d145
1d0d820
c33131b
36f1bdf
044e70f
0c897c6
76224aa
fa66c14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # MCP Feature Server | ||
|
|
||
| ## Overview | ||
|
|
||
| Feast can expose the Python Feature Server as an MCP (Model Context Protocol) server using `fastapi_mcp`. When enabled, MCP clients can discover and call Feast tools such as online feature retrieval. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| pip install feast[mcp] | ||
| ``` | ||
|
|
||
| ## Configuration | ||
|
|
||
| Add an MCP `feature_server` block to your `feature_store.yaml`: | ||
|
|
||
| ```yaml | ||
| feature_server: | ||
| type: mcp | ||
| enabled: true | ||
| mcp_enabled: true | ||
| mcp_transport: http | ||
| mcp_server_name: "feast-feature-store" | ||
| mcp_server_version: "1.0.0" | ||
| ``` | ||
|
|
||
| ### mcp_transport | ||
|
|
||
| `mcp_transport` controls how MCP is mounted into the Feature Server: | ||
|
|
||
| - `sse`: SSE-based transport. This is the default for backward compatibility. | ||
| - `http`: Streamable HTTP transport. This is recommended for improved compatibility with some MCP clients. | ||
|
|
||
| If `mcp_transport: http` is configured but your installed `fastapi_mcp` version does not support Streamable HTTP mounting, Feast will fail fast with an error asking you to upgrade `fastapi_mcp` (or reinstall `feast[mcp]`). | ||
|
|
||
| ## Endpoints | ||
|
|
||
| MCP is mounted at: | ||
|
|
||
| - `/mcp` | ||
|
|
||
| ## Connecting an MCP client | ||
|
|
||
| Use your MCP client’s “HTTP” configuration and point it to the Feature Server base URL. For example, if your Feature Server runs at `http://localhost:6566`, use: | ||
|
|
||
| - `http://localhost:6566/mcp` | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| - If you see a deprecation warning about `mount()` at runtime, upgrade `fastapi_mcp` and use `mcp_transport: http` or `mcp_transport: sse`. | ||
| - If your MCP client has intermittent connectivity issues with `mcp_transport: sse`, switch to `mcp_transport: http`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ | |
| """ | ||
|
|
||
| import logging | ||
| from typing import Optional | ||
| from typing import Literal, Optional | ||
|
|
||
| from feast.feature_store import FeatureStore | ||
|
|
||
|
|
@@ -26,6 +26,10 @@ | |
| FastApiMCP = None | ||
|
|
||
|
|
||
| class McpTransportNotSupportedError(RuntimeError): | ||
| pass | ||
|
|
||
|
|
||
| def add_mcp_support_to_app(app, store: FeatureStore, config) -> Optional["FastApiMCP"]: | ||
| """Add MCP support to the FastAPI app if enabled in configuration.""" | ||
| if not MCP_AVAILABLE: | ||
|
|
@@ -40,8 +44,29 @@ def add_mcp_support_to_app(app, store: FeatureStore, config) -> Optional["FastAp | |
| description="Feast Feature Store MCP Server - Access feature store data and operations through MCP", | ||
| ) | ||
|
|
||
| # Mount the MCP server to the FastAPI app | ||
| mcp.mount() | ||
| transport: Literal["sse", "http"] = getattr(config, "mcp_transport", "sse") | ||
| if transport == "http": | ||
| mount_http = getattr(mcp, "mount_http", None) | ||
| if mount_http is None: | ||
| raise McpTransportNotSupportedError( | ||
| "mcp_transport=http requires fastapi_mcp with FastApiMCP.mount_http(). " | ||
| "Upgrade fastapi_mcp (or install feast[mcp]) to a newer version." | ||
| ) | ||
| mount_http() | ||
| elif transport == "sse": | ||
| mount_sse = getattr(mcp, "mount_sse", None) | ||
| if mount_sse is not None: | ||
| mount_sse() | ||
| else: | ||
| logger.warning( | ||
| "transport sse not supported, fallback to the deprecated mount()." | ||
| ) | ||
| mcp.mount() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Silent fallback to the deprecated |
||
| else: | ||
| # Code should not reach here | ||
| raise McpTransportNotSupportedError( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This branch is unreachable —
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Anarion-zuo u didn't solve it btw :) |
||
| f"Unsupported mcp_transport={transport!r}. Expected 'sse' or 'http'." | ||
| ) | ||
|
|
||
| logger.info( | ||
| "MCP support has been enabled for the Feast feature server at /mcp endpoint" | ||
|
|
@@ -53,6 +78,8 @@ def add_mcp_support_to_app(app, store: FeatureStore, config) -> Optional["FastAp | |
|
|
||
| return mcp | ||
|
|
||
| except McpTransportNotSupportedError: | ||
| raise | ||
| except Exception as e: | ||
| logger.error(f"Failed to initialize MCP integration: {e}") | ||
| logger.error(f"Failed to initialize MCP integration: {e}", exc_info=True) | ||
| return None | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the server crashes immediately after backgrounding, this loop burns 60 seconds before failing. Add
kill -0 "$SERVER_PID" || { echo "server died"; exit 1; }at the top of the loop body.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc: @franciscojavierarceo