Skip to content

feat(extensions): add client-side MCP tools API for chat - #43093

Open
justinpark wants to merge 1 commit into
apache:dashboard-v2from
justinpark:feat--client-tool-mcp
Open

feat(extensions): add client-side MCP tools API for chat#43093
justinpark wants to merge 1 commit into
apache:dashboard-v2from
justinpark:feat--client-tool-mcp

Conversation

@justinpark

@justinpark justinpark commented Aug 12, 2026

Copy link
Copy Markdown
Member

SUMMARY

  • Add mcpTools as a top-level extension.json property (sibling to commands), auto-exposed over Module Federation as ./mcpTools at build time and auto-loaded by the host — no manual webpack wiring required per extension.
  • Add chat.getTools(), chat.McpTool, chat.McpToolsFactory, and chat.McpToolsFormat to @apache-superset/core's chat namespace, backed by a new tool registry in ChatProvider (registerTools()/getTools()).
  • Enforce a naming convention automatically: tool authors write an unprefixed surface__name (e.g. dashboard__get_active_id); the registry validates the surface and adds the core./. prefix itself, rejecting anything already-prefixed or malformed.
  • Add chat.getTools(chat.McpToolsFormat.Claude) to convert registered tools to the wire format Anthropic's Messages API (and this extension's own backend) expects, dropping the non-serializable handler. AgUi/CopilotKit/Codex exist as named placeholders that throw, since nothing in this codebase talks to those frameworks yet.
  • Add dashboard.getDashboardId() to @apache-superset/core's dashboard namespace and implement core.dashboard__get_active_id as the first real core MCP tool, scaffolding empty stubs for the other seven product surfaces.
  • Migrate the chat extension's own dashboard-editing tools onto this mechanism, replacing an ad hoc React hook.
  • Fix an extension-loading order bug: ExtensionsLoader now registers an extension's mcpTools before running its ./index entrypoint, closing a race where ./index synchronously mounting an already-open chat panel (persisted state from a prior session) could call chat.getTools() before that extension's own tools were registered.

How to Use

Declare mcpTools in extension.json, pointing at a file resolved relative to frontend/src/:

{
  "mcpTools": { "url": "./mcpTools.ts" }
}

That file's default export is a chat.McpToolsFactory(chat) => McpTool[]. Each tool's name is unprefixed (surface__name, no core./extension-id prefix — the host adds that automatically). From extensions/chat/frontend/src/mcpTools.ts:

import { dashboard } from "@apache-superset/core";
import type { chat as chatApi } from "@apache-superset/core";

