Skip to content

feat(mcp): allow skipping MCP host-tool registration in non-MCP processes - #43842

Open
eschutho wants to merge 11 commits into
apache:masterfrom
eschutho:fix/gate-mcp-host-tools-off-workers
Open

eschutho wants to merge 11 commits into
apache:masterfrom
eschutho:fix/gate-mcp-host-tools-off-workers

Conversation

@eschutho

@eschutho eschutho commented Sep 4, 2026 •

Copy link
Copy Markdown
Member

SUMMARY

init_core_dependencies() calls the MCP host-tool setup in every process that
builds the Flask app — including Celery workers, which never serve MCP. Registering the MCP host
tools imports the MCP service app (from superset.mcp_service import app), which pulls in every
host-tool module and carries a real per-process memory cost. On high-concurrency prefork worker
pools this import can push workers over their memory limit and OOM them at boot.

This adds a CORE_MCP_HOST_TOOLS_ENABLED config flag (default True, so no behavior change)
and gates the host-tool registration on it. Deployments can set it to False for processes that
never serve MCP (e.g. Celery workers) to avoid importing the MCP service app where it is unused.

MCP decorators are always registered, independent of this flag. Registering the host tools was
originally bundled with swapping the abstract @tool/@prompt decorators
(superset_core.mcp.decorators) for their concrete implementations. Those abstract decorators
raise NotImplementedError, so gating the whole thing would break any extension that applies
@tool/@prompt at import time in a flag-off worker. The decorator swap is therefore split out
and runs unconditionally; only the host-tool import is gated.

CHANGES

  • superset/config.py: new CORE_MCP_HOST_TOOLS_ENABLED = True flag; docstring notes it gates
    host-tool registration only (decorators always register).
  • superset/core/mcp/core_mcp_injection.py: split initialize_core_mcp_dependencies() into
    initialize_core_mcp_decorators() (decorator swap, always run) and
    initialize_core_mcp_host_tools() (imports superset.mcp_service.app, gated).
    initialize_core_mcp_dependencies() is kept as a thin wrapper calling both.
  • superset/initialization/__init__.py: init_core_dependencies() always calls
    initialize_core_mcp_decorators() and gates initialize_core_mcp_host_tools() on the flag.
  • tests/unit_tests/initialization_test.py: regression tests for the flag on / off / default paths.

Decisions made that were not in the original instructions

This PR gates a startup/behavior path (what happens when the flag is off), so calling out the
choices made while resolving review feedback:

  • Decorator swap is ungated (behavior change on the flag-off path). Rather than only documenting
    the constraint, initialize_core_mcp_decorators() runs even when CORE_MCP_HOST_TOOLS_ENABLED=False,
    so @tool/@prompt extensions keep working in every process. This means a flag-off worker still
    imports the decorator support (fastmcp + mcp); measured to add ~0 MB beyond what the decorator
    swap requires anyway, and far below the gated superset.mcp_service.app import. This is the
    intended, observable trade-off: correctness for extensions over shaving the (small, shared) decorator
    import.
  • Host-tool import failures stay observable. initialize_core_mcp_host_tools() keeps the existing
    try/except that logs failures via logger.error(...) — the gated/degraded path is not silently
    swallowed.
  • The core_mcp_injection module import stays before the flag check (it can't move — the always-on
    decorator swap lives there), which is fine: its marginal cost is ~0 MB once decorators run.

TESTING INSTRUCTIONS

  • Default (flag True): the web app and standalone MCP service register decorators and host tools
    exactly as before.
  • Set CORE_MCP_HOST_TOOLS_ENABLED = False in a worker's config: the worker registers the MCP
    decorators (so @tool/@prompt extensions still import cleanly) but skips importing the MCP
    service app / host-tool stack; web / MCP processes are unaffected.
  • pytest tests/unit_tests/initialization_test.py -k init_core_dependencies.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
  • Migration is atomic, supports rollback & is backwards-compatible
  • Confirm DB migration upgrade and downgrade tested
  • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

…sses

init_core_dependencies() calls initialize_core_mcp_dependencies() in every process that
builds the app, including Celery workers that never serve MCP. Importing the MCP service
app to register host tools has a real per-process memory cost that can OOM high-concurrency
prefork worker pools on boot.

Add a CORE_MCP_HOST_TOOLS_ENABLED config flag (default True, no behavior change) and gate
the call on it, so deployments can turn host-tool registration off for processes that do
not serve MCP.

Signed-off-by: Elizabeth Thompson <elizabeth@preset.io>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bito-code-review

bito-code-review Bot commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #31e528

Actionable Suggestions - 0
Additional Suggestions - 2
  • superset/config.py - 2
    • Undocumented config option · Line 546-546
      This new config option is user-facing (deployments can set it in superset_config.py) but isn't listed in the MCP configuration reference in `docs/admin_docs/configuration/mcp-server.mdx`, which documents all other MCP settings. Per AGENTS.md, docs should be updated for user-facing changes. Consider adding a row for `CORE_MCP_HOST_TOOLS_ENABLED`.
    • Naming inconsistency · Line 546-546
      All other MCP settings use the `MCP_` prefix (`MCP_AUTH_ENABLED`, `MCP_RBAC_ENABLED`, etc.) and live in `mcp_service/mcp_config.py`. This new flag uses a `CORE_` prefix in `config.py`, which is inconsistent with the related API family and may confuse operators searching for MCP options. Align the name or note the intentional distinction.
Review Details
  • Files reviewed - 2 · Commit Range: fedcb64..fedcb64
    • superset/config.py
    • superset/initialization/__init__.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Comment thread superset/initialization/__init__.py Outdated
Comment on lines +565 to +566
if self.config.get("CORE_MCP_HOST_TOOLS_ENABLED", True):
initialize_core_mcp_dependencies()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: With this flag false, the abstract tool and prompt decorators remain active, so extensions using them raise NotImplementedError and are skipped during extension loading. [api mismatch]

Assessment: 🟠 Major · 🔁 Occurrence: Sometimes

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/initialization/__init__.py
**Line:** 565:566
**Comment:**
	*Api Mismatch: With this flag false, the abstract `tool` and `prompt` decorators remain active, so extensions using them raise `NotImplementedError` and are skipped during extension loading.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed—the disabled path leaves the Core decorators as raising stubs, so an extension entrypoint using @tool or @prompt can fail before its non-MCP contributions, including Celery tasks, are registered. Should decorator replacement be kept independent of host-tool registration?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Decorator replacement should be independent of host-tool registration.

CORE_MCP_HOST_TOOLS_ENABLED=False should prevent importing/registering the MCP host-tool stack, but it must still install the functional @tool and @prompt decorators before extensions load. Otherwise an extension that declares MCP metadata can raise NotImplementedError and prevent unrelated contributions, such as Celery tasks, from being registered.

A minimal structure would be:

initialize_core_mcp_decorators()

if self.config.get("CORE_MCP_HOST_TOOLS_ENABLED", True):
    initialize_core_mcp_host_tools()

This likely requires splitting initialize_core_mcp_dependencies() into a lightweight decorator-initialization step and a host-tool registration step. The decorator path should avoid importing the MCP service application, preserving the worker memory benefit while maintaining extension-loading compatibility.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f2009f2 — thanks for catching this, and confirming @aminghadersohi's open question from the review.

You're right: the abstract tool/prompt in superset_core.mcp.decorators do raise NotImplementedError (verified against the source — both bodies are literally raise NotImplementedError("MCP ... decorator not initialized. ...")). So with the previous all-or-nothing gate, a process with CORE_MCP_HOST_TOOLS_ENABLED=False that loaded an extension applying @tool/@prompt at import time would have crashed at import.

initialize_core_mcp_dependencies() is now split into two independent steps:

  • initialize_core_mcp_decorators() — swaps the abstract decorators for the concrete ones. Runs unconditionally in init_core_dependencies(), so @tool/@prompt extensions keep working in every process (workers included). Cheap: no superset.mcp_service import.
  • initialize_core_mcp_host_tools() — does from superset.mcp_service import app (the memory-heavy host-tool registration that OOMed the workers). This is the only part now gated on the flag.

Verified end to end: before init, @tool raises NotImplementedError; after initialize_core_mcp_decorators(), superset_core.mcp.decorators.tool is create_tool_decorator and decoration no longer raises — with the host-tool import still skipped when the flag is off.

@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. When CORE_MCP_HOST_TOOLS_ENABLED is set to False, initialize_core_mcp_dependencies() is skipped, but if other parts of the application still attempt to use the tool or prompt decorators (which are typically registered by that function), they will fail with a NotImplementedError because the underlying infrastructure was not initialized.

To resolve this, you should ensure that the decorators themselves are either conditionally registered or provide a safe fallback when the MCP stack is disabled. A common approach is to make the decorators no-ops or return a dummy implementation when the feature is disabled, rather than raising an error.

Would you like me to fetch all comments and validate the rest of the PR to implement a comprehensive fix?

superset/initialization/init.py

if self.config.get("CORE_MCP_HOST_TOOLS_ENABLED", True):
            initialize_core_mcp_dependencies()

@codecov

codecov Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.73684% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.66%. Comparing base (2350348) to head (edf1386).
⚠️ Report is 54 commits behind head on master.

Files with missing lines Patch % Lines
superset/core/mcp/core_mcp_injection.py 94.11% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #43842      +/-   ##
==========================================
+ Coverage   84.63%   84.66%   +0.02%     
==========================================
  Files        3069     3069              
  Lines      205446   205722     +276     
  Branches    48306    48346      +40     
==========================================
+ Hits       173879   174170     +291     
+ Misses      28277    28262      -15     
  Partials     3290     3290              
Flag Coverage Δ
hive 34.50% <50.00%> (-0.03%) ⬇️
mysql 52.59% <50.00%> (+0.04%) ⬆️
postgres 52.60% <50.00%> (+0.04%) ⬆️
presto 36.23% <50.00%> (-0.03%) ⬇️
python 87.77% <94.73%> (+0.04%) ⬆️
sqlite 52.36% <50.00%> (+0.04%) ⬆️
unit 82.54% <94.73%> (+0.08%) ⬆️

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.

@aminghadersohi aminghadersohi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — approving.

The diagnosis and the shape of the fix both look right, and defaulting to True means this is a genuine no-op until a deployment opts out.

One thing I verified explicitly, because it looks wrong at a glance and I suspect it'll come up again in review: the from superset.core.mcp.core_mcp_injection import initialize_core_mcp_dependencies at the top of init_core_dependencies sits outside the new if, so at first read the import cost appears to be paid regardless of the flag — which would defeat the stated purpose. It isn't. core_mcp_injection's module-level imports are just logging, typing, and superset.extensions.context; the expensive work (import superset_core.mcp.decorators, from fastmcp.tools import Tool, and from superset.mcp_service import app) all happens inside initialize_core_mcp_dependencies(). So gating the call does skip the heavy imports, and the memory win is real. Might be worth a one-line comment saying so, purely to save the next reader the same detour.

One question rather than a change request: gating the call also skips the two decorator replacements —

superset_core.mcp.decorators.tool = create_tool_decorator
superset_core.mcp.decorators.prompt = create_prompt_decorator

