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,467 @@
1
+ """An app to retrieve detailed parameter information for Panel components.
2
+
3
+ An interactive version of the holoviz_mcp.panel_mcp.server.get_component_parameters 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 Parameters Tool
21
+
22
+ This tool provides **detailed parameter information** for a single Panel component, focusing specifically
23
+ on parameter specifications without the full docstring and signature overhead.
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 Parameters**: Retrieve parameter specifications
31
+
32
+ **Note**: Component names are case-insensitive.
33
+
34
+ ### Key Features
35
+
36
+ This tool is **focused on parameters only**:
37
+ - Returns **parameter specifications** for exactly ONE component
38
+ - Shows **types, defaults, documentation, and constraints** for each parameter
39
+ - Displays parameters in a **sortable, filterable table**
40
+ - Lighter weight than `get_component` (no docstring/signature)
41
+ - Best for **understanding configuration options**
42
+
43
+ ### Difference from get_component
44
+
45
+ | Feature | get_component | get_component_parameters |
46
+ |---------|---------------|-------------------------|
47
+ | **Returns** | Full component details | Parameters only |
48
+ | **Includes** | Name, package, docstring, signature, parameters | Parameters dict only |
49
+ | **Size** | Larger payload | Smaller, focused |
50
+ | **Use Case** | Complete understanding | Configuration focus |
51
+
52
+ Use `get_component_parameters` when you:
53
+ - Already know what the component does
54
+ - Need quick access to parameter options
55
+ - Want to understand configuration possibilities
56
+ - Are building parameter-driven interfaces
57
+
58
+ ### Parameter Information
59
+
60
+ Each parameter shows:
61
+ - **Name**: Parameter identifier
62
+ - **Type**: Parameter type (String, Number, Boolean, Selector, etc.)
63
+ - **Default**: Default value if not specified
64
+ - **Documentation**: Description of what the parameter does
65
+ - **Constraints**: Bounds, available options, regex patterns, readonly/constant flags
66
+
67
+ ### Handling Ambiguous Names
68
+
69
+ When a component name matches multiple packages (e.g., "Button" exists in both "panel" and "panel_material_ui"):
70
+ - The tool will **error** and show all matching module paths
71
+ - Use the **Package filter** to specify which one you want
72
+ - Or use the exact **Module Path** for precision
73
+
74
+ ### Example Usage
75
+
76
+ **Get Panel Button parameters**:
77
+ - Component Name: `Button`
78
+ - Package: `panel`
79
+
80
+ **Get Material UI TextInput parameters**:
81
+ - Component Name: `TextInput`
82
+ - Package: `panel_material_ui`
83
+
84
+ **Get by exact path**:
85
+ - Module Path: `panel.widgets.button.Button`
86
+ - (Leave other fields empty)
87
+
88
+ ### Learn More
89
+
90
+ For more information visit: [HoloViz MCP](https://marcskovmadsen.github.io/holoviz-mcp/).
91
+ """
92
+
93
+
94
+ class GetComponentParametersConfiguration(param.Parameterized):
95
+ """Configuration for Panel get_component_parameters tool."""
96
+
97
+ component_name = param.String(default="Button", label="Component Name")
98
+ module_path = param.String(default="", label="Module Path")
99
+ package = param.Selector(default="panel", objects=[ALL, "panel"], label="Package")
100
+
101
+ get_parameters = param.Event(label="Get Parameters")
102
+
103
+ result = param.Dict(default={}, doc="Component parameters result")
104
+ loading = param.Boolean(default=False, doc="Loading state")
105
+ error_message = param.String(default="", doc="Error message if request fails")
106
+
107
+ def __init__(self, **params):
108
+ super().__init__(**params)
109
+ if pn.state.location:
110
+ pn.state.location.sync(self, ["component_name", "module_path", "package"])
111
+ # Initialize with available packages
112
+ pn.state.execute(self._update_packages)
113
+ # Load default component parameters on startup
114
+ pn.state.execute(self._update_result)
115
+
116
+ async def _update_packages(self):
117
+ """Update the available Panel packages."""
118
+ try:
119
+ result = await call_tool("panel_list_packages", {})
120
+ packages = sorted([p for p in result.data])
121
+ self.param.package.objects = [ALL] + packages
122
+ except Exception as e:
123
+ self.error_message = f"Failed to load packages: {str(e)}"
124
+
125
+ @param.depends("get_parameters", watch=True)
126
+ async def _update_result(self):
127
+ """Execute get_component_parameters and update result."""
128
+ self.loading = True
129
+ self.error_message = ""
130
+ self.result = {}
131
+
132
+ params = {}
133
+
134
+ # Add filters based on what's provided
135
+ if self.component_name.strip():
136
+ params["name"] = self.component_name.strip()
137
+ if self.module_path.strip():
138
+ params["module_path"] = self.module_path.strip()
139
+ if self.package != ALL:
140
+ params["package"] = self.package
141
+
142
+ # At least one filter must be provided
143
+ if not params:
144
+ self.error_message = "Please provide at least a component name, module path, or package"
145
+ self.loading = False
146
+ return
147
+
148
+ try:
149
+ result = await call_tool("panel_get_component_parameters", params)
150
+
151
+ if result and hasattr(result, "structured_content"):
152
+ # get_component_parameters returns a dict of parameter info
153
+ if result.structured_content:
154
+ self.result = result.structured_content
155
+ else:
156
+ self.error_message = "No parameters found for the specified component"
157
+ else:
158
+ self.error_message = "Request returned no data"
159
+
160
+ except Exception as e:
161
+ error_str = str(e)
162
+ # Make error messages more user-friendly
163
+ if "Multiple components found" in error_str:
164
+ self.error_message = f"⚠️ Ambiguous component name.\n\n{error_str}\n\nPlease specify the package or use exact module path."
165
+ elif "No components found" in error_str:
166
+ self.error_message = f"❌ Component not found.\n\n{error_str}"
167
+ else:
168
+ self.error_message = f"Request failed: {error_str}"
169
+ finally:
170
+ self.loading = False
171
+
172
+
173
+ class ComponentParametersViewer(pn.viewable.Viewer):
174
+ """Viewer for displaying component parameters."""
175
+
176
+ result = param.Dict(default={}, allow_refs=True, doc="Component parameters")
177
+
178
+ def __init__(self, **params):
179
+ super().__init__(**params)
180
+
181
+ # Empty state message
182
+ no_parameters_message = pn.pane.Markdown(
183
+ "### No parameters to display.\n\n"
184
+ "Click 'Get Parameters' to retrieve parameter information for a Panel component.\n\n"
185
+ "The default 'Button' component parameters will be loaded automatically.",
186
+ sizing_mode="stretch_width",
187
+ visible=self.is_empty,
188
+ )
189
+
190
+ # Summary statistics cards
191
+ self._stats_row = pn.Row(
192
+ self._create_stat_card("Total Parameters", self._get_param_count, "📊"),
193
+ self._create_stat_card("Required Parameters", self._get_required_count, "⚠️"),
194
+ self._create_stat_card("With Constraints", self._get_constrained_count, "🔒"),
195
+ self._create_stat_card("Readonly Parameters", self._get_readonly_count, "🔐"),
196
+ visible=self.is_not_empty,
197
+ sizing_mode="stretch_width",
198
+ )
199
+
200
+ # Parameters table
201
+ parameters_table = pn.widgets.Tabulator(
202
+ self._get_parameters_df,
203
+ sizing_mode="stretch_both",
204
+ show_index=False,
205
+ disabled=True,
206
+ sortable=True,
207
+ titles={
208
+ "name": "Parameter",
209
+ "type": "Type",
210
+ "default": "Default",
211
+ "doc": "Documentation",
212
+ "constraints": "Constraints",
213
+ },
214
+ formatters={"doc": "textarea"},
215
+ header_filters={
216
+ "name": {"type": "input", "func": "like", "placeholder": "Filter name..."},
217
+ "type": {"type": "list", "valuesLookup": True, "multiselect": True},
218
+ "doc": {"type": "input", "func": "like", "placeholder": "Search docs..."},
219
+ },
220
+ layout="fit_data_table",
221
+ theme="materialize",
222
+ )
223
+
224
+ formatted_view = pmui.Column(
225
+ no_parameters_message,
226
+ self._stats_row,
227
+ pmui.Typography("## Parameter Details", variant="h6", visible=self.is_not_empty),
228
+ parameters_table,
229
+ name="Parameters Table",
230
+ sizing_mode="stretch_width",
231
+ )
232
+
233
+ response_pane = pn.pane.JSON(
234
+ self.param.result,
235
+ theme="dark",
236
+ name="Raw Response",
237
+ depth=1,
238
+ sizing_mode="stretch_width",
239
+ )
240
+
241
+ self._layout = pn.Tabs(formatted_view, response_pane)
242
+
243
+ def _create_stat_card(self, title: str, value_func, icon: str):
244
+ """Create a statistic card."""
245
+ return pmui.Paper(
246
+ pmui.Column(
247
+ pmui.Row(
248
+ pmui.Typography(icon, variant="h4", sx={"marginRight": "10px"}),
249
+ pmui.Column(
250
+ pmui.Typography(title, variant="caption", sx={"color": "text.secondary"}),
251
+ pmui.Typography(value_func, variant="h5", sx={"color": "primary.main"}),
252
+ ),
253
+ ),
254
+ sx={"padding": "15px"},
255
+ ),
256
+ elevation=2,
257
+ sx={"minWidth": "200px"},
258
+ margin=10,
259
+ )
260
+
261
+ @param.depends("result")
262
+ def is_empty(self):
263
+ """Check if there is no result."""
264
+ return not bool(self.result)
265
+
266
+ @param.depends("result")
267
+ def is_not_empty(self):
268
+ """Check if there is a result."""
269
+ return bool(self.result)
270
+
271
+ @param.depends("result")
272
+ def _get_param_count(self):
273
+ """Get total parameter count."""
274
+ return str(len(self.result))
275
+
276
+ @param.depends("result")
277
+ def _get_required_count(self):
278
+ """Get count of parameters with no default."""
279
+ count = sum(1 for p in self.result.values() if p.get("default") is None and not p.get("allow_None", False))
280
+ return str(count)
281
+
282
+ @param.depends("result")
283
+ def _get_constrained_count(self):
284
+ """Get count of parameters with constraints."""
285
+ count = sum(1 for p in self.result.values() if p.get("bounds") is not None or p.get("objects") is not None or p.get("regex") is not None)
286
+ return str(count)
287
+
288
+ @param.depends("result")
289
+ def _get_readonly_count(self):
290
+ """Get count of readonly or constant parameters."""
291
+ count = sum(1 for p in self.result.values() if p.get("readonly", False) or p.get("constant", False))
292
+ return str(count)
293
+
294
+ @param.depends("result")
295
+ def _get_parameters_df(self):
296
+ """Convert parameters dict to DataFrame."""
297
+ if not self.result:
298
+ return pd.DataFrame(columns=["name", "type", "default", "doc", "constraints"])
299
+
300
+ rows = []
301
+ for param_name, param_info in self.result.items():
302
+ # Extract common fields
303
+ param_type = param_info.get("type", "Unknown")
304
+ default = param_info.get("default", "")
305
+ doc = param_info.get("doc", "")
306
+
307
+ # Build constraints string from bounds, objects, etc.
308
+ constraints = []
309
+ if "bounds" in param_info and param_info["bounds"] is not None:
310
+ constraints.append(f"bounds: {param_info['bounds']}")
311
+ if "objects" in param_info and param_info["objects"] is not None:
312
+ objects = param_info["objects"]
313
+ if isinstance(objects, list) and len(objects) > 5:
314
+ constraints.append(f"options: {len(objects)} choices")
315
+ else:
316
+ constraints.append(f"options: {objects}")
317
+ if "regex" in param_info and param_info["regex"] is not None:
318
+ constraints.append(f"regex: {param_info['regex']}")
319
+ if "allow_None" in param_info and param_info["allow_None"]:
320
+ constraints.append("allow_None")
321
+ if "readonly" in param_info and param_info["readonly"]:
322
+ constraints.append("readonly")
323
+ if "constant" in param_info and param_info["constant"]:
324
+ constraints.append("constant")
325
+
326
+ constraint_str = ", ".join(constraints) if constraints else ""
327
+
328
+ rows.append(
329
+ {
330
+ "name": param_name,
331
+ "type": param_type,
332
+ "default": str(default) if default is not None else "",
333
+ "doc": doc or "",
334
+ "constraints": constraint_str,
335
+ }
336
+ )
337
+
338
+ return pd.DataFrame(rows)
339
+
340
+ def __panel__(self):
341
+ """Return the Panel layout."""
342
+ return self._layout
343
+
344
+
345
+ class PanelGetComponentParametersApp(pn.viewable.Viewer):
346
+ """Main application for retrieving Panel component parameters."""
347
+
348
+ title = param.String(default="HoloViz MCP - panel_get_component_parameters Tool Demo")
349
+
350
+ def __init__(self, **params):
351
+ super().__init__(**params)
352
+
353
+ # Create configuration and viewer
354
+ self._config = GetComponentParametersConfiguration()
355
+ self._parameters_viewer = ComponentParametersViewer(result=self._config.param.result)
356
+
357
+ with pn.config.set(sizing_mode="stretch_width"):
358
+ self._name_input = pmui.TextInput.from_param(
359
+ self._config.param.component_name,
360
+ label="Component Name",
361
+ placeholder="e.g., Button, TextInput, Slider...",
362
+ variant="outlined",
363
+ sx={"width": "100%"},
364
+ )
365
+
366
+ self._module_path_input = pmui.TextInput.from_param(
367
+ self._config.param.module_path,
368
+ label="Module Path (optional)",
369
+ placeholder="e.g., panel.widgets.Button...",
370
+ variant="outlined",
371
+ sx={"width": "100%"},
372
+ )
373
+
374
+ self._package_select = pmui.Select.from_param(
375
+ self._config.param.package,
376
+ label="Package (optional)",
377
+ variant="outlined",
378
+ sx={"width": "100%"},
379
+ )
380
+
381
+ self._get_button = pmui.Button.from_param(
382
+ self._config.param.get_parameters,
383
+ label="Get Parameters",
384
+ color="primary",
385
+ variant="contained",
386
+ sx={"width": "100%", "marginTop": "10px"},
387
+ on_click=lambda e: self._config.param.trigger("get_parameters"),
388
+ )
389
+
390
+ # Status indicators
391
+ self._status_pane = pn.pane.Markdown(self._status_text, sizing_mode="stretch_width")
392
+ self._error_pane = pmui.Alert(
393
+ self._error_text,
394
+ alert_type="error",
395
+ visible=pn.rx(lambda msg: bool(msg))(self._config.param.error_message),
396
+ sizing_mode="stretch_width",
397
+ )
398
+
399
+ # Create static layout structure
400
+ self._sidebar = pn.Column(
401
+ pmui.Typography("## Parameter Lookup", variant="h6"),
402
+ self._name_input,
403
+ self._module_path_input,
404
+ self._package_select,
405
+ self._get_button,
406
+ self._error_pane,
407
+ self._status_pane,
408
+ sizing_mode="stretch_width",
409
+ )
410
+
411
+ self._main = pmui.Container(self._parameters_viewer, width_option="xl", margin=10)
412
+
413
+ @param.depends("_config.loading", "_config.result")
414
+ def _status_text(self):
415
+ """Generate status message."""
416
+ if self._config.loading:
417
+ return "_Loading parameter information..._"
418
+ elif self._config.result:
419
+ param_count = len(self._config.result)
420
+ component_name = self._config.component_name or "Component"
421
+ return f"_Successfully loaded **{param_count} parameters** for **{component_name}**_"
422
+ return ""
423
+
424
+ @param.depends("_config.error_message")
425
+ def _error_text(self):
426
+ """Get error message."""
427
+ return self._config.error_message
428
+
429
+ def __panel__(self):
430
+ """Return the main page layout."""
431
+ with pn.config.set(sizing_mode="stretch_width"):
432
+ # About button and dialog
433
+ about_button = pmui.IconButton(
434
+ label="About",
435
+ icon="info",
436
+ description="Click to learn about the Panel Get Component Parameters Tool.",
437
+ sizing_mode="fixed",
438
+ color="light",
439
+ margin=(10, 0),
440
+ )
441
+ about = pmui.Dialog(ABOUT, close_on_click=True, width=0)
442
+ about_button.js_on_click(args={"about": about}, code="about.data.open = true")
443
+
444
+ # GitHub button
445
+ github_button = pmui.IconButton(
446
+ label="Github",
447
+ icon="star",
448
+ description="Give HoloViz-MCP a star on GitHub",
449
+ sizing_mode="fixed",
450
+ color="light",
451
+ margin=(10, 0),
452
+ href="https://github.com/MarcSkovMadsen/holoviz-mcp",
453
+ target="_blank",
454
+ )
455
+
456
+ return pmui.Page(
457
+ title=self.title,
458
+ site_url="./",
459
+ sidebar=[self._sidebar],
460
+ header=[pn.Row(pn.Spacer(), about_button, github_button, align="end")],
461
+ main=[about, self._main],
462
+ )
463
+
464
+
465
+ if pn.state.served:
466
+ pn.extension("tabulator")
467
+ PanelGetComponentParametersApp().servable()