@@ -244,6 +244,50 @@ def update(val):
244244 in tool .description
245245 )
246246
247+ def test_app_level_opt_in_exposes_docstrings (self ):
248+ """Dash(mcp_expose_docstrings=True) exposes docstrings for all callbacks."""
249+ app = Dash (__name__ , mcp_expose_docstrings = True )
250+ app .layout = html .Div ([dcc .Input (id = "inp" ), html .Div (id = "out" )])
251+
252+ @app .callback (Output ("out" , "children" ), Input ("inp" , "value" ))
253+ def update (val ):
254+ """intentionally-exposed callback docstring text for the LLM"""
255+ return val
256+
257+ app_context .set (app )
258+ app .mcp_callback_map = CallbackAdapterCollection (app )
259+
260+ with app .server .test_request_context ():
261+ tool = app .mcp_callback_map [0 ].as_mcp_tool
262+ assert (
263+ "intentionally-exposed callback docstring text for the LLM"
264+ in tool .description
265+ )
266+
267+ def test_per_callback_false_overrides_app_level_opt_in (self ):
268+ """Per-callback mcp_expose_docstring=False wins over app-level opt-in."""
269+ app = Dash (__name__ , mcp_expose_docstrings = True )
270+ app .layout = html .Div ([dcc .Input (id = "inp" ), html .Div (id = "out" )])
271+
272+ @app .callback (
273+ Output ("out" , "children" ),
274+ Input ("inp" , "value" ),
275+ mcp_expose_docstring = False ,
276+ )
277+ def update (val ):
278+ """sensitive callback docstring text that must not leak to LLMs"""
279+ return val
280+
281+ app_context .set (app )
282+ app .mcp_callback_map = CallbackAdapterCollection (app )
283+
284+ with app .server .test_request_context ():
285+ tool = app .mcp_callback_map [0 ].as_mcp_tool
286+ assert (
287+ "sensitive callback docstring text that must not leak to LLMs"
288+ not in tool .description
289+ )
290+
247291 def test_description_includes_output_target (self , simple_app ):
248292 with simple_app .server .test_request_context ():
249293 tool = app_context .get ().mcp_callback_map [0 ].as_mcp_tool
0 commit comments