so on a process with the flag off, superset_core.mcp.decorators.tool stays the abstract version. If any extension that gets loaded in a Celery worker applies @tool/@prompt at import time, it'll now hit the abstract decorator instead of the concrete one. I couldn't check what the abstract implementation does from here (superset_core isn't in my environment). If it's an inert stub, this is a non-issue; if it raises, it'd be worth either leaving the decorator swap ungated or noting the constraint in the config docstring. You'll know the worker-side extension set better than I do.

Two trivia, neither blocking:

  • The new constant lands between the feature-flag lifecycle comment block (# - stable: ... / # - deprecated: ...) and DEFAULT_FEATURE_FLAGS, so it reads as though it belongs to the feature-flag section. It isn't a feature flag — which is the right call, since this is a process-startup concern rather than a runtime toggle — so a few lines earlier or later would make that clearer.
  • - [x] Changes UI is ticked in the description, but this is backend-only.

Comment thread superset/initialization/__init__.py Resolved
Comment thread superset/initialization/__init__.py Resolved
…port

Review follow-up (Amin, sadpandajoe): the previous version gated the whole
initialize_core_mcp_dependencies() call, which also skipped swapping the
abstract @tool/@prompt decorators in superset_core.mcp.decorators for their
concrete implementations. Those abstract decorators raise NotImplementedError,
so any extension that applies @tool/@prompt at import time would crash in a
process running with CORE_MCP_HOST_TOOLS_ENABLED=False (e.g. Celery workers).

Split initialize_core_mcp_dependencies() into:
  - initialize_core_mcp_decorators(): always run; swaps in the concrete
    decorators. Cheap -- does not import the MCP service app.
  - initialize_core_mcp_host_tools(): gated on CORE_MCP_HOST_TOOLS_ENABLED;
    imports superset.mcp_service.app (the memory-heavy part that OOMed workers).

init_core_dependencies() now always registers the decorators and only gates the
host-tool import. Extensions using @tool/@prompt keep working in every process
while non-MCP workers still skip the heavy MCP service app import.

Also clarifies the CORE_MCP_HOST_TOOLS_ENABLED docstring (it gates host tools
only) and adds regression tests covering the flag on/off/default paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pull-request-size pull-request-size Bot added size/L and removed size/S labels Sep 7, 2026
@eschutho

eschutho commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

@aminghadersohi — thanks for the thorough review and the approval. Your open question turned out to be a real issue, so I pushed a follow-up (f2009f2) rather than just documenting the constraint. Summary of what changed and how it lands against your notes:

The decorator question (the substantive one). You were right to flag it: the abstract superset_core.mcp.decorators.tool/prompt raise NotImplementedError (verified against source — both bodies are raise NotImplementedError("MCP ... decorator not initialized ...")). So the previous all-or-nothing gate would have broken any extension that applies @tool/@prompt at import time in a flag-off worker. Fixed by splitting the decorator swap (now always runs — cheap, no mcp_service import) from the host-tool registration (from superset.mcp_service import app, the heavy part, still gated). So @tool/@prompt extensions keep working everywhere, and non-MCP workers still skip the heavy import.

The "import cost is actually paid regardless" note. One correction: core_mcp_injection's module-level imports aren't only logging/typing/superset.extensions.context — there's also a guarded from mcp.types import ToolAnnotations. sadpandajoe flagged the same thing. I measured it: once the (now always-on) decorator step imports fastmcp.tools, mcp is already loaded, so the module-level mcp.types import adds 0 MB on top — and when mcp/fastmcp aren't installed it falls back to dict for free. The real win is gating from superset.mcp_service import app (the service app + all host-tool modules), which is preserved. Details in the thread on that line.

Your "one-line comment" suggestion. Added — init_core_dependencies() now has inline comments explaining that the decorator swap is unconditional and only the host-tool import is gated, and the CORE_MCP_HOST_TOOLS_ENABLED docstring now spells out that it gates host tools only (decorators always register).

Trivia: unchecked the erroneous Changes UI box (this is backend-only). On the constant placement — agreed it isn't a feature flag; I kept it out of DEFAULT_FEATURE_FLAGS for exactly that reason and the docstring now makes the "process-startup concern, not a runtime toggle" intent explicit.

Regression tests for the flag on/off/default paths added per sadpandajoe's request.

@netlify

netlify Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit edf1386
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6ae521eea2bffae7b3587d3c
😎 Deploy Preview https://deploy-preview-43842--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@bito-code-review

bito-code-review Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #0f7024

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset/core/mcp/core_mcp_injection.py - 1
    • Stale module path in docstring · Line 329-329
      The docstring says decorators are replaced in `superset_core.api.mcp`, but the code (lines 287, 299-300) operates on `superset_core.mcp.decorators`. The new `initialize_core_mcp_decorators` docstring names the correct module, so this stale reference is inconsistent and could mislead callers about where the swap happens.
Review Details
  • Files reviewed - 4 · Commit Range: fedcb64..f2009f2
    • superset/config.py
    • superset/core/mcp/core_mcp_injection.py
    • superset/initialization/__init__.py
    • tests/unit_tests/initialization_test.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Comment thread tests/unit_tests/initialization_test.py Resolved
…import

Addresses @sadpandajoe's review on tests/unit_tests/initialization_test.py
in PR apache#43842. Joe was correct: the concrete decorators returned by
create_tool_decorator / create_prompt_decorator imported
superset.mcp_service.app inside their inner decorator (core_mcp_injection.py
lines 101 & 211), so a real extension applying @tool/@prompt at import time
would load the heavy host-tool stack even in a process that opted out via
CORE_MCP_HOST_TOOLS_ENABLED=False -- defeating the worker memory benefit.

The concrete decorators now skip registration (and the import) when
superset.mcp_service.app is not already loaded in the process. Every
MCP-serving process imports it via initialize_core_mcp_host_tools() before
any extension applies a decorator, so registration still works there; a
disabled worker never imports it and the decoration stays a no-op that
returns the original function unchanged.

Adds a regression test that exercises the concrete decorators directly (not
mocked) with the service app unloaded and asserts it stays unimported, per
Joe's request.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the requires:rebase Requires rebasing on top of current master label Sep 16, 2026
eschutho and others added 2 commits September 16, 2026 15:26
The master merge left two ruff failures CI would catch:
- I001: missing blank line between the stdlib and third-party import groups
  in core_mcp_injection.py (master's `from pydantic import TypeAdapter` and
  our added `import sys`/`get_type_hints` ended up adjacent).
- C901: master's `create_tool_decorator` was exactly at the complexity limit
  (10); the lazy `_mcp_host_tools_loaded()` guard added for disabled workers
  tips it to 11. Suppress with `# noqa: C901` (an established pattern in the
  repo) rather than restructuring master's decorator.

No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bito-code-review

bito-code-review Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #7a25ea

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: f2009f2..5fdb531
    • superset/core/mcp/core_mcp_injection.py
    • tests/unit_tests/initialization_test.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@github-actions github-actions Bot removed the requires:rebase Requires rebasing on top of current master label Sep 17, 2026
@rusackas

Copy link
Copy Markdown
Member

Heya @eschutho, the CI red here looks like the dual migration heads on master that got merged into one via 60f94cd6cd11 right after your last rebase, so a fresh rebase should clear it up.

Also, @sadpandajoe's four threads look addressed in code already (f2009f2/d45a8ae) but GitHub's still showing them open, might be worth marking those resolved when you get a chance.

_MCP_SERVICE_APP_MODULE = "superset.mcp_service.app"


def _mcp_host_tools_loaded() -> bool:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_mcp_host_tools_loaded() only checks whether superset.mcp_service.app is a key in sys.modules, and Python adds a module to sys.modules the moment it starts importing it — before that module's own host-tool registration (further down in mcp_service/app.py) has actually run. A caller relying on this helper to mean "host tools are registered" could get a false positive while that module is still mid-import. Could the name/docstring make clear this signals "app module present in sys.modules" rather than "host tools finished registering"?

def initialize_core_mcp_dependencies() -> None:
"""
Initialize MCP dependency injection by replacing abstract functions
in superset_core.api.mcp with concrete implementations, then registering the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docstring says the decorator swap happens "in superset_core.api.mcp", but the code replaces attributes on superset_core.mcp.decorators (see initialize_core_mcp_decorators above and the assignments a few lines up). A reader tracing where the swap happens from this docstring would look in the wrong module.

Suggested change
in superset_core.api.mcp with concrete implementations, then registering the
in superset_core.mcp.decorators with concrete implementations, then registering the

# The heavy MCP service app import is skipped.
mock_init_host_tools.assert_not_called()

def test_disabled_worker_decorators_do_not_import_service_app(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test calls create_tool_decorator/create_prompt_decorator directly rather than through superset_core.mcp.decorators.tool/.prompt, and the three tests above it mock initialize_core_mcp_decorators() out entirely. So no test in this file exercises the actual assignment inside initialize_core_mcp_decorators() (superset_core.mcp.decorators.tool = create_tool_decorator, etc.) — a regression there (e.g. binding the wrong function) would pass every current test. Could one test call the real initialize_core_mcp_decorators() and then decorate through superset_core.mcp.decorators.tool/.prompt to cover that wiring?

@github-actions github-actions Bot added the requires:rebase Requires rebasing on top of current master label Oct 2, 2026
…ools-off-workers

# Conflicts:
#	superset/core/mcp/core_mcp_injection.py
@eschutho
eschutho requested review from geido and msyavuz as code owners October 7, 2026 00:52
@codeant-ai-for-open-source

codeant-ai-for-open-source Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

CodeAnt  CodeAnt PR Risk

🟢  Low Risk

  • The PR appears safe to merge; decorator initialization is independent of host-tool loading, and disabled workers skip MCP service imports while retaining usable extension decorators.

Assessed commit: edf13863c6a7

Comment thread superset/core/mcp/core_mcp_injection.py Resolved
Comment thread superset/core/mcp/core_mcp_injection.py Resolved
@github-actions github-actions Bot removed the requires:rebase Requires rebasing on top of current master label Oct 7, 2026
…ations

Addresses @sadpandajoe's review on PR apache#43842:

- Without the optional fastmcp extra, initialize_core_mcp_decorators()
  skipped quietly but initialize_core_mcp_host_tools() still ran and logged
  "Failed to register MCP host tools" at ERROR on every boot. It now checks
  the same fastmcp-availability guard first and returns with a debug log,
  restoring the pre-split quiet-skip behavior end to end.
- When the MCP service app is not loaded, the concrete @tool/@prompt
  decorators still no-op, but now log a WARNING once per registration so a
  process that serves MCP with CORE_MCP_HOST_TOOLS_ENABLED=False does not
  silently lose extension tools/prompts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eschutho
eschutho requested a review from sadpandajoe October 7, 2026 19:00
@rusackas

Copy link
Copy Markdown
Member

Thanks @eschutho, the split makes sense and Amin's approval plus green CI looks good to me. One leftover: the initialize_core_mcp_dependencies() docstring still says the swap happens in superset_core.api.mcp, but it's superset_core.mcp.decorators, so Joe's suggestion there is still open. I'd also resolve the threads that are already fixed so the remaining ones are easy to see. Happy to stamp/approve after that.

with patch.object(
core_mcp_injection, "_fastmcp_available", return_value=False
):
core_mcp_injection.initialize_core_mcp_dependencies()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The missing-dependency test can still pass with the _fastmcp_available() guard in initialize_core_mcp_host_tools() deleted. The module-scoped app fixture has already imported the MCP service app, so popping sys.modules["superset.mcp_service.app"] leaves app cached as an attribute on the superset.mcp_service package; from superset.mcp_service import app then succeeds without touching sys.modules and without logging an error, so both assertions hold either way. Could this run in a state where the package attribute is also cleared (for example monkeypatch.delattr(superset.mcp_service, "app", raising=False)), or patch the import so the test fails when the guard is absent?

…guard

Also clear the cached superset.mcp_service.app attribute so the import cannot
succeed without sys.modules, and assert the debug skip log.
Comment thread superset/core/mcp/core_mcp_injection.py Outdated
_skipped_registrations.add(key)
logger.warning(
"Skipping MCP %s registration for %s: the MCP service app is not loaded "
"in this process (CORE_MCP_HOST_TOOLS_ENABLED is False). This is expected "

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This warning states the flag is False, but the guard only checks whether superset.mcp_service.app is in sys.modules. With the default CORE_MCP_HOST_TOOLS_ENABLED = True, if initialize_core_mcp_host_tools() hits its caught import error, the app never loads and each extension @tool/@prompt then logs that the flag is False, sending an operator to enable a setting that is already enabled. Could the message describe the observed condition (service app not loaded) and mention the flag only as one possible cause?

… warning

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants