Skip to main content
Glama
deslicer

MCP Server for Splunk

list_dashboards

Get a list of Splunk dashboards with their metadata: type, owner, permissions, URLs. Filter by app, owner, type, or search terms.

Instructions

List dashboards in Splunk (Simple XML and Dashboard Studio). Returns metadata including name, label, type (classic/studio), app, owner, permissions, sharing level, last updated, and Splunk Web viewing URLs.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
ownerNoFilter by owner. Use 'me' for current user's dashboards, 'nobody' for shared dashboards, or a specific username. Default: 'nobody'nobody
appNoFilter by app context. Default: '-' (all apps)-
countNoMax results to return. 0=all, default: 50 for performance
offsetNoResult offset for pagination. Default: 0
search_filterNoFilter results (e.g., 'name=*security*')
type_filterNoFilter by type: 'classic', 'studio', or 'any'. Default: 'any'any
my_dashboards_onlyNoIf True, only return dashboards owned by the current user. Overrides 'owner' parameter. Default: False
private_onlyNoIf True, only return private dashboards (sharing='user'). Works with any owner filter. Default: False

Implementation Reference

  • Main handler that calls Splunk REST endpoint /servicesNS/{owner}/{app}/data/ui/views, parses results, detects dashboard type (classic vs studio) by inspecting eai:data, applies filters (type, private_only, owner), and returns formatted dashboard metadata.
    async def execute(
        self,
        ctx: Context,
        owner: str = "nobody",
        app: str = "-",
        count: int = 50,
        offset: int = 0,
        search_filter: str = "",
        type_filter: str = "any",
        my_dashboards_only: bool = False,
        private_only: bool = False,
    ) -> dict[str, Any]:
        """
        List dashboards from Splunk.
    
        Args:
            owner: Filter by owner (use 'me' for current user, default: nobody for all)
            app: Filter by app (default: - for all)
            count: Maximum results (default: 50 for performance, 0 for all)
            offset: Pagination offset
            search_filter: Optional search filter
            type_filter: Filter by dashboard type (classic/studio/any)
            my_dashboards_only: If True, only return current user's dashboards
            private_only: If True, only return private dashboards (sharing='user')
    
        Returns:
            Dict with status and list of dashboard metadata, includes total_available
            for pagination info, and sharing level for each dashboard
        """
        log_tool_execution(
            "list_dashboards",
            owner=owner,
            app=app,
            count=count,
            offset=offset,
            search_filter=search_filter,
            type_filter=type_filter,
            my_dashboards_only=my_dashboards_only,
            private_only=private_only,
        )
    
        is_available, service, error_msg = self.check_splunk_available(ctx)
    
        if not is_available:
            await ctx.error(f"List dashboards failed: {error_msg}")
            return self.format_error_response(error_msg)
    
        try:
            # Get current username if my_dashboards_only is True or owner is 'me'
            if my_dashboards_only or owner == "me":
                current_username = service.username
                if not current_username:
                    error_msg = "Unable to determine current username"
                    await ctx.error(error_msg)
                    return self.format_error_response(error_msg)
                owner = current_username
                await ctx.info(f"Filtering dashboards for current user: {current_username}")
    
            # Add info about private_only filter
            if private_only:
                await ctx.info("Filtering for private dashboards only (sharing='user')")
    
            await ctx.info(f"Retrieving dashboards from Splunk (owner={owner}, app={app})")
    
            # Build request parameters
            params = {
                "output_mode": "json",
                "count": count,
                "offset": offset,
            }
    
            # Filter for dashboards only
            base_filter = "isDashboard=1"
            if search_filter:
                params["search"] = f"{base_filter} {search_filter}"
            else:
                params["search"] = base_filter
    
            # Get Splunk Web base URL from service
            splunk_host = service.host
            # Use HTTPS by default for web UI (typically port 8000)
            web_port = 8000  # Standard Splunk Web port
            web_scheme = "https"
            web_base = f"{web_scheme}://{splunk_host}:{web_port}"
    
            # Call the REST endpoint
            endpoint = f"/servicesNS/{owner}/{app}/data/ui/views"
            response = service.get(endpoint, **params)
    
            # Parse JSON response
            response_body = response.body.read()
            data = json.loads(response_body)
    
            # Extract and format entries
            entries = data.get("entry", [])
            dashboards = []
    
            for entry in entries:
                content = entry.get("content", {})
                acl = entry.get("acl", {})
                dashboard_name = entry.get("name", "")
                dashboard_app = acl.get("app", "")
    
                # Detect dashboard type by checking eai:data
                eai_data = content.get("eai:data", "")
                dashboard_type = "classic"  # Default to classic Simple XML
    
                if eai_data:
                    # Dashboard Studio can be in two formats:
                    # 1. Pure JSON (direct Studio format)
                    # 2. Hybrid XML with <definition> tag containing JSON in CDATA
    
                    # Check for hybrid format: <definition> tag (Dashboard Studio specific)
                    if "<definition>" in eai_data:
                        dashboard_type = "studio"
                    else:
                        # Try to parse as pure JSON (Dashboard Studio format)
                        try:
                            json.loads(eai_data)
                            dashboard_type = "studio"
                        except (json.JSONDecodeError, TypeError):
                            # Falls back to classic XML
                            dashboard_type = "classic"
    
                # Apply type filter
                if type_filter != "any":
                    if type_filter != dashboard_type:
                        continue
    
                # Get sharing level
                sharing = acl.get("sharing", "unknown")
    
                # Apply private_only filter
                if private_only and sharing != "user":
                    continue  # Skip non-private dashboards
    
                # Build Splunk Web URL
                web_url = f"{web_base}/en-US/app/{dashboard_app}/{dashboard_name}"
    
                # Safely handle perms which could be None
                perms = acl.get("perms") or {}
    
                dashboard = {
                    "name": dashboard_name,
                    "label": content.get("label", dashboard_name),
                    "type": dashboard_type,
                    "app": dashboard_app,
                    "owner": acl.get("owner", ""),
                    "sharing": sharing,
                    "description": content.get("description", ""),
                    "updated": content.get("updated", ""),
                    "version": content.get("version", ""),
                    "permissions": {
                        "read": perms.get("read", []),
                        "write": perms.get("write", []),
                    },
                    "web_url": web_url,
                    "id": entry.get("id", ""),
                }
                dashboards.append(dashboard)
    
            self.logger.info("Retrieved %d dashboards", len(dashboards))
            await ctx.info(f"Found {len(dashboards)} dashboards")
    
            return self.format_success_response(
                {
                    "dashboards": dashboards,
                    "count": len(dashboards),
                    "total_available": data.get("paging", {}).get("total", len(dashboards)),
                    "offset": offset,
                    "type_filter": type_filter,
                    "private_only": private_only,
                    "owner_filter": owner,
                }
            )
    
        except Exception as e:  # pylint: disable=broad-except
            self.logger.error("Failed to list dashboards: %s", str(e), exc_info=True)
            await ctx.error(f"Failed to list dashboards: {str(e)}")
    
            error_detail = str(e)
            if "404" in error_detail or "Not Found" in error_detail:
                error_detail += " (Endpoint not found - check Splunk version and permissions)"
            elif "403" in error_detail or "Forbidden" in error_detail:
                error_detail += " (Permission denied - check user role and capabilities)"
            elif "401" in error_detail or "Unauthorized" in error_detail:
                error_detail += " (Authentication failed - check credentials)"
    
            return self.format_error_response(error_detail)
  • Tool metadata defining name, description with parameter docs, category, tags, and connection requirement.
    METADATA = ToolMetadata(
        name="list_dashboards",
        description=(
            "List dashboards in Splunk (Simple XML and Dashboard Studio). Returns metadata "
            "including name, label, type (classic/studio), app, owner, permissions, sharing level, "
            "last updated, and Splunk Web viewing URLs.\n\n"
            "Args:\n"
            "    owner (str, optional): Filter by owner. Use 'me' for current user's dashboards, "
            "'nobody' for shared dashboards, or a specific username. Default: 'nobody'\n"
            "    app (str, optional): Filter by app context. Default: '-' (all apps)\n"
            "    count (int, optional): Max results to return. 0=all, default: 50 for performance\n"
            "    offset (int, optional): Result offset for pagination. Default: 0\n"
            "    search_filter (str, optional): Filter results (e.g., 'name=*security*')\n"
            "    type_filter (str, optional): Filter by type: 'classic', 'studio', or 'any'. Default: 'any'\n"
            "    my_dashboards_only (bool, optional): If True, only return dashboards owned by the "
            "current user. Overrides 'owner' parameter. Default: False\n"
            "    private_only (bool, optional): If True, only return private dashboards (sharing='user'). "
            "Works with any owner filter. Default: False"
        ),
        category="dashboards",
        tags=["dashboards", "visualization", "ui", "list"],
        requires_connection=True,
    )
  • Exports ListDashboards from the dashboards package init.
    from src.tools.dashboards.get_dashboard_definition import GetDashboardDefinition
    from src.tools.dashboards.list_dashboards import ListDashboards
    
    __all__ = ["ListDashboards", "GetDashboardDefinition"]
  • Top-level tool registration including ListDashboards in the global __all__ and re-exporting via wildcard import from dashboards package.
    "ListDashboards",
    "GetDashboardDefinition",
  • Test verifying the list_dashboards tool returns expected response structure with dashboards, count, name, label, type, and web_url fields.
    async def test_list_dashboards_success(self, fastmcp_client, extract_tool_result):
        """Test successful listing of dashboards."""
        async with fastmcp_client as client:
            # Execute tool through FastMCP
            result = await client.call_tool("list_dashboards", {})
            data = extract_tool_result(result)
    
            # Verify response structure
            if data.get("status") == "success":
                assert "dashboards" in data
                assert "count" in data
                assert isinstance(data["dashboards"], list)
                if data["count"] > 0:
                    first_dashboard = data["dashboards"][0]
                    assert "name" in first_dashboard
                    assert "label" in first_dashboard
                    assert "type" in first_dashboard
                    assert "web_url" in first_dashboard
                    # Type should be either 'classic' or 'studio'
                    assert first_dashboard["type"] in ["classic", "studio"]
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description indicates a read-only operation without destructive side effects, but no annotations exist to confirm. It does not disclose behavioral traits like permissions required, performance implications, or pagination behavior. The listing of returned metadata provides some transparency, but gaps remain.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single efficient sentence that front-loads the core purpose and then lists the returned metadata. Every word adds value, and the structure is clear despite the length. No redundant or irrelevant information is present.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a list tool with 8 parameters and no output schema, the description covers the return fields but omits important behavioral context such as pagination behavior, interaction between filters, and performance notes. The schema covers parameters adequately, but the description does not fully compensate for missing annotations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% coverage with descriptions for all 8 parameters. The tool description adds no additional meaning or usage context beyond what the schema already provides. No special constraints or interactions between parameters are explained.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists dashboards in Splunk, distinguishing between Simple XML and Dashboard Studio types. It explicitly mentions the verb 'list' and the resource 'dashboards', and the sibling list tools for other resources make this distinct. The inclusion of returned metadata fields further clarifies the purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives such as get_dashboard_definition or other list tools. There is no mention of prerequisites, limitations, or scenarios where this tool is not appropriate. The description lacks any usage context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/deslicer/mcp-for-splunk'

If you have feedback or need assistance with the MCP directory API, please join our Discord server