Repository navigation
Description
Is your feature request related to a problem? Please describe.
Feast can expose the Python feature server as an MCP server (feature_server.type: mcp, mcp_enabled: true, endpoint /mcp) via fastapi_mcp. Operators and platform teams need a clear audit trail for agent/MCP access: who invoked which tool, what resource was accessed, whether authentication and authorization succeeded or failed, and a correlation ID that links MCP calls to internal REST requests.
Today, MCP integration only logs startup/errors (sdk/python/feast/infra/mcp_servers/mcp_server.py). That is not enough for security and operations use cases:
| Mechanism | What it does | Gap for MCP audit |
|---|---|---|
feature_logging |
Logs served feature values to the offline store | Not a security audit log; not wired on the Python FastAPI MCP server |
| Uvicorn access logs | HTTP path/status (off by default) | No principal, no MCP tool name |
| RBAC logs | Unstructured permission messages in app logs | No MCP correlation; often DEBUG-only for allows |
With auth.type: no_auth: MCP tool calls are effectively anonymous — no principal in logs.
With auth.type: oidc / kubernetes: RBAC applies on secured REST routes when the MCP client forwards Authorization (default in fastapi_mcp), but there is still no structured audit stream for MCP tools/call, successful reads, or request correlation. The /mcp transport itself is not audited.
Registry MCP (registry.mcp.enabled: true) has the same gap.
Describe the solution you'd like
Add structured audit logging (JSONL) for the Python MCP feature server, separate from feature_logging:
- Config under
feature_server(e.g.audit_logging.enabled,sink,file_path,require_auth_for_mcp,log_successful_reads). - Audit events with stable schema:
event_type, UTCtimestamp,request_id,principal,source(IP, transport),action(MCP tool / HTTP path),resource,outcome,duration_ms. - Two emission layers:
- MCP middleware on
/mcp(mcp.tools.call, session events). - REST + RBAC hooks (
authn.*,authz.decision,http.request) on secured endpoints.
- MCP middleware on
- Header propagation via
FastApiMCP(headers=[...])so MCP and REST events sharerequest_id(and optionally tool name). - No sensitive payloads in audit logs (no tokens, entity rows, or feature values).
Optional follow-ups: registry MCP parity, OTEL sink, operator CRD fields.
Example config:
feature_server:
type: mcp
mcp_enabled: true
audit_logging:
enabled: true
sink: stdoutExample event (JSONL):
{
"event_type": "mcp.tools.call",
"timestamp": "2026-05-28T12:00:00.000Z",
"request_id": "…",
"principal": {
"username": "[email protected]",
"roles": ["feast-reader", "ml-engineer"],
"auth_type": "oidc"
},
"source": { "ip": "10.0.0.1", "transport": "mcp-http" },
"action": { "mcp_tool": "get_online_features", "path": "/get-online-features" },
"resource": { "type": "feature_service", "name": "driver_activity_v1", "actions": ["READ_ONLINE"] },
"outcome": "success",
"duration_ms": 42
}
Activity
HarperZ9 commentedon Jun 28, 2026
This audit stream becomes much more useful if each
mcp.tools.callevent is treated as a compact execution receipt, not only as an access log row.I would add a few fields to the proposed schema:
decision_id: stable join key shared by MCP middleware, RBAC decision, downstream REST request, and outcome eventpolicy_ref/policy_digest: the ruleset or RBAC snapshot that allowed, denied, or required approvalresource_digest: digest of the feature service/view identity and request shape, without feature values or entity rowsoutcome_state:success | denied | error | timeout | redacted | unverifiableprev_event_hash: optional per-session hash chain for tamper-evident exportTwo fixture cases would catch most regressions:
get_online_featuressuccessfully: MCP event, authz decision, REST request, and outcome all sharedecision_id; no feature values are logged.That keeps the audit trail useful for security review without turning it into a second feature log or leaking the data Feast is serving.