const getDashboardTools: chatApi.McpToolsFactory = (chat) => [
  {
    name: "dashboard__get_root",
    description:
      "Returns the dashboard's root canvas node, is. " +
      "Use this to see the top-level layout before
    inputSchema: { type: "object", properties: {}
    handler: () => ({ success: true, node: dashboa
  },
  // ...seven more tools
];

export default getDashboardTools;

Consuming them (from ChatPanel.tsx):

import { chat } from "@apache-superset/core";

// Native format, keeps `handler` — for dispatchin
const clientTools = chat.getTools();

// Claude wire format (input_schema, no handler) — for the API request
const clientToolSpecs = chat.getTools(chat.McpToolsFormat.Claude);

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

TESTING INSTRUCTIONS

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

@dosubot dosubot Bot added the change:frontend Requires changing the frontend label Aug 12, 2026
@bito-code-review

bito-code-review Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped - Branch Excluded

Bito didn't auto-review because the source or target branch is excluded from automatic reviews.
No action is needed if you didn't intend for the agent to review it. Otherwise, to manually trigger a review, type /review in a comment and save.
You can change the branch exclusion settings here, or contact your Bito workspace admin at evan@preset.io.

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 2 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (dashboard-v2@116142e). Learn more about missing BASE report.

Files with missing lines Patch % Lines
superset/extensions/utils.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff               @@
##             dashboard-v2   #43093   +/-   ##
===============================================
  Coverage                ?   65.35%           
===============================================
  Files                   ?     2800           
  Lines                   ?   158275           
  Branches                ?    36053           
===============================================
  Hits                    ?   103440           
  Misses                  ?    52857           
  Partials                ?     1978           
Flag Coverage Δ
hive 38.38% <0.00%> (?)
mysql 57.55% <0.00%> (?)
postgres 57.58% <0.00%> (?)
presto 40.30% <0.00%> (?)
python 58.99% <0.00%> (?)
sqlite 57.21% <0.00%> (?)
superset-extensions-cli 90.57% <ø> (?)
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.

@netlify

netlify Bot commented Aug 12, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 9f5bf2f
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a7ca1662fcce60008fbaa2a
😎 Deploy Preview https://deploy-preview-43093--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.

export declare function getTools(
format: typeof McpToolsFormat.Claude,
): ClaudeToolSpec[];
export declare function getTools(format: McpToolsFormat): never;

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: The overload for a general McpToolsFormat incorrectly returns never, even though the union includes Claude, which has a valid return value. A caller storing chat.McpToolsFormat.Claude in a variable typed as McpToolsFormat will see the result of getTools(format) typed as unreachable and cannot use the returned tool specifications. Provide a return type that reflects the possible formats, or separate the throwing placeholder formats from the Claude overload. [type error]

Severity Level: Major ⚠️
- ⚠️ Dynamic Claude tool selection fails TypeScript compilation.
- ⚠️ Extension consumers cannot safely use union-typed format variables.
- ❌ Typed integrations cannot send returned tools to Claude APIs.

Fix in Cursor Fix in VSCode Claude

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

**Path:** superset-frontend/packages/superset-core/src/chat/index.ts
**Line:** 260:260
**Comment:**
	*Type Error: The overload for a general `McpToolsFormat` incorrectly returns `never`, even though the union includes `Claude`, which has a valid return value. A caller storing `chat.McpToolsFormat.Claude` in a variable typed as `McpToolsFormat` will see the result of `getTools(format)` typed as unreachable and cannot use the returned tool specifications. Provide a return type that reflects the possible formats, or separate the throwing placeholder formats from the Claude overload.

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
👍 | 👎

@bito-code-review

Copy link
Copy Markdown
Contributor

The issue is correct. The current getTools overload for McpToolsFormat returns never, which prevents TypeScript from allowing a variable typed as McpToolsFormat to be passed to getTools(), even when it holds a valid value like Claude.

To resolve this, you should update the getTools overloads to allow McpToolsFormat as a valid input, while keeping the throwing behavior for unsupported formats at runtime. You can achieve this by defining a more permissive overload for getTools that accepts McpToolsFormat and returns ClaudeToolSpec[] | never (or simply ClaudeToolSpec[] if you handle the throwing logic inside the implementation).

superset-frontend/packages/superset-core/src/chat/index.ts

export declare function getTools(): McpTool[];
export declare function getTools(
  format: McpToolsFormat,
): ClaudeToolSpec[];

Comment on lines +112 to +118
url: str = Field(
...,
description=(
"Path to the module exporting the mcpTools factory, resolved "
"relative to frontend/src/ (e.g. './mcpTools.ts')"
),
)

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: The schema accepts an empty url, but the webpack template only exposes ./mcpTools for a truthy URL while manifest generation marks any truthy mcpTools object as enabled. An extension using {"url": ""} therefore advertises MCP tools while its container does not expose ./mcpTools, causing extension initialization to fail. Require a non-empty URL in the schema or make manifest generation use the same truthiness condition as the webpack template. [api mismatch]

Severity Level: Major ⚠️
- ❌ Invalid extension configuration can prevent the extension entrypoint from loading.
- ⚠️ Generated metadata advertises an unavailable MCP module.
- ⚠️ Extension startup logs a failure instead of loading normal contributions.

Fix in Cursor Fix in VSCode Claude

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

**Path:** superset-core/src/superset_core/extensions/types.py
**Line:** 112:118
**Comment:**
	*Api Mismatch: The schema accepts an empty `url`, but the webpack template only exposes `./mcpTools` for a truthy URL while manifest generation marks any truthy `mcpTools` object as enabled. An extension using `{"url": ""}` therefore advertises MCP tools while its container does not expose `./mcpTools`, causing extension initialization to fail. Require a non-empty URL in the schema or make manifest generation use the same truthiness condition as the webpack template.

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
👍 | 👎

Comment on lines +50 to +51
function getDashboardId(): number | undefined {
return store.getState().dashboardInfo?.id;

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: dashboardInfo is a merge-based global reducer and is not cleared when navigating away from a classic dashboard, so this accessor returns the last visited dashboard ID on Explore, SQL Lab, or other non-dashboard pages instead of undefined as promised by the public API. Track the active dashboard page lifecycle or clear the reducer state on navigation before exposing this value. [stale reference]

Severity Level: Major ⚠️
- ❌ Chat tools receive the wrong active dashboard identifier.
- ⚠️ Extensions may target a dashboard after navigation.
- ⚠️ Non-dashboard pages expose stale dashboard context.

Fix in Cursor Fix in VSCode Claude

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

**Path:** superset-frontend/src/core/dashboard/index.ts
**Line:** 50:51
**Comment:**
	*Stale Reference: `dashboardInfo` is a merge-based global reducer and is not cleared when navigating away from a classic dashboard, so this accessor returns the last visited dashboard ID on Explore, SQL Lab, or other non-dashboard pages instead of `undefined` as promised by the public API. Track the active dashboard page lifecycle or clear the reducer state on navigation before exposing this value.

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
👍 | 👎

const mcpToolsFactory = await container.get('./mcpTools');
const mcpToolsModule = mcpToolsFactory() as unknown as McpToolsModule;
const tools = mcpToolsModule.default(scopedCore.chat);
registerChatTools(extension.id, tools);

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: The tool registration occurs before the main ./index factory, but its returned Disposable is discarded. If the subsequent factory() throws, initialization reports failure while the extension's MCP tools remain registered and callable, leaving orphaned tools from an extension that was not successfully initialized. Retain the disposable and remove the tools when main-entry initialization fails. [missing cleanup]

Severity Level: Major ⚠️
- ⚠️ Failed extensions leave stale MCP tools callable.
- ⚠️ Chat can invoke handlers from an uninitialized extension.
- ⚠️ Tool listings disagree with extension initialization status.

Fix in Cursor Fix in VSCode Claude

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

**Path:** superset-frontend/src/extensions/ExtensionsLoader.ts
**Line:** 271:271
**Comment:**
	*Missing Cleanup: The tool registration occurs before the main `./index` factory, but its returned `Disposable` is discarded. If the subsequent `factory()` throws, initialization reports failure while the extension's MCP tools remain registered and callable, leaving orphaned tools from an extension that was not successfully initialized. Retain the disposable and remove the tools when main-entry initialization fails.

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
👍 | 👎

@michael-s-molina

Copy link
Copy Markdown
Member

Thanks for the PR, @justinpark! I'll go through it in detail and leave comments, but one initial request: could we align the tools registration approach with how other contributions are registered, using a code-first pattern? This mirrors a change we made in the extensions framework, discussed in Extension Contributions: Code-First vs Manifest-First. Concretely, rather than declaring an mcpTools entry in the manifest, we'd expose a registerMcpTool function—similar to registerView or registerCommand—that extensions can call directly.

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

Labels

change:frontend Requires changing the frontend packages size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants