New in Dash 4.3.0
Dash apps can act as MCP Servers for AI agents such as Claude. By implementing the Model Context Protocol behind the scenes, your users can link their AI agents to your Dash app and interact with it directly without a browser.
An MCP client is any software that can connect to an MCP server. Claude Code, Claude Desktop, ChatGPT, and Cursor are all MCP clients. Once a user points their client at your app, its AI agent can answer questions about your specific datasets, charts, and analyses by executing code that you have written in your app. Rather than drawing on general knowledge, AI agents can query your app to discover trustworthy, domain-specific knowledge as implemented by you.
Pass enable_mcp=True to the Dash constructor to start an MCP server alongside your app.
app = Dash(__name__, enable_mcp=True)
With MCP enabled, you can add the URL of your published Dash app as an MCP Server within your AI agent. Agents will immediately know about your app’s layout, data, pages, and callbacks. They will understand the relationship between your callbacks and what they produce. They will know how to call your callbacks with valid inputs and how to present their outputs in a meaningful way.
Connecting to an MCP-enabled Dash app works the same way as connecting to any other MCP server. Point your client at your app’s URL followed by the MCP path (/_mcp by default, configurable with mcp_path). For example, an app deployed at https://your-app.com exposes its MCP server at https://your-app.com/_mcp.
Claude Code
Run the following command in your terminal, substituting your app’s URL:
claude mcp add my-dash-app --transport http --scope user <a href="https://your-app.com/_mcp">https://your-app.com/_mcp</a>
Note: You need to restart Claude Code after running this command for the new MCP server to be available.
Other clients (JSON config)
Most MCP clients (Cursor, Windsurf, Kiro, and others) accept an HTTP server entry in their MCP config. Point it at your app’s URL:
{
"mcpServers": {
"my-dash-app": {
"type": "http",
"url": "https://your-app.com/_mcp"
}
}
}
If your app is running locally or an app that is available on the public internet, you can immediately start asking the AI agent about it without authentication.
For published apps, the way users authenticate depends on how you’ve secured your Dash app—see MCP Authentication. The simplest option is publishing your app to Plotly Cloud, which handles authentication for you. Learn more in Dash MCP on Plotly Cloud.
By default, the server exposes your layout, callbacks, pages, and clientside callbacks. Use configure_mcp_server to narrow that down or to opt into exposing callback docstrings:
from dash.mcp import configure_mcp_server
configure_mcp_server(
include_pages=False, # don't expose the page registry
expose_callback_docstrings=True, # include callback docstrings as context
)
See MCP Reference for the complete list of options.
The MCP server does not expose your source code or private/internal variables to AI agents. It only presents your layout and callbacks in an AI-friendly format: an alternative view of what users see for themselves in a browser.
An exception to the above is that the function names of your callbacks are exposed to AI agents. They are typically displayed to end users when asking for permission to call a tool.
For example, if you were to have this callback:
@callback(Output(...), Input(...))
def get_data_from_s3():
...
be aware that AI agents will see this function name and usually show it to the app user (Calling tool "get_data_from_s3"...) when asking for user permission to execute the callback. In this example, the function name reveals that S3 is used internally, although the details of the S3 connection are not revealed.
AI agents are efficient at discovering all possible callback inputs, making them powerful for iterating through options. If your callbacks rely on security through obscurity, enabling MCP on the callback would bring this risk to the forefront, since AI agents can easily see through the obscurity. Importantly, this would not be a new risk since AI agents are also capable of iterating through callbacks via browser or curl network requests. If there is a sensitive input to your callback and you rely on it not being obvious to the user, consider that AI agents are readily able to discover it.
For example, consider a callback whose input comes from a dropdown that only lists public regions, but which also accepts a special "__internal__" value that returns privileged data:
@callback(
Output("report", "children"),
Input("region", "value"), # dropdown options: "north", "south", "east", "west"
)
def get_report(region):
if region == "__internal__":
return fetch_confidential_company_wide_report()
return fetch_public_report(region)
In the browser, the "__internal__" value is not obvious because it isn’t one of the dropdown’s options. But the callback still accepts it, so the protection is only obscurity: a determined user could already discover it through network requests. An AI agent, prompted to “try other values,” is likely to find it quickly. Don’t rely on the value being hidden from the UI; rather, enforce the access rule explicitly (for example, by checking the authenticated user’s permissions).
Learn more: