Skip to content

feat(mcp): add observability to MCP service#41921

Draft
aminghadersohi wants to merge 1 commit into
apache:masterfrom
aminghadersohi:mcp-observability
Draft

feat(mcp): add observability to MCP service#41921
aminghadersohi wants to merge 1 commit into
apache:masterfrom
aminghadersohi:mcp-observability

Conversation

@aminghadersohi

Copy link
Copy Markdown
Contributor

SUMMARY

The MCP service emitted zero StatsD/Prometheus metrics, and its middleware swallowed every exception before any error tracker could see them. This adds:

  • Per-tool metrics: success/error counters and timing in LoggingMiddleware.on_call_tool, plus a user-vs-system error counter split in GlobalErrorHandlerMiddleware._handle_error — mirrors the @statsd_metrics pattern already used in views/base_api.py.
  • Fixed silently-dropped audit rows: LoggingMiddleware skipped its event_logger.log(...) call entirely when has_app_context() was False at the time the middleware finally block 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.
  • Pluggable MCP_ERROR_HOOK config: a vendor-neutral Callable[[Exception, dict], None] | None hook invoked for system-class errors in GlobalErrorHandlerMiddleware._handle_error (primary capture point) and in StructuredContentStripperMiddleware.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.md is updated to note that FlaskIntegration does not cover the FastMCP tool-execution path and shows wiring Sentry via the hook instead.
  • error_type extraction: LoggingMiddleware previously 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.
  • Small fixes: the generic "Internal error" branch used a second-granularity f"err_{int(time.time())}" ID that collides under concurrent failures — replaced with the existing per-call mcp_call_id. Added event_logger.log_context instrumentation to get_chart_type_schema (previously the only uninstrumented tool). Added server-side logger.warning/logger.exception calls next to several return XError(...) sites in get_chart_data, get_tag_info, and query_dataset that 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 for mcp_tool_call/mcp_message were silently dropped whenever the ambient Flask app context had already exited.

After: Per-tool mcp.tool.{name}.{success|error} counters and mcp.tool.{name}.time timing are emitted on every tool call. Audit rows are always written. Operators can wire MCP_ERROR_HOOK to forward system-class errors to an external tracker.

TESTING INSTRUCTIONS

  • Extended tests/unit_tests/mcp_service/test_middleware.py and test_middleware_logging.py with unit tests covering: metrics emission (success/error counters + timing, including the call_tool proxy tool-name resolution case), error_type extraction from structured responses, the MCP_ERROR_HOOK invocation/no-op/exception-swallowing paths in both middlewares, the mcp_call_id-as-error_id fix, and regression tests confirming both LoggingMiddleware.on_call_tool and on_message route 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 / pylint all pass on the changed files.

ADDITIONAL INFORMATION

  • Has associated tests
  • Has screenshots (N/A — backend-only observability change)
  • Changes UI
  • Introduces new feature or API
  • Removes existing feature

… 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

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 3.07692% with 63 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.72%. Comparing base (0ff52d4) to head (9ab2905).
⚠️ Report is 217 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/middleware.py 0.00% 44 Missing ⚠️
superset/mcp_service/chart/tool/get_chart_data.py 0.00% 15 Missing ⚠️
...et/mcp_service/chart/tool/get_chart_type_schema.py 33.33% 2 Missing ⚠️
superset/mcp_service/dataset/tool/query_dataset.py 0.00% 1 Missing ⚠️
superset/mcp_service/tag/tool/get_tag_info.py 0.00% 1 Missing ⚠️
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              
Flag Coverage Δ
hive 38.96% <3.07%> (-0.03%) ⬇️
mysql 57.88% <3.07%> (-0.05%) ⬇️
postgres 57.94% <3.07%> (-0.05%) ⬇️
presto 40.92% <3.07%> (-0.03%) ⬇️
python 59.33% <3.07%> (-0.05%) ⬇️
sqlite 57.55% <3.07%> (-0.05%) ⬇️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants