|
6 | 6 |
|
7 | 7 | from __future__ import annotations |
8 | 8 |
|
| 9 | +from typing import Optional |
| 10 | + |
9 | 11 | from dash import get_app |
10 | 12 | from dash.exceptions import AppNotFoundError |
11 | 13 | from dash.mcp.primitives.resources import _RESOURCE_PROVIDERS as MCP_RESOURCE_PROVIDERS |
|
23 | 25 | _ALL_MCP_RESOURCE_PROVIDERS = list(MCP_RESOURCE_PROVIDERS) |
24 | 26 | _ALL_MCP_TOOL_PROVIDERS = list(MCP_TOOL_PROVIDERS) |
25 | 27 |
|
| 28 | +# Membership groupings (order-independent): which providers each toggle |
| 29 | +# controls. The exposed order is owned solely by the registry lists. |
| 30 | +_LAYOUT_RESOURCES = {LayoutResource, ComponentsResource} |
| 31 | +_CLIENTSIDE_CALLBACK_RESOURCES = {ClientsideCallbacksResource} |
| 32 | +_PAGE_RESOURCES = {PagesResource, PageLayoutResource} |
| 33 | +_LAYOUT_TOOLS = {GetDashComponentTool} |
| 34 | + |
| 35 | +_DEFAULT_CONFIG = { |
| 36 | + "include_layout": True, |
| 37 | + "include_callbacks": True, |
| 38 | + "include_clientside_callbacks": True, |
| 39 | + "include_pages": True, |
| 40 | + "expose_callback_docstrings": False, |
| 41 | +} |
| 42 | +_current_config = dict(_DEFAULT_CONFIG) |
| 43 | + |
26 | 44 |
|
27 | 45 | def configure_mcp_server( |
28 | 46 | *, |
29 | | - include_layout: bool = True, |
30 | | - include_callbacks: bool = True, |
31 | | - include_clientside_callbacks: bool = True, |
32 | | - include_pages: bool = True, |
33 | | - expose_callback_docstrings: bool = False, |
| 47 | + include_layout: Optional[bool] = None, |
| 48 | + include_callbacks: Optional[bool] = None, |
| 49 | + include_clientside_callbacks: Optional[bool] = None, |
| 50 | + include_pages: Optional[bool] = None, |
| 51 | + expose_callback_docstrings: Optional[bool] = None, |
34 | 52 | ) -> None: |
35 | 53 | """ |
36 | 54 | Configure which content the Dash MCP server exposes. |
37 | 55 |
|
38 | | - Any parameter that is omitted will be reset to its default value. Calling |
39 | | - with no args will reset all configuration to its default state. |
| 56 | + Only the parameters that are explicitly passed are updated; any parameter |
| 57 | + that is omitted keeps its current value. On the first call, unset values |
| 58 | + take their defaults (all content included except callback docstrings). |
40 | 59 |
|
41 | 60 | :param include_layout: Expose ``dash://layout``, ``dash://components``, |
42 | 61 | and the ``get_dash_component`` tool. Defaults to ``True``. |
@@ -66,25 +85,49 @@ def configure_mcp_server( |
66 | 85 | if get_app().backend.has_request_context(): |
67 | 86 | raise RuntimeError("MCP server can't be configured within a callback") |
68 | 87 | except AppNotFoundError: |
69 | | - ... |
70 | | - |
71 | | - CallbackTools.callbacks_mcp_enabled_by_default = include_callbacks |
72 | | - CallbackTools.expose_docstrings_by_default = expose_callback_docstrings |
73 | | - |
74 | | - updated_resources = list(_ALL_MCP_RESOURCE_PROVIDERS) |
75 | | - if not include_layout: |
76 | | - updated_resources.remove(LayoutResource) |
77 | | - updated_resources.remove(ComponentsResource) |
78 | | - if not include_clientside_callbacks: |
79 | | - updated_resources.remove(ClientsideCallbacksResource) |
80 | | - if not include_pages: |
81 | | - updated_resources.remove(PagesResource) |
82 | | - updated_resources.remove(PageLayoutResource) |
83 | | - MCP_RESOURCE_PROVIDERS[:] = updated_resources |
84 | | - |
85 | | - updated_tools = list(_ALL_MCP_TOOL_PROVIDERS) |
86 | | - if not include_layout: |
87 | | - updated_tools.remove(GetDashComponentTool) |
88 | | - MCP_TOOL_PROVIDERS[:] = updated_tools |
89 | | - |
90 | | - get_app().mcp_callback_map = None |
| 88 | + pass |
| 89 | + |
| 90 | + passed = { |
| 91 | + "include_layout": include_layout, |
| 92 | + "include_callbacks": include_callbacks, |
| 93 | + "include_clientside_callbacks": include_clientside_callbacks, |
| 94 | + "include_pages": include_pages, |
| 95 | + "expose_callback_docstrings": expose_callback_docstrings, |
| 96 | + } |
| 97 | + _current_config.update( |
| 98 | + {key: value for key, value in passed.items() if value is not None} |
| 99 | + ) |
| 100 | + |
| 101 | + CallbackTools.callbacks_mcp_enabled_by_default = _current_config[ |
| 102 | + "include_callbacks" |
| 103 | + ] |
| 104 | + CallbackTools.expose_docstrings_by_default = _current_config[ |
| 105 | + "expose_callback_docstrings" |
| 106 | + ] |
| 107 | + |
| 108 | + excluded_resources: set = set() |
| 109 | + if not _current_config["include_layout"]: |
| 110 | + excluded_resources |= _LAYOUT_RESOURCES |
| 111 | + if not _current_config["include_clientside_callbacks"]: |
| 112 | + excluded_resources |= _CLIENTSIDE_CALLBACK_RESOURCES |
| 113 | + if not _current_config["include_pages"]: |
| 114 | + excluded_resources |= _PAGE_RESOURCES |
| 115 | + MCP_RESOURCE_PROVIDERS[:] = [ |
| 116 | + resource |
| 117 | + for resource in _ALL_MCP_RESOURCE_PROVIDERS |
| 118 | + if resource not in excluded_resources |
| 119 | + ] |
| 120 | + |
| 121 | + excluded_tools: set = set() |
| 122 | + if not _current_config["include_layout"]: |
| 123 | + excluded_tools |= _LAYOUT_TOOLS |
| 124 | + MCP_TOOL_PROVIDERS[:] = [ |
| 125 | + tool for tool in _ALL_MCP_TOOL_PROVIDERS if tool not in excluded_tools |
| 126 | + ] |
| 127 | + |
| 128 | + # Invalidate the cached callback map so it is rebuilt with the new config. |
| 129 | + # No app yet (configured before `Dash()`) means there is no cache to clear. |
| 130 | + try: |
| 131 | + get_app().mcp_callback_map = None |
| 132 | + except AppNotFoundError: |
| 133 | + pass |
0 commit comments