feat(mcp): add observability to MCP service#41921
Draft
aminghadersohi wants to merge 1 commit into
Draft
Conversation
… error hook The MCP service emitted zero StatsD metrics and its middleware swallowed every exception before any error tracker could see them. This adds per-tool success/error counters and timing in LoggingMiddleware and GlobalErrorHandlerMiddleware (mirroring the @statsd_metrics pattern in base_api.py), fixes has_app_context() silently dropping DB audit rows by wrapping event_logger calls in the existing app-context manager, adds a vendor-neutral MCP_ERROR_HOOK config for wiring an external error tracker, surfaces the previously-discarded error_type from structured error responses, and adds server-side logging at several ops-invisible error return sites.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #41921 +/- ##
==========================================
- Coverage 64.74% 64.72% -0.03%
==========================================
Files 2738 2738
Lines 152950 153009 +59
Branches 35055 35059 +4
==========================================
- Hits 99032 99028 -4
- Misses 52018 52081 +63
Partials 1900 1900
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SUMMARY
The MCP service emitted zero StatsD/Prometheus metrics, and its middleware swallowed every exception before any error tracker could see them. This adds:
LoggingMiddleware.on_call_tool, plus a user-vs-system error counter split inGlobalErrorHandlerMiddleware._handle_error— mirrors the@statsd_metricspattern already used inviews/base_api.py.LoggingMiddlewareskipped itsevent_logger.log(...)call entirely whenhas_app_context()wasFalseat the time the middlewarefinallyblock ran (the per-tool app context had already exited). Both call sites now wrap the log call in the existing_get_app_context_manager()helper instead of skipping it.MCP_ERROR_HOOKconfig: a vendor-neutralCallable[[Exception, dict], None] | Nonehook invoked for system-class errors inGlobalErrorHandlerMiddleware._handle_error(primary capture point) and inStructuredContentStripperMiddleware.on_call_tool's last-resort except block (for exceptions that bypass the primary handler entirely). This lets operators wire an external error tracker (e.g. Sentry) without the OSS repo taking a hard dependency on any vendor SDK.PRODUCTION.mdis updated to note thatFlaskIntegrationdoes not cover the FastMCP tool-execution path and shows wiring Sentry via the hook instead.error_typeextraction:LoggingMiddlewarepreviously substring-sniffed"error_type"in serialized tool responses only to decide success/failure, discarding the actual value. It's now parsed and included in the log line, curated payload, and metric tag.f"err_{int(time.time())}"ID that collides under concurrent failures — replaced with the existing per-callmcp_call_id. Addedevent_logger.log_contextinstrumentation toget_chart_type_schema(previously the only uninstrumented tool). Added server-sidelogger.warning/logger.exceptioncalls next to severalreturn XError(...)sites inget_chart_data,get_tag_info, andquery_datasetthat only logged to the MCP client (ctx.error/ctx.warning, which never reaches server logs) or didn't log at all.BEFORE/AFTER
Before:
grep -r "stats_logger\|statsd\|incr(\|timing(" superset/mcp_service/→ no hits. No error tracker could ever see an MCP tool failure, and DB audit rows formcp_tool_call/mcp_messagewere silently dropped whenever the ambient Flask app context had already exited.After: Per-tool
mcp.tool.{name}.{success|error}counters andmcp.tool.{name}.timetiming are emitted on every tool call. Audit rows are always written. Operators can wireMCP_ERROR_HOOKto forward system-class errors to an external tracker.TESTING INSTRUCTIONS
tests/unit_tests/mcp_service/test_middleware.pyandtest_middleware_logging.pywith unit tests covering: metrics emission (success/error counters + timing, including thecall_toolproxy tool-name resolution case),error_typeextraction from structured responses, theMCP_ERROR_HOOKinvocation/no-op/exception-swallowing paths in both middlewares, themcp_call_id-as-error_idfix, and regression tests confirming bothLoggingMiddleware.on_call_toolandon_messageroute their audit-row log call through_get_app_context_manager()rather than conditionally skipping it.pytest tests/unit_tests/mcp_service/— 2691 passed.ruff check/ruff format --check/mypy/pylintall pass on the changed files.ADDITIONAL INFORMATION