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,443 @@
1
+ """[Panel](https://panel.holoviz.org/) MCP Server.
2
+
3
+ This MCP server provides tools, resources and prompts for using Panel to develop quick, interactive
4
+ applications, tools and dashboards in Python using best practices.
5
+
6
+ Use this server to access:
7
+
8
+ - Panel Best Practices: Learn how to use Panel effectively.
9
+ - Panel Components: Get information about specific Panel components like widgets (input), panes (output) and layouts.
10
+ """
11
+
12
+ from importlib.metadata import distributions
13
+
14
+ from fastmcp import Context
15
+ from fastmcp import FastMCP
16
+
17
+ from holoviz_mcp.config.loader import get_config
18
+ from holoviz_mcp.panel_mcp.data import get_components as _get_components_org
19
+ from holoviz_mcp.panel_mcp.models import ComponentDetails
20
+ from holoviz_mcp.panel_mcp.models import ComponentSummary
21
+ from holoviz_mcp.panel_mcp.models import ComponentSummarySearchResult
22
+ from holoviz_mcp.panel_mcp.models import ParameterInfo
23
+
24
+ # Create the FastMCP server
25
+ mcp = FastMCP(
26
+ name="panel",
27
+ instructions="""
28
+ [Panel](https://panel.holoviz.org/) MCP Server.
29
+
30
+ This MCP server provides tools, resources and prompts for using Panel to develop quick, interactive
31
+ applications, tools and dashboards in Python using best practices.
32
+
33
+ DO use this server to search for specific Panel components and access detailed information including docstrings and parameter information.
34
+ """,
35
+ )
36
+
37
+ _config = get_config()
38
+
39
+
40
+ async def _list_packages_depending_on(target_package: str, ctx: Context) -> list[str]:
41
+ """
42
+ Find all installed packages that depend on a given package.
43
+
44
+ This is a helper function that searches through installed packages to find
45
+ those that have the target package as a dependency. Used to discover
46
+ Panel-related packages in the environment.
47
+
48
+ Parameters
49
+ ----------
50
+ target_package : str
51
+ The name of the package to search for dependencies on (e.g., 'panel').
52
+ ctx : Context
53
+ FastMCP context for logging and debugging.
54
+
55
+ Returns
56
+ -------
57
+ list[str]
58
+ Sorted list of package names that depend on the target package.
59
+ """
60
+ dependent_packages = []
61
+
62
+ for dist in distributions():
63
+ if dist.requires:
64
+ dist_name = dist.metadata["Name"]
65
+ await ctx.debug(f"Checking package: {dist_name} for dependencies on {target_package}")
66
+ for requirement_str in dist.requires:
67
+ if "extra ==" in requirement_str:
68
+ continue
69
+ package_name = requirement_str.split()[0].split(";")[0].split(">=")[0].split("==")[0].split("!=")[0].split("<")[0].split(">")[0].split("~")[0]
70
+ if package_name.lower() == target_package.lower():
71
+ dependent_packages.append(dist_name.replace("-", "_"))
72
+ break
73
+
74
+ return sorted(set(dependent_packages))
75
+
76
+
77
+ COMPONENTS: list[ComponentDetails] = []
78
+
79
+
80
+ async def _get_all_components(ctx: Context) -> list[ComponentDetails]:
81
+ """
82
+ Get all available Panel components from discovered packages.
83
+
84
+ This function initializes and caches the global COMPONENTS list by:
85
+ 1. Discovering all packages that depend on Panel
86
+ 2. Importing those packages to register their components
87
+ 3. Collecting detailed information about all Panel components
88
+
89
+ This is called lazily to populate the component cache when needed.
90
+
91
+ Parameters
92
+ ----------
93
+ ctx : Context
94
+ FastMCP context for logging and debugging.
95
+
96
+ Returns
97
+ -------
98
+ list[ComponentDetails]
99
+ Complete list of all discovered Panel components with detailed metadata.
100
+ """
101
+ global COMPONENTS
102
+ if not COMPONENTS:
103
+ packages_depending_on_panel = await _list_packages_depending_on("panel", ctx=ctx)
104
+
105
+ await ctx.info(f"Discovered {len(packages_depending_on_panel)} packages depending on Panel: {packages_depending_on_panel}")
106
+
107
+ for package in packages_depending_on_panel:
108
+ try:
109
+ __import__(package)
110
+ except ImportError as e:
111
+ await ctx.warning(f"Discovered but failed to import {package}: {e}")
112
+
113
+ COMPONENTS = _get_components_org()
114
+
115
+ return COMPONENTS
116
+
117
+
118
+ @mcp.tool
119
+ async def list_packages(ctx: Context) -> list[str]:
120
+ """
121
+ List all installed packages that provide Panel UI components.
122
+
123
+ Use this tool to discover what Panel-related packages are available in your environment.
124
+ This helps you understand which packages you can use in the 'package' parameter of other tools.
125
+
126
+ Parameters
127
+ ----------
128
+ ctx : Context
129
+ FastMCP context (automatically provided by the MCP framework).
130
+
131
+ Returns
132
+ -------
133
+ list[str]
134
+ List of package names that provide Panel components, sorted alphabetically.
135
+ Examples: ["panel"] or ["panel", "panel_material_ui"]
136
+
137
+ Examples
138
+ --------
139
+ Use this tool to see available packages:
140
+ >>> list_packages()
141
+ ["panel", "panel_material_ui"]
142
+
143
+ Then use those package names in other tools:
144
+ >>> list_components(package="panel_material_ui")
145
+ >>> search("button", package="panel")
146
+ """
147
+ return sorted(set(component.package for component in await _get_all_components(ctx)))
148
+
149
+
150
+ @mcp.tool
151
+ async def search_components(ctx: Context, query: str, package: str | None = None, limit: int = 10) -> list[ComponentSummarySearchResult]:
152
+ """
153
+ Search for Panel components by search query and optional package filter.
154
+
155
+ Use this tool to find components when you don't know the exact name but have keywords.
156
+ The search looks through component names, module paths, and documentation to find matches.
157
+
158
+ Parameters
159
+ ----------
160
+ ctx : Context
161
+ FastMCP context (automatically provided by the MCP framework).
162
+ query : str
163
+ Search term to look for. Can be component names, functionality keywords, or descriptions.
164
+ Examples: "button", "input", "text", "chart", "plot", "slider", "select"
165
+ package : str, optional
166
+ Package name to filter results. If None, searches all packages.
167
+ Examples: "hvplot", "panel", or "panel_material_ui"
168
+ limit : int, optional
169
+ Maximum number of results to return. Default is 10.
170
+
171
+ Returns
172
+ -------
173
+ list[ComponentSummarySearchResult]
174
+ List of matching components with relevance scores (0-100, where 100 is exact match).
175
+ Results are sorted by relevance score in descending order.
176
+
177
+ Examples
178
+ --------
179
+ Search for button components:
180
+ >>> search_components("button")
181
+ [ComponentSummarySearchResult(name="Button", package="panel", relevance_score=80, ...)]
182
+
183
+ Search within a specific package:
184
+ >>> search_components("input", package="panel_material_ui")
185
+ [ComponentSummarySearchResult(name="TextInput", package="panel_material_ui", ...)]
186
+
187
+ Find chart components with limited results:
188
+ >>> search_components("chart", limit=5)
189
+ [ComponentSummarySearchResult(name="Bokeh", package="panel", ...)]
190
+ """
191
+ query_lower = query.lower()
192
+
193
+ matches = []
194
+ for component in await _get_all_components(ctx=ctx):
195
+ score = 0
196
+ if package and component.package.lower() != package.lower():
197
+ continue
198
+
199
+ if component.name.lower() == query_lower or component.module_path.lower() == query_lower:
200
+ score = 100
201
+ elif query_lower in component.name.lower():
202
+ score = 80
203
+ elif query_lower in component.module_path.lower():
204
+ score = 60
205
+ elif query_lower in component.docstring.lower():
206
+ score = 40
207
+ elif any(word in component.docstring.lower() for word in query_lower.split()):
208
+ score = 20
209
+
210
+ if score > 0:
211
+ matches.append(ComponentSummarySearchResult.from_component(component=component, relevance_score=score))
212
+
213
+ matches.sort(key=lambda x: x.relevance_score, reverse=True)
214
+ if len(matches) > limit:
215
+ matches = matches[:limit]
216
+
217
+ return matches
218
+
219
+
220
+ async def _get_component(ctx: Context, name: str | None = None, module_path: str | None = None, package: str | None = None) -> list[ComponentDetails]:
221
+ """
222
+ Get component details based on filtering criteria.
223
+
224
+ This is an internal function used by the public component tools to filter
225
+ and retrieve components based on name, module path, and package criteria.
226
+
227
+ Parameters
228
+ ----------
229
+ ctx : Context
230
+ FastMCP context for logging and debugging.
231
+ name : str, optional
232
+ Component name to filter by (case-insensitive). If None, all components match.
233
+ module_path : str, optional
234
+ Module path prefix to filter by. If None, all components match.
235
+ package : str, optional
236
+ Package name to filter by. For example "panel" or "panel_material_ui". If None, all components match.
237
+
238
+ Returns
239
+ -------
240
+ list[ComponentDetails]
241
+ List of components matching the specified criteria.
242
+ """
243
+ components_list = []
244
+
245
+ for component in await _get_all_components(ctx=ctx):
246
+ if name and component.name.lower() != name.lower():
247
+ continue
248
+ if package and component.package != package:
249
+ continue
250
+ if module_path and not component.module_path.startswith(module_path):
251
+ continue
252
+ components_list.append(component)
253
+
254
+ return components_list
255
+
256
+
257
+ @mcp.tool
258
+ async def list_components(ctx: Context, name: str | None = None, module_path: str | None = None, package: str | None = None) -> list[ComponentSummary]:
259
+ """
260
+ Get a summary list of Panel components without detailed docstring and parameter information.
261
+
262
+ Use this tool to get an overview of available Panel components when you want to browse
263
+ or discover components without needing full parameter details. This is faster than
264
+ get_component and provides just the essential information.
265
+
266
+ Parameters
267
+ ----------
268
+ ctx : Context
269
+ FastMCP context (automatically provided by the MCP framework).
270
+ name : str, optional
271
+ Component name to filter by (case-insensitive). If None, returns all components.
272
+ Examples: "Button", "TextInput", "Slider"
273
+ module_path : str, optional
274
+ Module path prefix to filter by. If None, returns all components.
275
+ Examples: "panel.widgets" to get all widgets, "panel.pane" to get all panes
276
+ package : str, optional
277
+ Package name to filter by. If None, returns all components.
278
+ Examples: "hvplot", "panel" or "panel_material_ui"
279
+
280
+ Returns
281
+ -------
282
+ list[ComponentSummary]
283
+ List of component summaries containing name, package, description, and module path.
284
+ No parameter details are included for faster responses.
285
+
286
+ Examples
287
+ --------
288
+ Get all available components:
289
+ >>> list_components()
290
+ [ComponentSummary(name="Button", package="panel", description="A clickable button widget", ...)]
291
+
292
+ Get all Material UI components:
293
+ >>> list_components(package="panel_material_ui")
294
+ [ComponentSummary(name="Button", package="panel_material_ui", ...)]
295
+
296
+ Get all Button components from all packages:
297
+ >>> list_components(name="Button")
298
+ [ComponentSummary(name="Button", package="panel", ...), ComponentSummary(name="Button", package="panel_material_ui", ...)]
299
+ """
300
+ components_list = []
301
+
302
+ for component in await _get_all_components(ctx=ctx):
303
+ if name and component.name.lower() != name.lower():
304
+ continue
305
+ if package and component.package != package:
306
+ continue
307
+ if module_path and not component.module_path.startswith(module_path):
308
+ continue
309
+ components_list.append(component.to_base())
310
+
311
+ return components_list
312
+
313
+
314
+ @mcp.tool
315
+ async def get_component(ctx: Context, name: str | None = None, module_path: str | None = None, package: str | None = None) -> ComponentDetails:
316
+ """
317
+ Get complete details about a single Panel component including docstring and parameters.
318
+
319
+ Use this tool when you need full information about a specific Panel component, including
320
+ its docstring, parameter specifications, and initialization signature. This is the most
321
+ comprehensive tool for component information.
322
+
323
+ IMPORTANT: This tool returns exactly one component. If your criteria match multiple components,
324
+ you'll get an error asking you to be more specific.
325
+
326
+ Parameters
327
+ ----------
328
+ ctx : Context
329
+ FastMCP context (automatically provided by the MCP framework).
330
+ name : str, optional
331
+ Component name to match (case-insensitive). If None, must specify other criteria.
332
+ Examples: "Button", "TextInput", "Slider"
333
+ module_path : str, optional
334
+ Full module path to match. If None, uses name and package to find component.
335
+ Examples: "panel.widgets.Button", "panel_material_ui.Button"
336
+ package : str, optional
337
+ Package name to filter by. If None, searches all packages.
338
+ Examples: "panel" or "panel_material_ui"
339
+
340
+ Returns
341
+ -------
342
+ ComponentDetails
343
+ Complete component information including docstring, parameters, and initialization signature.
344
+
345
+ Raises
346
+ ------
347
+ ValueError
348
+ If no components match the criteria or if multiple components match (be more specific).
349
+
350
+ Examples
351
+ --------
352
+ Get Panel's Button component:
353
+ >>> get_component(name="Button", package="panel")
354
+ ComponentDetails(name="Button", package="panel", docstring="A clickable button...", parameters={...})
355
+
356
+ Get Material UI Button component:
357
+ >>> get_component(name="Button", package="panel_material_ui")
358
+ ComponentDetails(name="Button", package="panel_material_ui", ...)
359
+
360
+ Get component by exact module path:
361
+ >>> get_component(module_path="panel.widgets.button.Button")
362
+ ComponentDetails(name="Button", module_path="panel.widgets.button.Button", ...)
363
+ """
364
+ components_list = await _get_component(ctx, name, module_path, package)
365
+
366
+ if not components_list:
367
+ raise ValueError(f"No components found matching criteria: '{name}', '{module_path}', '{package}'. Please check your inputs.")
368
+ if len(components_list) > 1:
369
+ module_paths = "'" + "','".join([component.module_path for component in components_list]) + "'"
370
+ raise ValueError(f"Multiple components found matching criteria: {module_paths}. Please refine your search.")
371
+ component = components_list[0]
372
+ return component
373
+
374
+
375
+ @mcp.tool
376
+ async def get_component_parameters(ctx: Context, name: str | None = None, module_path: str | None = None, package: str | None = None) -> dict[str, ParameterInfo]:
377
+ """
378
+ Get detailed parameter information for a single Panel component.
379
+
380
+ Use this tool when you need to understand the parameters of a specific Panel component,
381
+ including their types, default values, documentation, and constraints. This is useful
382
+ for understanding how to properly initialize and configure a component.
383
+
384
+ IMPORTANT: This tool returns parameters for exactly one component. If your criteria
385
+ match multiple components, you'll get an error asking you to be more specific.
386
+
387
+ Parameters
388
+ ----------
389
+ ctx : Context
390
+ FastMCP context (automatically provided by the MCP framework).
391
+ name : str, optional
392
+ Component name to match (case-insensitive). If None, must specify other criteria.
393
+ Examples: "Button", "TextInput", "Slider"
394
+ module_path : str, optional
395
+ Full module path to match. If None, uses name and package to find component.
396
+ Examples: "panel.widgets.Button", "panel_material_ui.Button"
397
+ package : str, optional
398
+ Package name to filter by. If None, searches all packages.
399
+ Examples: "hvplot", "panel" or "panel_material_ui"
400
+
401
+ Returns
402
+ -------
403
+ dict[str, ParameterInfo]
404
+ Dictionary mapping parameter names to their detailed information, including:
405
+ - type: Parameter type (e.g., 'String', 'Number', 'Boolean')
406
+ - default: Default value
407
+ - doc: Parameter documentation
408
+ - bounds: Value constraints for numeric parameters
409
+ - objects: Available options for selector parameters
410
+
411
+ Raises
412
+ ------
413
+ ValueError
414
+ If no components match the criteria or if multiple components match (be more specific).
415
+
416
+ Examples
417
+ --------
418
+ Get Button parameters:
419
+ >>> get_component_parameters(name="Button", package="panel")
420
+ {"name": ParameterInfo(type="String", default="Button", doc="The text displayed on the button"), ...}
421
+
422
+ Get TextInput parameters:
423
+ >>> get_component_parameters(name="TextInput", package="panel")
424
+ {"value": ParameterInfo(type="String", default="", doc="The current text value"), ...}
425
+
426
+ Get parameters by exact module path:
427
+ >>> get_component_parameters(module_path="panel.widgets.Slider")
428
+ {"start": ParameterInfo(type="Number", default=0, bounds=(0, 100)), ...}
429
+ """
430
+ components_list = await _get_component(ctx, name, module_path, package)
431
+
432
+ if not components_list:
433
+ raise ValueError(f"No components found matching criteria: '{name}', '{module_path}', '{package}'. Please check your inputs.")
434
+ if len(components_list) > 1:
435
+ module_paths = "'" + "','".join([component.module_path for component in components_list]) + "'"
436
+ raise ValueError(f"Multiple components found matching criteria: {module_paths}. Please refine your search.")
437
+
438
+ component = components_list[0]
439
+ return component.parameters
440
+
441
+
442
+ if __name__ == "__main__":
443
+ mcp.run(transport=_config.server.transport)
holoviz_mcp/py.typed ADDED
File without changes
holoviz_mcp/serve.py ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env python3
2
+ """Serve Panel apps with configurable arguments."""
3
+
4
+ import logging
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ # Configure logging
10
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
11
+ logger = logging.getLogger(__name__)
12
+
13
+ THUMBNAILS_DIR = Path(__file__).parent / "thumbnails"
14
+
15
+
16
+ def main() -> None:
17
+ """Serve all Panel apps in the apps directory."""
18
+ apps_dir = Path(__file__).parent / "apps"
19
+ app_files = [str(f) for f in apps_dir.glob("*.py") if f.name != "__init__.py"]
20
+
21
+ if not app_files:
22
+ logger.warning("No Panel apps found in apps directory")
23
+ return
24
+
25
+ # Use python -m panel to ensure we use the same Python environment
26
+ cmd = [sys.executable, "-m", "panel", "serve", *app_files, "--static-dirs", f"thumbnails={THUMBNAILS_DIR}"]
27
+ logger.info(f"Running: {' '.join(cmd)}")
28
+
29
+ try:
30
+ subprocess.run(cmd, check=True)
31
+ except KeyboardInterrupt:
32
+ logger.info("Server stopped")
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()
holoviz_mcp/server.py ADDED
@@ -0,0 +1,86 @@
1
+ """HoloViz MCP Server.
2
+
3
+ This MCP server provides comprehensive tools, resources and prompts for working with the HoloViz ecosystem,
4
+ including Panel and hvPlot following best practices.
5
+
6
+ The server is composed of multiple sub-servers that provide various functionalities:
7
+
8
+ - Documentation: Search and access HoloViz documentation as context
9
+ - hvPlot: Tools, resources and prompts for using hvPlot to develop quick, interactive plots in Python
10
+ - Panel: Tools, resources and prompts for using Panel Material UI
11
+ """
12
+
13
+ import asyncio
14
+ import logging
15
+ import os
16
+
17
+ from fastmcp import FastMCP
18
+
19
+ from holoviz_mcp.config.loader import get_config
20
+ from holoviz_mcp.holoviz_mcp.server import mcp as holoviz_mcp
21
+ from holoviz_mcp.hvplot_mcp.server import mcp as hvplot_mcp
22
+ from holoviz_mcp.panel_mcp.server import mcp as panel_mcp
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ mcp: FastMCP = FastMCP(
27
+ name="holoviz",
28
+ instructions="""
29
+ [his MCP server provides comprehensive tools, resources and prompts for working with the HoloViz ecosystem following best practices.
30
+
31
+ HoloViz provides a set of core Python packages that make visualization easier, more accurate, and more powerful:
32
+
33
+ - [Panel](https://panel.holoviz.org): for making apps and dashboards for your plots from any supported plotting library.
34
+ - [hvPlot](https://hvplot.holoviz.org): to quickly generate interactive plots from your data.
35
+ - [HoloViews](https://holoviews.org): to help you make all of your data instantly visualizable.
36
+ - [GeoViews](https://geoviews.org): to extend HoloViews for geographic data.
37
+ - [Datashader](https://datashader.org): for rendering even the largest datasets.
38
+ - [Lumen](https://lumen.holoviz.org): to build data-driven dashboards from a simple YAML specification that's well suited to modern AI tools like LLMs.
39
+ - [Param](https://param.holoviz.org): to create declarative user-configurable objects.
40
+ - [Colorcet](https://colorcet.holoviz.org): for perceptually uniform colormaps.
41
+
42
+ The server is composed of multiple sub-servers that provide various functionalities:
43
+
44
+ - Documentation: Search and access HoloViz documentation and reference guides
45
+ - Panel: Tools, resources and prompts for using Panel and Panel Extension packages
46
+ - hvPlot: Tools, resources and prompts for using hvPlot to develop quick, interactive plots in Python
47
+ """,
48
+ )
49
+
50
+
51
+ async def setup_composed_server() -> None:
52
+ """Set up the composed server by importing all sub-servers with prefixes.
53
+
54
+ This uses static composition (import_server), which copies components
55
+ from sub-servers into the main server with appropriate prefixes.
56
+ """
57
+ await mcp.import_server(holoviz_mcp, prefix="holoviz")
58
+ await mcp.import_server(hvplot_mcp, prefix="hvplot")
59
+ await mcp.import_server(panel_mcp, prefix="panel")
60
+
61
+
62
+ def main() -> None:
63
+ """Set up and run the composed MCP server."""
64
+ pid = f"Process ID: {os.getpid()}"
65
+ print(pid) # noqa: T201
66
+
67
+ async def setup_and_run() -> None:
68
+ await setup_composed_server()
69
+ config = get_config()
70
+
71
+ # Pass host and port for HTTP transport
72
+ if config.server.transport == "http":
73
+ await mcp.run_async(
74
+ transport=config.server.transport,
75
+ host=config.server.host,
76
+ port=config.server.port,
77
+ )
78
+ else:
79
+ await mcp.run_async(transport=config.server.transport)
80
+
81
+ asyncio.run(setup_and_run())
82
+
83
+
84
+ if __name__ == "__main__":
85
+ # Run the composed MCP server
86
+ main()
@@ -0,0 +1 @@
1
+ """Shared features for the holoviz-mcp project."""
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+ """Extract tools from the MCP server for documentation."""
3
+
4
+ import asyncio
5
+ import logging
6
+
7
+ from holoviz_mcp.server import mcp
8
+ from holoviz_mcp.server import setup_composed_server
9
+
10
+ # Configure logging
11
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ async def extract_tools():
16
+ """Extract available tools from the HoloViz MCP server and return as structured data."""
17
+ await setup_composed_server()
18
+ tools_dict = await mcp.get_tools()
19
+
20
+ # Group tools by category
21
+ holoviz_tools = []
22
+ panel_tools = []
23
+ utility_tools = []
24
+
25
+ for tool_name, tool_info in tools_dict.items():
26
+ tool_data = {"name": tool_name, "description": getattr(tool_info, "description", "No description available"), "parameters": []}
27
+
28
+ # Get input schema
29
+ input_schema = getattr(tool_info, "inputSchema", None)
30
+ if input_schema and hasattr(input_schema, "get") and "properties" in input_schema:
31
+ for param_name, param_info in input_schema["properties"].items():
32
+ required = param_name in input_schema.get("required", [])
33
+ param_type = param_info.get("type", "unknown")
34
+ desc = param_info.get("description", "No description")
35
+ tool_data["parameters"].append({"name": param_name, "type": param_type, "required": required, "description": desc})
36
+
37
+ # Categorize tools
38
+ if any(x in tool_name for x in ["docs", "get_best_practices", "get_reference_guide", "get_document", "update_docs"]) or (
39
+ tool_name == "search" and "component" not in str(tool_info)
40
+ ):
41
+ holoviz_tools.append(tool_data)
42
+ elif any(x in tool_name for x in ["component", "packages"]) or "search" in tool_name:
43
+ panel_tools.append(tool_data)
44
+ else:
45
+ utility_tools.append(tool_data)
46
+
47
+ return {"panel_tools": panel_tools, "holoviz_tools": holoviz_tools, "utility_tools": utility_tools}
48
+
49
+
50
+ async def main():
51
+ """Extract and print available tools from the HoloViz MCP server."""
52
+ tools_data = await extract_tools()
53
+ logger.info("## 🛠️ Available Tools")
54
+ logger.info("")
55
+
56
+ def print_tools(tools_list, category_name):
57
+ if not tools_list:
58
+ return
59
+ logger.info("<details>")
60
+ logger.info(f"<summary><b>{category_name}</b></summary>")
61
+ logger.info("")
62
+ for tool_data in tools_list:
63
+ logger.info(f"- **{tool_data['name']}**: {tool_data['description']}")
64
+ logger.info("")
65
+ logger.info("</details>")
66
+ logger.info("")
67
+
68
+ print_tools(tools_data["panel_tools"], "Panel Components")
69
+ print_tools(tools_data["holoviz_tools"], "Documentation")
70
+ print_tools(tools_data["utility_tools"], "Utilities")
71
+
72
+
73
+ if __name__ == "__main__":
74
+ asyncio.run(main())