Skip to content

MCP Server Part 7: get_dash_component tool and callback tool execution#3749

Open
KoolADE85 wants to merge 6 commits intomcpfrom
feature/mcp-get-dash-component-tool
Open

MCP Server Part 7: get_dash_component tool and callback tool execution#3749
KoolADE85 wants to merge 6 commits intomcpfrom
feature/mcp-get-dash-component-tool

Conversation

@KoolADE85
Copy link
Copy Markdown
Contributor

Summary

  • Wires up the full tool pipeline following the same pattern as resources:

    • list_tools() builds the tool list from all registered tool providers
    • call_tool(name, args) routes LLM calls to the appropriate callback function
  • Adds a static tool get_dash_component which allows LLMs to query a specific prop for a given ID.

Manual verification:

from dash import Dash, html, dcc, Input, Output
import json

app = Dash(__name__)
app.layout = html.Div([
    dcc.Dropdown(id="city", options=["NYC", "LA"], value="NYC"),
    dcc.Graph(id="chart"),
    html.Div(id="summary"),
])

@app.callback(Output("chart", "figure"), Input("city", "value"))
def update_chart(city):
    return {"data": [], "layout": {"title": city}}

@app.callback(Output("summary", "children"), Input("city", "value"))
def update_summary(city):
    return f"Showing data for {city}"

with app.server.app_context():
    from dash.mcp.primitives.tools import list_tools, call_tool

    # List all tools — should include update_chart, update_summary, get_dash_component
    tools = list_tools()
    for tool in tools.tools:
        print(f"- {tool.name}")

    # Inspect a component — shows it's input to both callbacks
    result = call_tool("get_dash_component", {"component_id": "city"})
    print(json.dumps(json.loads(result.content[0].text), indent=2))
    # "value" property should list both `update_chart` and `update_summary` in input_to_tool

    # Execute a callback via its tool
    result = call_tool("update_summary", {"city": "LA"})
    print(result.content[0].text)  # Shows "Showing data for LA"

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 16, 2026

Thank you for your contribution to Dash! 🎉

This PR is exempt from requiring a linked issue due to its labels.

@KoolADE85 KoolADE85 force-pushed the feature/mcp-formatted-tool-results branch from 0e54d74 to 8a41b76 Compare April 21, 2026 17:28
@KoolADE85 KoolADE85 force-pushed the feature/mcp-get-dash-component-tool branch from 8c1f392 to f9b05da Compare April 21, 2026 17:28
@KoolADE85 KoolADE85 force-pushed the feature/mcp-formatted-tool-results branch from 8a41b76 to 4fb9d4a Compare April 22, 2026 21:31
@KoolADE85 KoolADE85 force-pushed the feature/mcp-get-dash-component-tool branch from f9b05da to bc340e6 Compare April 22, 2026 21:37
@KoolADE85 KoolADE85 force-pushed the feature/mcp-formatted-tool-results branch from 4fb9d4a to 10a544b Compare April 23, 2026 20:15
@KoolADE85 KoolADE85 force-pushed the feature/mcp-get-dash-component-tool branch from bc340e6 to b3d4015 Compare April 23, 2026 20:24
@KoolADE85 KoolADE85 force-pushed the feature/mcp-formatted-tool-results branch 2 times, most recently from d203cf9 to e73cd17 Compare April 30, 2026 15:23
@KoolADE85 KoolADE85 force-pushed the feature/mcp-get-dash-component-tool branch 2 times, most recently from e407569 to 37e92f0 Compare April 30, 2026 16:02
@KoolADE85 KoolADE85 force-pushed the feature/mcp-formatted-tool-results branch from 2acb39d to 04ca2fe Compare April 30, 2026 16:32
@KoolADE85 KoolADE85 force-pushed the feature/mcp-get-dash-component-tool branch from 37e92f0 to 28d5fed Compare April 30, 2026 16:45
@KoolADE85 KoolADE85 force-pushed the feature/mcp-formatted-tool-results branch from 04ca2fe to 8f9d5a0 Compare May 6, 2026 21:31
@KoolADE85 KoolADE85 force-pushed the feature/mcp-get-dash-component-tool branch 2 times, most recently from f098f88 to 79df12e Compare May 8, 2026 15:40
@KoolADE85 KoolADE85 changed the base branch from feature/mcp-formatted-tool-results to mcp May 8, 2026 17:07
Copy link
Copy Markdown
Contributor

@camdecoster camdecoster left a comment

Choose a reason for hiding this comment

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

I made a few suggestions. The one to pay attention to is the loop going through callbacks/inputs/outputs. Let me know what you think.

if prop_filter and prop_name != prop_filter:
continue

value = callback_map.get_initial_value(f"{comp_id}.{prop_name}")
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.

Suggested change
value = callback_map.get_initial_value(f"{comp_id}.{prop_name}")
id_and_prop = f"{comp_id}.{prop_name}"
value = callback_map.get_initial_value(id_and_prop)


modified_by: list[str] = []
input_to: list[str] = []
id_and_prop = f"{comp_id}.{prop_name}"
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.

Suggested change
id_and_prop = f"{comp_id}.{prop_name}"

def call_tool(cls, tool_name: str, arguments: dict[str, Any]) -> CallToolResult:
comp_id = arguments.get("component_id", "")
if not comp_id:
raise ValueError("component_id is required")
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.

Suggested change
raise ValueError("component_id is required")
return CallToolResult(
content=[TextContent(type="text", text="component_id is required")],
isError=True,
)

Comment thread dash/mcp/primitives/tools/tool_get_dash_component.py
Comment on lines +69 to +70
if component is None:
callback_map = get_app().mcp_callback_map
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.

Suggested change
if component is None:
callback_map = get_app().mcp_callback_map
callback_map = get_app().mcp_callback_map
if component is None:

isError=True,
)

callback_map = get_app().mcp_callback_map
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.

Suggested change
callback_map = get_app().mcp_callback_map

Comment on lines +103 to +109
for cb in callback_map:
for out in cb.outputs:
if out["id_and_prop"] == id_and_prop:
modified_by.append(cb.tool_name)
for inp in cb.inputs:
if inp["id_and_prop"] == id_and_prop:
input_to.append(cb.tool_name)
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.

This could get expensive for apps with a lot of callbacks, inputs, outputs. Is there a way to create and use an index for looking this up?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great point! The dash renderer actually implements such a mapping (but in JS obviously). I'll do the same on the python side.

@KoolADE85 KoolADE85 force-pushed the feature/mcp-get-dash-component-tool branch from f1b6cc2 to d25123b Compare May 8, 2026 22:04
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