holoviz-mcp 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. holoviz_mcp/__init__.py +18 -0
  2. holoviz_mcp/apps/__init__.py +1 -0
  3. holoviz_mcp/apps/configuration_viewer.py +116 -0
  4. holoviz_mcp/apps/holoviz_get_best_practices.py +173 -0
  5. holoviz_mcp/apps/holoviz_search.py +319 -0
  6. holoviz_mcp/apps/hvplot_get_docstring.py +255 -0
  7. holoviz_mcp/apps/hvplot_get_signature.py +252 -0
  8. holoviz_mcp/apps/hvplot_list_plot_types.py +83 -0
  9. holoviz_mcp/apps/panel_get_component.py +496 -0
  10. holoviz_mcp/apps/panel_get_component_parameters.py +467 -0
  11. holoviz_mcp/apps/panel_list_components.py +311 -0
  12. holoviz_mcp/apps/panel_list_packages.py +71 -0
  13. holoviz_mcp/apps/panel_search_components.py +312 -0
  14. holoviz_mcp/cli.py +75 -0
  15. holoviz_mcp/client.py +94 -0
  16. holoviz_mcp/config/__init__.py +29 -0
  17. holoviz_mcp/config/config.yaml +178 -0
  18. holoviz_mcp/config/loader.py +316 -0
  19. holoviz_mcp/config/models.py +208 -0
  20. holoviz_mcp/config/resources/best-practices/holoviews.md +423 -0
  21. holoviz_mcp/config/resources/best-practices/hvplot.md +465 -0
  22. holoviz_mcp/config/resources/best-practices/panel-material-ui.md +318 -0
  23. holoviz_mcp/config/resources/best-practices/panel.md +562 -0
  24. holoviz_mcp/config/schema.json +228 -0
  25. holoviz_mcp/holoviz_mcp/__init__.py +1 -0
  26. holoviz_mcp/holoviz_mcp/data.py +970 -0
  27. holoviz_mcp/holoviz_mcp/models.py +21 -0
  28. holoviz_mcp/holoviz_mcp/pages_design.md +407 -0
  29. holoviz_mcp/holoviz_mcp/server.py +220 -0
  30. holoviz_mcp/hvplot_mcp/__init__.py +1 -0
  31. holoviz_mcp/hvplot_mcp/server.py +146 -0
  32. holoviz_mcp/panel_mcp/__init__.py +17 -0
  33. holoviz_mcp/panel_mcp/data.py +319 -0
  34. holoviz_mcp/panel_mcp/models.py +124 -0
  35. holoviz_mcp/panel_mcp/server.py +443 -0
  36. holoviz_mcp/py.typed +0 -0
  37. holoviz_mcp/serve.py +36 -0
  38. holoviz_mcp/server.py +86 -0
  39. holoviz_mcp/shared/__init__.py +1 -0
  40. holoviz_mcp/shared/extract_tools.py +74 -0
  41. holoviz_mcp/thumbnails/configuration_viewer.png +0 -0
  42. holoviz_mcp/thumbnails/holoviz_get_best_practices.png +0 -0
  43. holoviz_mcp/thumbnails/holoviz_search.png +0 -0
  44. holoviz_mcp/thumbnails/hvplot_get_docstring.png +0 -0
  45. holoviz_mcp/thumbnails/hvplot_get_signature.png +0 -0
  46. holoviz_mcp/thumbnails/hvplot_list_plot_types.png +0 -0
  47. holoviz_mcp/thumbnails/panel_get_component.png +0 -0
  48. holoviz_mcp/thumbnails/panel_get_component_parameters.png +0 -0
  49. holoviz_mcp/thumbnails/panel_list_components.png +0 -0
  50. holoviz_mcp/thumbnails/panel_list_packages.png +0 -0
  51. holoviz_mcp/thumbnails/panel_search_components.png +0 -0
  52. holoviz_mcp-0.4.0.dist-info/METADATA +216 -0
  53. holoviz_mcp-0.4.0.dist-info/RECORD +56 -0
  54. holoviz_mcp-0.4.0.dist-info/WHEEL +4 -0
  55. holoviz_mcp-0.4.0.dist-info/entry_points.txt +2 -0
  56. holoviz_mcp-0.4.0.dist-info/licenses/LICENSE.txt +30 -0
@@ -0,0 +1,496 @@
1
+ """An app to retrieve complete details about a Panel component.
2
+
3
+ An interactive version of the holoviz_mcp.panel_mcp.server.get_component tool.
4
+ """
5
+
6
+ import pandas as pd
7
+ import panel as pn
8
+ import panel_material_ui as pmui
9
+ import param
10
+
11
+ from holoviz_mcp.client import call_tool
12
+
13
+ pn.extension()
14
+
15
+ pn.pane.Markdown.disable_anchors = True
16
+
17
+ ALL = "All Packages"
18
+
19
+ ABOUT = """
20
+ ## Panel Get Component Tool
21
+
22
+ This tool provides detailed information about a **single** Panel component, including its complete docstring,
23
+ initialization signature, and parameter specifications.
24
+
25
+ ### How to Use This Tool
26
+
27
+ 1. **Component Name**: Enter the component name (e.g., "Button", "TextInput", "Slider")
28
+ 2. **Module Path** (optional): Enter exact module path for precision (e.g., "panel.widgets.Button")
29
+ 3. **Package** (optional): Select a package to disambiguate components with same name
30
+ 4. **Click Get Component**: Retrieve complete component details
31
+
32
+ **Note**: Component names are case-insensitive.
33
+
34
+ ### Key Features
35
+
36
+ Unlike `list_components` which returns summaries, this tool:
37
+ - Returns **complete details** for exactly ONE component
38
+ - Includes **full docstring** with usage examples
39
+ - Shows **initialization signature** with all parameters
40
+ - Provides **detailed parameter specifications** (types, defaults, constraints)
41
+ - Best for **understanding how to use** a specific component
42
+
43
+ ### Handling Ambiguous Names
44
+
45
+ When a component name matches multiple packages (e.g., "Button" exists in both "panel" and "panel_material_ui"):
46
+ - The tool will **error** and show all matching module paths
47
+ - Use the **Package filter** to specify which one you want
48
+ - Or use the exact **Module Path** for precision
49
+
50
+ ### Results Display
51
+
52
+ The component details are organized into expandable sections:
53
+ - **Overview**: Name, package, module path, and description
54
+ - **Docstring**: Complete documentation with usage examples
55
+ - **Parameters**: Table of all parameters with types, defaults, and documentation
56
+ - **Signature**: Initialization method signature
57
+
58
+ ### Example Usage
59
+
60
+ **Get Panel's Button**:
61
+ - Component Name: `Button`
62
+ - Package: `panel`
63
+
64
+ **Get Material UI Button**:
65
+ - Component Name: `Button`
66
+ - Package: `panel_material_ui`
67
+
68
+ **Get by exact path**:
69
+ - Module Path: `panel.widgets.button.Button`
70
+ - (Leave other fields empty)
71
+
72
+ ### Learn More
73
+
74
+ For more information visit: [HoloViz MCP](https://marcskovmadsen.github.io/holoviz-mcp/).
75
+ """
76
+
77
+
78
+ class GetComponentConfiguration(param.Parameterized):
79
+ """Configuration for Panel get_component tool."""
80
+
81
+ component_name = param.String(default="Button", label="Component Name")
82
+ module_path = param.String(default="", label="Module Path")
83
+ package = param.Selector(default="panel", objects=[ALL, "panel"], label="Package")
84
+
85
+ get_component = param.Event(label="Get Component")
86
+
87
+ result = param.Dict(default={}, doc="Component details result")
88
+ loading = param.Boolean(default=False, doc="Loading state")
89
+ error_message = param.String(default="", doc="Error message if request fails")
90
+
91
+ def __init__(self, **params):
92
+ super().__init__(**params)
93
+ if pn.state.location:
94
+ pn.state.location.sync(self, ["component_name", "module_path", "package"])
95
+ # Initialize with available packages
96
+ pn.state.execute(self._update_packages)
97
+ # Load default component on startup
98
+ pn.state.execute(self._update_result)
99
+
100
+ async def _update_packages(self):
101
+ """Update the available Panel packages."""
102
+ try:
103
+ result = await call_tool("panel_list_packages", {})
104
+ packages = sorted([p for p in result.data])
105
+ self.param.package.objects = [ALL] + packages
106
+ except Exception as e:
107
+ self.error_message = f"Failed to load packages: {str(e)}"
108
+
109
+ @param.depends("get_component", watch=True)
110
+ async def _update_result(self):
111
+ """Execute get_component and update result."""
112
+ self.loading = True
113
+ self.error_message = ""
114
+ self.result = {}
115
+
116
+ params = {}
117
+
118
+ # Add filters based on what's provided
119
+ if self.component_name.strip():
120
+ params["name"] = self.component_name.strip()
121
+ if self.module_path.strip():
122
+ params["module_path"] = self.module_path.strip()
123
+ if self.package != ALL:
124
+ params["package"] = self.package
125
+
126
+ # At least one filter must be provided
127
+ if not params:
128
+ self.error_message = "Please provide at least a component name, module path, or package"
129
+ self.loading = False
130
+ return
131
+
132
+ try:
133
+ result = await call_tool("panel_get_component", params)
134
+ if result and hasattr(result, "structured_content"):
135
+ # get_component returns a single ComponentDetails object, not a list
136
+ if result.structured_content:
137
+ self.result = result.structured_content
138
+ else:
139
+ self.error_message = "No component found matching the criteria"
140
+ else:
141
+ self.error_message = "Request returned no data"
142
+
143
+ except Exception as e:
144
+ error_str = str(e)
145
+ # Make error messages more user-friendly
146
+ if "Multiple components found" in error_str:
147
+ self.error_message = f"⚠️ Ambiguous component name.\n\n{error_str}\n\nPlease specify the package or use exact module path."
148
+ elif "No components found" in error_str:
149
+ self.error_message = f"❌ Component not found.\n\n{error_str}"
150
+ else:
151
+ self.error_message = f"Request failed: {error_str}"
152
+ finally:
153
+ self.loading = False
154
+
155
+
156
+ class ComponentDetailsViewer(pn.viewable.Viewer):
157
+ """Viewer for displaying complete component details."""
158
+
159
+ result = param.Dict(default={}, allow_refs=True, doc="Component details")
160
+
161
+ def __init__(self, **params):
162
+ super().__init__(**params)
163
+
164
+ # Empty state message
165
+ no_component_message = pn.pane.Markdown(
166
+ "### No component details to display.\n\n"
167
+ "Click 'Get Component' to retrieve information about a Panel component.\n\n"
168
+ "The default 'Button' component will be loaded automatically.",
169
+ sizing_mode="stretch_width",
170
+ visible=self.is_empty,
171
+ )
172
+
173
+ # Create accordion sections with reactive content
174
+ self._overview_section = self._create_overview_section()
175
+ self._docstring_section = self._create_docstring_section()
176
+ self._parameters_section = self._create_parameters_section()
177
+ self._signature_section = self._create_signature_section()
178
+
179
+ accordion = pmui.Accordion(
180
+ ("📋 Overview", self._overview_section),
181
+ ("📖 Docstring", self._docstring_section),
182
+ ("🔧 Signature", self._signature_section),
183
+ ("⚙️ Parameters", self._parameters_section),
184
+ active=[0, 1], # Open first two sections by default
185
+ visible=self.is_not_empty,
186
+ sizing_mode="stretch_width",
187
+ )
188
+
189
+ formatted_view = pmui.Column(
190
+ no_component_message,
191
+ accordion,
192
+ name="Human Readable View",
193
+ sizing_mode="stretch_width",
194
+ )
195
+
196
+ response_pane = pn.pane.JSON(
197
+ self.param.result,
198
+ theme="dark",
199
+ name="Raw Response",
200
+ depth=2,
201
+ sizing_mode="stretch_width",
202
+ )
203
+
204
+ self._layout = pn.Tabs(formatted_view, response_pane)
205
+
206
+ def _create_overview_section(self):
207
+ """Create overview section with component metadata."""
208
+ return pmui.Column(
209
+ # Component Name
210
+ pmui.Typography("Component Name", variant="subtitle2", sx={"color": "text.secondary", "marginBottom": "5px"}),
211
+ pmui.Typography(self._get_name, variant="h6", sx={"color": "primary.main", "marginBottom": "15px"}),
212
+ pmui.Divider(),
213
+ # Package
214
+ pmui.Typography("Package", variant="subtitle2", sx={"color": "text.secondary", "marginTop": "15px", "marginBottom": "5px"}),
215
+ pmui.Typography(self._get_package, variant="body1", sx={"marginBottom": "15px"}),
216
+ pmui.Divider(),
217
+ # Module Path
218
+ pmui.Typography("Module Path", variant="subtitle2", sx={"color": "text.secondary", "marginTop": "15px", "marginBottom": "5px"}),
219
+ pmui.Typography(self._get_module_path, variant="body2", sx={"fontFamily": "monospace", "marginBottom": "15px"}),
220
+ pmui.Divider(),
221
+ # Description
222
+ pmui.Typography("Description", variant="subtitle2", sx={"color": "text.secondary", "marginTop": "15px", "marginBottom": "5px"}),
223
+ pn.pane.Markdown(self._get_description),
224
+ )
225
+
226
+ def _create_docstring_section(self):
227
+ """Create docstring section."""
228
+ return pn.pane.Markdown(
229
+ self._get_docstring,
230
+ sizing_mode="stretch_width",
231
+ )
232
+
233
+ def _create_parameters_section(self):
234
+ """Create parameters section with tabulator."""
235
+ return pmui.Column(
236
+ pn.pane.Markdown(self._get_param_count, sizing_mode="stretch_width"),
237
+ pn.widgets.Tabulator(
238
+ self._get_parameters_df,
239
+ sizing_mode="stretch_width",
240
+ show_index=False,
241
+ disabled=True,
242
+ sortable=True,
243
+ titles={
244
+ "name": "Parameter",
245
+ "type": "Type",
246
+ "default": "Default",
247
+ "doc": "Documentation",
248
+ "constraints": "Constraints",
249
+ },
250
+ formatters={"doc": "textarea"},
251
+ header_filters=True,
252
+ ),
253
+ sizing_mode="stretch_width",
254
+ )
255
+
256
+ def _create_signature_section(self):
257
+ """Create signature section."""
258
+ return pmui.Column(
259
+ pmui.Typography("Initialization Signature", variant="subtitle2", sx={"color": "text.secondary", "marginBottom": "10px"}),
260
+ pn.pane.Markdown(
261
+ self._get_signature_markdown,
262
+ sizing_mode="stretch_width",
263
+ ),
264
+ )
265
+
266
+ @param.depends("result")
267
+ def is_empty(self):
268
+ """Check if there is no result."""
269
+ return not bool(self.result)
270
+
271
+ @param.depends("result")
272
+ def is_not_empty(self):
273
+ """Check if there is a result."""
274
+ return bool(self.result)
275
+
276
+ @param.depends("result")
277
+ def _get_name(self):
278
+ """Get component name."""
279
+ return self.result.get("name", "N/A")
280
+
281
+ @param.depends("result")
282
+ def _get_package(self):
283
+ """Get component package."""
284
+ return self.result.get("package", "N/A")
285
+
286
+ @param.depends("result")
287
+ def _get_module_path(self):
288
+ """Get component module path."""
289
+ return self.result.get("module_path", "N/A")
290
+
291
+ @param.depends("result")
292
+ def _get_description(self):
293
+ """Get component description."""
294
+ return self.result.get("description", "_No description available._")
295
+
296
+ @param.depends("result")
297
+ def _get_docstring(self):
298
+ """Get component docstring."""
299
+ docstring = self.result.get("docstring", "")
300
+ if not docstring:
301
+ return "_No docstring available._"
302
+ # Wrap in code block if it looks like plain text
303
+ if not docstring.strip().startswith("#"):
304
+ return f"```markdown\n{docstring}\n```"
305
+ return docstring
306
+
307
+ @param.depends("result")
308
+ def _get_param_count(self):
309
+ """Get parameter count message."""
310
+ params = self.result.get("parameters", {})
311
+ count = len(params)
312
+ return f"**{count} parameter(s)** defined for this component:"
313
+
314
+ @param.depends("result")
315
+ def _get_parameters_df(self):
316
+ """Convert parameters dict to DataFrame."""
317
+ params = self.result.get("parameters", {})
318
+ if not params:
319
+ return pd.DataFrame(columns=["name", "type", "default", "doc", "constraints"])
320
+
321
+ rows = []
322
+ for param_name, param_info in params.items():
323
+ # Extract common fields
324
+ param_type = param_info.get("type", "Unknown")
325
+ default = param_info.get("default", "")
326
+ doc = param_info.get("doc", "")
327
+
328
+ # Build constraints string from bounds, objects, etc.
329
+ constraints = []
330
+ if "bounds" in param_info and param_info["bounds"] is not None:
331
+ constraints.append(f"bounds: {param_info['bounds']}")
332
+ if "objects" in param_info and param_info["objects"] is not None:
333
+ objects = param_info["objects"]
334
+ if isinstance(objects, list) and len(objects) > 5:
335
+ constraints.append(f"objects: {len(objects)} options")
336
+ else:
337
+ constraints.append(f"objects: {objects}")
338
+ if "regex" in param_info and param_info["regex"] is not None:
339
+ constraints.append(f"regex: {param_info['regex']}")
340
+ if "allow_None" in param_info and param_info["allow_None"]:
341
+ constraints.append("allow_None: True")
342
+ if "readonly" in param_info and param_info["readonly"]:
343
+ constraints.append("readonly: True")
344
+ if "constant" in param_info and param_info["constant"]:
345
+ constraints.append("constant: True")
346
+
347
+ constraint_str = ", ".join(constraints) if constraints else ""
348
+
349
+ rows.append(
350
+ {
351
+ "name": param_name,
352
+ "type": param_type,
353
+ "default": str(default) if default is not None else "",
354
+ "doc": doc or "",
355
+ "constraints": constraint_str,
356
+ }
357
+ )
358
+
359
+ return pd.DataFrame(rows)
360
+
361
+ @param.depends("result")
362
+ def _get_signature_markdown(self):
363
+ """Get signature as markdown code block."""
364
+ signature = self.result.get("init_signature", "")
365
+ if not signature:
366
+ return "_No signature available._"
367
+ return f"```python\n{signature}\n```"
368
+
369
+ def __panel__(self):
370
+ """Return the Panel layout."""
371
+ return self._layout
372
+
373
+
374
+ class PanelGetComponentApp(pn.viewable.Viewer):
375
+ """Main application for retrieving Panel component details."""
376
+
377
+ title = param.String(default="HoloViz MCP - panel_get_component Tool Demo")
378
+
379
+ def __init__(self, **params):
380
+ super().__init__(**params)
381
+
382
+ # Create configuration and viewer
383
+ self._config = GetComponentConfiguration()
384
+ self._component_viewer = ComponentDetailsViewer(result=self._config.param.result)
385
+
386
+ with pn.config.set(sizing_mode="stretch_width"):
387
+ self._name_input = pmui.TextInput.from_param(
388
+ self._config.param.component_name,
389
+ label="Component Name",
390
+ placeholder="e.g., Button, TextInput, Slider...",
391
+ variant="outlined",
392
+ sx={"width": "100%"},
393
+ )
394
+
395
+ self._module_path_input = pmui.TextInput.from_param(
396
+ self._config.param.module_path,
397
+ label="Module Path (optional)",
398
+ placeholder="e.g., panel.widgets.Button...",
399
+ variant="outlined",
400
+ sx={"width": "100%"},
401
+ )
402
+
403
+ self._package_select = pmui.Select.from_param(
404
+ self._config.param.package,
405
+ label="Package (optional)",
406
+ variant="outlined",
407
+ sx={"width": "100%"},
408
+ )
409
+
410
+ self._get_button = pmui.Button.from_param(
411
+ self._config.param.get_component,
412
+ label="Get Component",
413
+ color="primary",
414
+ variant="contained",
415
+ sx={"width": "100%", "marginTop": "10px"},
416
+ on_click=lambda e: self._config.param.trigger("get_component"),
417
+ )
418
+
419
+ # Status indicators
420
+ self._status_pane = pn.pane.Markdown(self._status_text, sizing_mode="stretch_width")
421
+ self._error_pane = pmui.Alert(
422
+ self._error_text,
423
+ alert_type="error",
424
+ visible=pn.rx(bool)(self._config.param.error_message),
425
+ sizing_mode="stretch_width",
426
+ )
427
+
428
+ # Create static layout structure
429
+ self._sidebar = pn.Column(
430
+ pmui.Typography("## Component Lookup", variant="h6"),
431
+ self._name_input,
432
+ self._module_path_input,
433
+ self._package_select,
434
+ self._get_button,
435
+ self._error_pane,
436
+ self._status_pane,
437
+ sizing_mode="stretch_width",
438
+ )
439
+
440
+ self._main = pmui.Container(self._component_viewer, width_option="xl", margin=10)
441
+
442
+ @param.depends("_config.loading", "_config.result")
443
+ def _status_text(self):
444
+ """Generate status message."""
445
+ if self._config.loading:
446
+ return "_Loading component details..._"
447
+ elif self._config.result:
448
+ name = self._config.result.get("name", "Component")
449
+ package = self._config.result.get("package", "")
450
+ return f"_Successfully loaded **{name}** from **{package}**_"
451
+ return ""
452
+
453
+ @param.depends("_config.error_message")
454
+ def _error_text(self):
455
+ """Get error message."""
456
+ return self._config.error_message
457
+
458
+ def __panel__(self):
459
+ """Return the main page layout."""
460
+ with pn.config.set(sizing_mode="stretch_width"):
461
+ # About button and dialog
462
+ about_button = pmui.IconButton(
463
+ label="About",
464
+ icon="info",
465
+ description="Click to learn about the Panel Get Component Tool.",
466
+ sizing_mode="fixed",
467
+ color="light",
468
+ margin=(10, 0),
469
+ )
470
+ about = pmui.Dialog(ABOUT, close_on_click=True, width=0)
471
+ about_button.js_on_click(args={"about": about}, code="about.data.open = true")
472
+
473
+ # GitHub button
474
+ github_button = pmui.IconButton(
475
+ label="Github",
476
+ icon="star",
477
+ description="Give HoloViz-MCP a star on GitHub",
478
+ sizing_mode="fixed",
479
+ color="light",
480
+ margin=(10, 0),
481
+ href="https://github.com/MarcSkovMadsen/holoviz-mcp",
482
+ target="_blank",
483
+ )
484
+
485
+ return pmui.Page(
486
+ title=self.title,
487
+ site_url="./",
488
+ sidebar=[self._sidebar],
489
+ header=[pn.Row(pn.Spacer(), about_button, github_button, align="end")],
490
+ main=[about, self._main],
491
+ )
492
+
493
+
494
+ if pn.state.served:
495
+ pn.extension("tabulator")
496
+ PanelGetComponentApp().servable()