feat: Add structured audit logging for MCP feature server - #6456
Conversation
OTel is a follow-up is fine, but the core schema and sink abstraction feel like reinventing what opentelemetry-sdk already provides. Also, AuditEvent should carry a trace_id/span_id if OpenTelemetry is active. Also, the fundamental problem with this is that it intercepts at the HTTP transport layer rather than at the MCP protocol layer. It should implement bidirectional correlation - linking a request to its response. |
|
Thanks for the review, @ntkathole. Both points have been addressed.
|
|
Will update the tests accordingly and push the revised commit once the changes are complete. Thanks! |
|
Suggestion: It might help to generate request_id once in the tools/call wrapper and propagate the same value to the internal REST call (e.g. via X-Request-Id and FastApiMCP(headers=[..., "x-request-id"])). Right now the MCP path always mints a new ID while REST reads the header, and most MCP clients don’t send X-Request-Id, so mcp.tools.call and http.request may not correlate in SIEM. Worth keeping jsonrpc_id separate from audit request_id. A test that both events share one request_id per tool call would be great. |
|
@patelchaitany The mcp.tools.call and http.request events currently get independent request_id values, so they won't correlate in SIEM. Will add "x-request-id" to FastApiMCP's forwarded headers and propagate the audit request_id from the wrapper into the internal REST call. jsonrpc_id is already kept separate from request_id. |
|
Now the flow is:
|
|
@SIDDHESH1564 - have you tested this locally by running the Feast feature server and checking audit logs end-to-end? My setup: fastapi-mcp==0.4.0, mcp==1.27.0, MCP enabled with mcp_transport: http and audit_logging enabled (file sink). What I see: REST audit (http.request) works, but MCP tool-call audit does not. On startup I get: Cannot wrap MCP call_tool handler: _request_handlers not found The wrapper never attaches — the handler stays handler, not audited_call_tool — so no mcp.tools.call events are emitted. Unit tests pass because they mock _request_handlers["tools/call"], which doesn’t exist in 0.4.0. Root cause: The PR wraps mcp.server._request_handlers["tools/call"], but in fastapi-mcp 0.4.0 + mcp 1.x the hook is mcp.server.request_handlers[CallToolRequest] with signature async def handler(req: CallToolRequest). Suggested fix: Update _wrap_call_tool_handler() in mcp_server.py to wrap request_handlers[CallToolRequest], read principal from server.request_context, and forward x-request-id / x-feast-auth-type in FastApiMCP(headers=...). Add a unit test using a real FastApiMCP instance (not a mocked _request_handlers). |
|
Thanks for the detailed report, @patelchaitany. you're right about the root cause. I've confirmed and fixed the issue. The wrapper was targeting mcp.server._request_handlers["tools/call"], which was the internal API of an older mcp SDK. |
|
lgtm, @ntkathole |
d54e24d to
3ca8671
Compare
ae45119 to
32bfff0
Compare
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #6456 +/- ##
==========================================
+ Coverage 47.19% 47.42% +0.23%
==========================================
Files 419 421 +2
Lines 51964 52249 +285
Branches 7548 7582 +34
==========================================
+ Hits 24522 24777 +255
- Misses 25689 25708 +19
- Partials 1753 1764 +11
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
|
@ntkathole Please review this PR at your convenience. Thanks! |
8bfb9d1 to
094fe7c
Compare
094fe7c to
a8fefb2
Compare
|
@ntkathole please review this PR. Thanks! |
Signed-off-by: Siddhesh Khairnar <khairnarsiddhesh4057@gmail.com>
…orrelation Signed-off-by: Siddhesh Khairnar <khairnarsiddhesh4057@gmail.com>
…lock Signed-off-by: Siddhesh Khairnar <khairnarsiddhesh4057@gmail.com>
Signed-off-by: Siddhesh Khairnar <khairnarsiddhesh4057@gmail.com>
Signed-off-by: Siddhesh Khairnar <khairnarsiddhesh4057@gmail.com>
Signed-off-by: Siddhesh Khairnar <khairnarsiddhesh4057@gmail.com>
2e6a59c to
f7b3e04
Compare
What this PR does / why we need it:
This PR adds structured audit logging in JSONL format for the Python MCP feature server.
Previously, the MCP integration only logged startup events and errors. Operators did not have a unified audit trail for MCP tool calls, internal REST requests, authentication results, authorization decisions, or request correlation.
This change introduces an
audit_loggingconfiguration section underfeature_serverand adds structured audit events for:X-Request-IdEach audit event follows a stable schema and includes:
event_typetimestamprequest_idprincipal(username,roles, andauth_type)source(ipandtransport)action(MCP tool name and/or HTTP path)resource(type,name, and permitted actions)outcomeduration_msSensitive data is intentionally excluded from audit logs. Tokens, entity rows, feature values, and request payloads are not logged.
Audit logging layers
McpAuditMiddleware/mcpJSON-RPC requestsmcp.tools.call,mcp.requestAuditLoggingMiddleware/get-online-featuresand/pushhttp.requestAuditLoggerhelpersauthn.success,authn.failure,authz.decisionNew files
sdk/python/feast/audit/audit_logger.pyAuditEventmodel.StdoutAuditSink,FileAuditSink, andLoggerAuditSink.AuditLoggerhelper methods for MCP, HTTP, authentication, and authorization events.sdk/python/feast/audit/audit_middleware.pyAuditLoggingMiddlewarefor REST endpoint auditing.McpAuditMiddlewarefor MCP JSON-RPC request auditing.Modified files
sdk/python/feast/infra/feature_servers/base_config.pyAuditLoggingConfigtoBaseFeatureServerConfig.sdk/python/feast/feature_server.pyget_app().Example configuration
Example JSONL event
{ "event_type": "mcp.tools.call", "timestamp": "2026-05-28T12:00:00.000Z", "request_id": "...", "principal": { "username": "jane@co.com", "roles": ["reader"], "auth_type": "oidc" }, "source": { "ip": "10.0.0.1", "transport": "mcp-http" }, "action": { "mcp_tool": "get_online_features", "path": "/mcp" }, "outcome": "success", "duration_ms": 42.0 }Which issue(s) this PR fixes:
Fixes #6452
Checks
git commit -s)Testing Strategy
Added 37 unit tests covering:
AuditEventserialization in JSONL format, including exclusion ofNonevaluesAuditLoggerhelpers:log_mcp_calllog_http_requestlog_authnlog_authzSuppression of successful read events when
log_successful_reads: falseAll supported sinks:
StdoutAuditSinkFileAuditSinkLoggerAuditSinkcreate_audit_logger_from_configfactory behavior and fallback handlingAuditLoggingConfigvalidation and inheritance throughMcpFeatureServerConfigAuditLoggingMiddlewarebehavior:X-Request-IdpropagationMcpAuditMiddlewarebehavior:Misc
audit_loggingis defined onBaseFeatureServerConfig, making it available to both local and MCP feature server types.SecurityManagercontext and supportsno_auth,oidc, andkubernetesauthentication modes.