Skip to content

Conversation

@rexagod
Copy link
Member

@rexagod rexagod commented Jan 25, 2026

This PR is currently blocked by GIE-376.


The MCP server can now find existing Perses dashboards in the cluster
and narrow them down to panels that, upon visualization, help answer the
user's concerns better. It is assumed that since the queries in these
panels have been through human reviews and used in production, they do
not need to be subject to the same validation and guardrails as we for
the queries that the LLM comes up with on-the-fly.

Upon the LLM sharing it's intent to render one or more panels after
going through the existing dashboards, the MCP server will expose
payloads representing panels in the same structure as found in
jhadvig/genie-plugin, so the UI side has no problems graphing these out.

Builds on #2.


curl -X POST http://localhost:8080/v1/query \
  -H "Content-Type: application/json" \
  -d '{
    "query": "List all available dashboards in the monitoring namespace"
  }'
{
	"conversation_id": "9a1925495e78c5fefaa84e6a7bc621ba85041f15e6ee8fa1",
	"response": "There is one available dashboard in the monitoring namespace:\n\n- **Name:** sample-dashboard\n- **Namespace:** monitoring",
	"rag_chunks": [],
	"referenced_documents": [],
	"truncated": false,
	"input_tokens": 2178,
	"output_tokens": 34,
	"available_quotas": {},
	"tool_calls": [
		{
			"id": "mcp_list_04e607c1-bc92-4e64-a101-4efe5eb23760",
			"name": "mcp_list_tools",
			"args": {
				"server_label": "obs"
			},
			"type": "mcp_list_tools"
		},
		{
			"id": "fc_f9d0408c-74c5-4223-85d3-7c23e6d15172",
			"name": "list_dashboards",
			"args": {
				"server_label": "obs"
			},
			"type": "mcp_call"
		}
	],
	"tool_results": [
		{
			"id": "mcp_list_04e607c1-bc92-4e64-a101-4efe5eb23760",
			"status": "success",
			"content": "{\"server_label\": \"obs\", \"tools\": [{\"name\": \"execute_range_query\", \"description\": \"Execute a PromQL range query with flexible time specification.\\n\\nFor current time data queries, use only the 'duration' parameter to specify how far back\\nto look from now (e.g., '1h' for last hour, '30m' for last 30 minutes). In that case\\nSET 'end' to 'NOW' and leave 'start' empty.\\n\\nFor historical data queries, use explicit 'start' and 'end' times.\\n\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"duration\": {\"description\": \"Duration to look back from now (e.g., '1h', '30m', '1d', '2w') (optional)\", \"pattern\": \"^\\\\d+[smhdwy]$\", \"type\": \"string\"}, \"end\": {\"description\": \"End time as RFC3339 or Unix timestamp (optional). Use `NOW` for current time.\", \"type\": \"string\"}, \"query\": {\"description\": \"PromQL query string\", \"type\": \"string\"}, \"start\": {\"description\": \"Start time as RFC3339 or Unix timestamp (optional)\", \"type\": \"string\"}, \"step\": {\"description\": \"Query resolution step width (e.g., '15s', '1m', '1h')\", \"pattern\": \"^\\\\d+[smhdwy]$\", \"type\": \"string\"}}, \"required\": [\"query\", \"step\"]}}, {\"name\": \"format_panels_for_ui\", \"description\": \"Format selected dashboard panels for UI rendering in DashboardWidget format.\\n\\nAfter choosing relevant panels, use this to prepare them for display.\\n\\nReturns an array of DashboardWidget objects ready for direct rendering, with:\\n- id: Unique panel identifier\\n- componentType: Perses component name (PersesTimeSeries, PersesPieChart, PersesTable)\\n- position: Grid layout coordinates (x, y, w, h) in 24-column grid\\n- breakpoint: Responsive grid breakpoint (xl/lg/md/sm) inferred from panel width\\n- props: Component properties (query, duration, step, start, end)\\n\\nPanel IDs (fetched using get_dashboard_panels) must be provided to specify which panels to format.\\n\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"name\": {\"description\": \"Name of the dashboard containing the panels\", \"type\": \"string\"}, \"namespace\": {\"description\": \"Namespace of the dashboard\", \"type\": \"string\"}, \"panel_ids\": {\"description\": \"Comma-separated list of panel IDs to format (e.g. 'myPanelID-1,0_1-2')\", \"type\": \"string\"}}, \"required\": [\"name\", \"namespace\", \"panel_ids\"]}}, {\"name\": \"get_dashboard\", \"description\": \"Get a specific Dashboard by name and namespace. This tool is used to get the dashboard's panels and configuration.\\n\\nUse the list_dashboards tool first to find available dashboards, then use this tool to get the full specification of a specific dashboard, if needed (to gather more context).\\n\\nThe intended use of this tool is only to gather more context on one or more dashboards when the description from list_dashboards is insufficient.\\n\\nInformation about panels themselves should be gathered using get_dashboard_panels instead (e.g., looking at a \\\"kind: Markdown\\\" panel to gather more context).\\n\\nReturns the dashboard's full specification including panels, layouts, variables, and datasources in JSON format.\\n\\nFor most use cases, you will want to follow up with get_dashboard_panels to extract panel metadata for selection.\\n\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"name\": {\"description\": \"Name of the Dashboard\", \"type\": \"string\"}, \"namespace\": {\"description\": \"Namespace of the Dashboard\", \"type\": \"string\"}}, \"required\": [\"name\", \"namespace\"]}}, {\"name\": \"get_dashboard_panels\", \"description\": \"Get panel(s) information from a specific Dashboard.\\n\\nAfter finding a relevant dashboard (using list_dashboards and conditionally, get_dashboard), use this to see what panels it contains.\\n\\nReturns panel metadata including:\\n- Panel IDs (format: 'panelName' or 'panelName-N' for multi-query panels)\\n- Titles and descriptions\\n- PromQL queries (may contain variables like $namespace)\\n- Chart types (TimeSeriesChart, PieChart, Table)\\n\\nYou can optionally provide specific panel IDs to fetch only those panels. This is useful when you remember panel IDs from earlier calls and want to re-fetch just their metadata without retrieving the entire dashboard's panels.\\n\\nUse this information to identify which panels answer the user's question, then use format_panels_for_ui with the selected panel IDs to prepare them for display.\\n\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"name\": {\"description\": \"Name of the Dashboard\", \"type\": \"string\"}, \"namespace\": {\"description\": \"Namespace of the Dashboard\", \"type\": \"string\"}, \"panel_ids\": {\"description\": \"Optional comma-separated list of panel IDs to filter. Panel IDs follow the format 'panelName' or 'panelName-N' where N is the query index (e.g. 'cpuUsage,memoryUsage-0,networkTraffic-1'). Use this to fetch metadata for specific panels you've seen in earlier calls. Leave empty to get all panels.\", \"type\": \"string\"}}, \"required\": [\"name\", \"namespace\"]}}, {\"name\": \"list_dashboards\", \"description\": \"List all PersesDashboard resources from the cluster.\\n\\nStart here when there is a need to visualize metrics.\\n\\nReturns dashboard summaries with name, namespace, labels, and descriptions.\\n\\nUse the descriptions to identify dashboards relevant to the user's question.\\n\\nIn the case that there is insufficient information in the description, use get_dashboard to fetch the full dashboard spec for more context. Doing so is an expensive operation, so only do this when necessary.\\n\\nFollow up with get_dashboard_panels to see what panels are available in the relevant dashboard(s).\\n\", \"input_schema\": {\"type\": \"object\", \"properties\": {}}}, {\"name\": \"list_metrics\", \"description\": \"List all available metrics in Prometheus\", \"input_schema\": {\"type\": \"object\", \"properties\": {}}}]}",
			"type": "mcp_list_tools",
			"round": 1
		},
		{
			"id": "fc_f9d0408c-74c5-4223-85d3-7c23e6d15172",
			"status": "success",
			"content": "{\"dashboards\":[{\"name\":\"sample-dashboard\",\"namespace\":\"monitoring\"}]}",
			"type": "mcp_call",
			"round": 1
		}
	]
}

Relevant Pod logs
ts=2026-01-26T13:18:44.786Z level=info caller=handlers.go(mcp.SetupTools.DashboardsHandler.func3):180 msg="DashboardsHandler called"
ts=2026-01-26T13:18:45.308Z level=info caller=handlers.go(mcp.SetupTools.DashboardsHandler.func3):207 msg="DashboardsHandler executed successfully" dashboardCount=1
ts=2026-01-26T13:18:45.308Z level=debug caller=handlers.go(mcp.SetupTools.DashboardsHandler.func3):208 msg="DashboardsHandler results" results="[{Name:sample-dashboard Namespace:monitoring Labels:map[] Description:}]"
ts=2026-01-26T13:18:45.311Z level=info caller=server.go(mcp.Serve.loggingMiddleware.func3):91 msg="Incoming request" method=POST path=/mcp remote_addr=127.0.0.1:42678
Relevant SQLite DB snapshot
=====The following is the user query that was asked:
Which dashboards are available right now?
The available dashboard right now is:

- **Name:** sample-dashboard
- **Namespace:** monitoringopenaigpt-4o-mini[{"id": "mcp_list_66abb2b2-5f6f-4399-863e-ad494a5b2930", "name": "mcp_list_tools", "args": {"server_label": "obs"}, "type": "mcp_list_tools"}, {"id": "mcp_list_685dcdd8-2818-4b37-a1e9-474b9edb3cf5", "name": "mcp_list_tools", "args": {"server_label": "layout-manager"}, "type": "mcp_list_tools"}, {"id": "fc_015fb951-52cd-48e1-a9f5-84e62e841be6", "name": "list_perses_dashboards", "args": {"server_label": "obs"}, "type": "mcp_call"}][{"id": "mcp_list_66abb2b2-5f6f-4399-863e-ad494a5b2930", "status": "success", "content": "{\"server_label\": \"obs\", \"tools\": [{\"name\": \"execute_range_query\", \"description\": \"Execute a PromQL range query with flexible time specification.\\n\\nFor current time data queries, use only the 'duration' parameter to specify how far back\\nto look from now (e.g., '1h' for last hour, '30m' for last 30 minutes). In that case\\nSET 'end' to 'NOW' and leave 'start' empty.\\n\\nFor historical data queries, use explicit 'start' and 'end' times.\\n\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"duration\": {\"description\": \"Duration to look back from now (e.g., '1h', '30m', '1d', '2w') (optional)\", \"pattern\": \"^\\\\d+[smhdwy]$\", \"type\": \"string\"}, \"end\": {\"description\": \"End time as RFC3339 or Unix timestamp (optional). Use `NOW` for current time.\", \"type\": \"string\"}, \"query\": {\"description\": \"PromQL query string\", \"type\": \"string\"}, \"start\": {\"description\": \"Start time as RFC3339 or Unix timestamp (optional)\", \"type\": \"string\"}, \"step\": {\"description\": \"Query resolution step width (e.g., '15s', '1m', '1h')\", \"pattern\": \"^\\\\d+[smhdwy]$\", \"type\": \"string\"}}, \"required\": [\"query\", \"step\"]}}, {\"name\": \"format_panels_for_ui\", \"description\": \"Format selected dashboard panels for UI rendering in DashboardWidget format.\\n\\nAfter choosing relevant panels, use this to prepare them for display.\\n\\nReturns an array of DashboardWidget objects ready for direct rendering, with:\\n- id: Unique panel identifier\\n- componentType: Perses component name (PersesTimeSeries, PersesPieChart, PersesTable)\\n- position: Grid layout coordinates (x, y, w, h) in 24-column grid\\n- breakpoint: Responsive grid breakpoint (xl/lg/md/sm) inferred from panel width\\n- props: Component properties (query, duration, step, start, end)\\n\\nPanel IDs (fetched using get_dashboard_panels) must be provided to specify which panels to format.\\n\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"name\": {\"description\": \"Name of the dashboard containing the panels\", \"type\": \"string\"}, \"namespace\": {\"description\": \"Namespace of the dashboard\", \"type\": \"string\"}, \"panel_ids\": {\"description\": \"Comma-separated list of panel IDs to format (e.g. 'myPanelID-1,0_1-2')\", \"type\": \"string\"}}, \"required\": [\"name\", \"namespace\", \"panel_ids\"]}}, {\"name\": \"get_dashboard_panels\", \"description\": \"Get panel(s) information from a specific Dashboard.\\n\\nAfter finding a relevant dashboard (using list_perses_dashboards and conditionally, get_perses_dashboard), use this to see what panels it contains.\\n\\nReturns panel metadata including:\\n- Panel IDs (format: 'panelName' or 'panelName-N' for multi-query panels)\\n- Titles and descriptions\\n- PromQL queries (may contain variables like $namespace)\\n- Chart types (TimeSeriesChart, PieChart, Table)\\n\\nYou can optionally provide specific panel IDs to fetch only those panels. This is useful when you remember panel IDs from earlier calls and want to re-fetch just their metadata without retrieving ����the entire dashboard's panels.\\n\\nUse this information to identify which panels answer the user's question, then use format_panels_for_ui with the selected panel IDs to prepare them for display.\\n\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"name\": {\"description\": \"Name of the Dashboard\", \"type\": \"string\"}, \"namespace\": {\"description\": \"Namespace of the Dashboard\", \"type\": \"string\"}, \"panel_ids\": {\"description\": \"Optional comma-separated list of panel IDs to filter. Panel IDs follow the format 'panelName' or 'panelName-N' where N is the query index (e.g. 'cpuUsage,memoryUsage-0,networkTraffic-1'). Use this to fetch metadata for specific panels you've seen in earlier calls. Leave empty to get all panels.\", \"type\": \"string\"}}, \"required\": [\"name\", \"namespace\"]}}, {\"name\": \"get_perses_dashboard\", \"description\": \"Get a specific Dashboard by name and namespace. This tool is used to get the dashboard's panels and configuration.\\n\\nUse the list_perses_dashboards tool first to find available dashboards, then use this tool to get the full specification of a specific dashboard, if needed (to gather more context).\\n\\nThe intended use of this tool is only to gather more context on one or more dashboards when the description from list_perses_dashboards is insufficient.\\n\\nInformation about panels themselves should be gathered using get_dashboard_panels instead (e.g., looking at a \\\"kind: Markdown\\\" panel to gather more context).\\n\\nReturns the dashboard's full specification including panels, layouts, variables, and datasources in JSON format.\\n\\nFor most use cases, you will want to follow up with get_dashboard_panels to extract panel metadata for selection.\\n\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"name\": {\"description\": \"Name of the Dashboard\", \"type\": \"string\"}, \"namespace\": {\"description\": \"Namespace of the Dashboard\", \"type\": \"string\"}}, \"required\": [\"name\", \"namespace\"]}}, {\"name\": \"list_metrics\", \"description\": \"List all available metrics in Prometheus\", \"input_schema\": {\"type\": \"object\", \"properties\": {}}}, {\"name\": \"list_perses_dashboards\", \"description\": \"List all PersesDashboard resources from the cluster.\\n\\nStart here when there is a need to visualize metrics.\\n\\nReturns dashboard summaries with name, namespace, labels, and descriptions.\\n\\nUse the descriptions to identify dashboards relevant to the user's question.\\n\\nIn the case that there is insufficient information in the description, use get_perses_dashboard to fetch the full dashboard spec for more context. Doing so is an expensive operation, so only do this when necessary.\\n\\nFollow up with get_dashboard_panels to see what panels are available in the relevant dashboard(s).\\n\", \"input_schema\": {\"type\": \"object\", \"properties\": {}}}]}", "type": "mcp_list_tools", "round": 1}, {"id": "mcp_list_685dcdd8-2818-4b37-a1e9-474b9edb3cf5", "status": "success", "content": "{\"server_label\": \"layout-manager\", \"tools\": [{\"name\": \"add_widget\", \"description\": \"Add new widgets to the active dashboard. RECOMMENDED: Call get_active_dashboard first to understand the current layout and find the best position for the new widget to avoid overlaps.\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"breakpoint\": {\"description\": \"Breakpoint to add widget to (default: lg)\", \"type\": \"string\"}, \"component_type\": {\"description\": \"Type of component to create\", \"enum\": [\"TimeSeriesChart\", \"Table\", \"PieChart\"], \"type\": \"string\"}, \"position_hint\": {\"description\": \"Optional position hint (e.g., 'top right', 'bottom left', 'next to the sales chart')\", \"type\": \"string\"}, \"props\": {\"description\": \"Optional JSON string containing widget-specific properties and configuration (e.g., chart settings, data sources, styling options)\", \"type\": \"string\"}, \"size_hint\": {\"description\": \"Optional size hint (e.g., 'large', 'small', 'medium', '4x3')\", \"type\": \"string\"}����, \"widget_description\": {\"description\": \"Description of widget to add (e.g., 'chart showing sales data', 'table with customer info', 'metric displaying revenue')\", \"type\": \"string\"}}, \"required\": [\"widget_description\", \"component_type\"]}}, {\"name\": \"analyze_layout\", \"description\": \"Analyze the active dashboard structure and provide insights. NOTE: For basic layout information, get_active_dashboard is often more direct and provides raw data instead of analysis.\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"breakpoint\": {\"description\": \"Breakpoint to analyze (default: lg)\", \"type\": \"string\"}, \"question\": {\"description\": \"Question about the layout (e.g., 'what widgets are here?', 'how many charts?', 'what's in the top row?')\", \"type\": \"string\"}}, \"required\": [\"question\"]}}, {\"name\": \"batch_widget_operations\", \"description\": \"Perform multiple widget operations on the active dashboard in a single command. ESSENTIAL: Call get_active_dashboard first to understand the current state before planning batch operations.\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"atomic\": {\"description\": \"Whether all operations must succeed or all fail (default: true)\", \"type\": \"boolean\"}, \"breakpoint\": {\"description\": \"Breakpoint to operate on (default: lg)\", \"type\": \"string\"}, \"commands_json\": {\"description\": \"JSON array of natural language commands to execute in sequence\", \"type\": \"string\"}}, \"required\": [\"commands_json\"]}}, {\"name\": \"configure_widget\", \"description\": \"Configure widget properties and settings in the active dashboard. BEST PRACTICE: Use get_active_dashboard first to see current widget properties, then make informed configuration changes.\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"breakpoint\": {\"description\": \"Breakpoint to modify (default: lg)\", \"type\": \"string\"}, \"configuration_request\": {\"description\": \"Natural language description of the configuration change (e.g., 'change filters to show only critical systems', 'set chart type to bar', 'update data source')\", \"type\": \"string\"}, \"current_props\": {\"description\": \"Optional JSON string of current widget props to help understand existing configuration\", \"type\": \"string\"}, \"domain_context\": {\"description\": \"Optional domain context to help interpret the request (e.g., 'monitoring dashboard', 'sales analytics', 'system health')\", \"type\": \"string\"}, \"suggested_props\": {\"description\": \"Optional JSON string of suggested prop changes from frontend if available\", \"type\": \"string\"}, \"widget_selector\": {\"description\": \"Description of which widget to configure (e.g., 'sales table', 'main chart', 'system status widget')\", \"type\": \"string\"}}, \"required\": [\"widget_selector\", \"configuration_request\"]}}, {\"name\": \"create_dashboard\", \"description\": \"Create a new empty dashboard and set it as active\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"description\": {\"description\": \"Optional description of the dashboard purpose\", \"type\": \"string\"}, \"name\": {\"description\": \"Optional display name for the dashboard (e.g., 'Sales Dashboard', 'System Overview')\", \"type\": \"string\"}}}}, {\"name\": \"find_widgets\", \"description\": \"Find widgets in the active dashboard based on natural language descriptions. TIP: Use get_active_dashboard first to see the current layout state, then use this tool to locate specific widgets by description.\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"breakpoint\": {\"description\": \"Breakpoint to search in (default: lg)\", \"type\": \"string\"}, \"component_type\": {\"description\": \"Optional: specific component type to filter by (chart, table, metric, text, image, iframe)\", \"type\": \"string\"}, \"description\": {\"description\": \"Natural language description of the widget to find (e.g., 'chart widget', 'sales graph', 'table in top right')\", \"type\": \"string\"}}, \"required\": [\"description\"]}}, {\"����name\": \"get_active_dashboard\", \"description\": \"Get the current active dashboard schema and layout information. This provides the LLM with the current state of widgets, their positions, and properties to make informed decisions about layout modifications.\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"breakpoint\": {\"description\": \"Breakpoint to return layout for (default: lg)\", \"type\": \"string\"}, \"include_all_breakpoints\": {\"description\": \"Whether to include layouts for all breakpoints (default: false)\", \"type\": \"boolean\"}}}}, {\"name\": \"get_dashboard\", \"description\": \"Get a specific dashboard by layout ID and set it as active (deactivating all other dashboards). This retrieves the dashboard schema and layout information.\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"breakpoint\": {\"description\": \"Breakpoint to return layout for (default: lg)\", \"type\": \"string\"}, \"include_all_breakpoints\": {\"description\": \"Whether to include layouts for all breakpoints (default: false)\", \"type\": \"boolean\"}, \"layout_id\": {\"description\": \"The layout ID of the dashboard to retrieve and activate\", \"type\": \"string\"}}, \"required\": [\"layout_id\"]}}, {\"name\": \"list_dashboards\", \"description\": \"List all dashboards (aka: list layouts, show dashboards, list existing dashboards). Returns dashboard identifiers and metadata. Use this to enumerate dashboards; use get_active_dashboard to inspect the current one.\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"limit\": {\"description\": \"Optional limit (default: 50)\", \"type\": \"string\"}, \"offset\": {\"description\": \"Optional offset (default: 0)\", \"type\": \"string\"}}}}, {\"name\": \"manipulate_widget\", \"description\": \"Perform widget operations on the active dashboard like move, resize, or remove. WORKFLOW: 1) Call get_active_dashboard to see all widgets, 2) Identify the exact widget ID, 3) Call this tool with the widget ID.\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"apply_to_all_breakpoints\": {\"description\": \"Whether to apply changes proportionally to all breakpoints (default: false)\", \"type\": \"boolean\"}, \"breakpoint\": {\"description\": \"Breakpoint to operate on (default: lg)\", \"type\": \"string\"}, \"h\": {\"description\": \"New height as string (required for resize operation, e.g., '3')\", \"type\": \"string\"}, \"operation\": {\"description\": \"Operation to perform: 'move', 'resize', or 'remove'\", \"type\": \"string\"}, \"w\": {\"description\": \"New width as string (required for resize operation, e.g., '4')\", \"type\": \"string\"}, \"widget_id\": {\"description\": \"Exact widget ID from get_active_dashboard (e.g., 'widget-1', 'chart-abc123'). Do NOT use descriptions - use the actual ID.\", \"type\": \"string\"}, \"x\": {\"description\": \"New X position as string (required for move operation, e.g., '2')\", \"type\": \"string\"}, \"y\": {\"description\": \"New Y position as string (required for move operation, e.g., '1')\", \"type\": \"string\"}}, \"required\": [\"widget_id\", \"operation\"]}}, {\"name\": \"set_dashboard_metadata\", \"description\": \"Set dashboard name and/or description. Target selection: prefer 'layout_id'; if missing, you MAY use 'dashboard_name'; if neither provided, default to the ACTIVE dashboard.\", \"input_schema\": {\"type\": \"object\", \"properties\": {\"dashboard_name\": {\"description\": \"Optional: dashboard name to target when layout_id is not provided\", \"type\": \"string\"}, \"description\": {\"description\": \"Optional new description\", \"type\": \"string\"}, \"layout_id\": {\"description\": \"Optional: dashboard layout_id to update (preferred when available)\", \"type\": \"string\"}, \"name\": {\"description\": \"Optional new name\", \"type\": \"string\"}}}}]}", "type": "mcp_list_tools", "round": 1}, {"id": "fc_015fb951-52cd-48e1-a9f5-84e62e841be6", "status": "success", "content": "{\"dashboards\":[{\"name\":\"sample-dashboard\",\"namespace\":\"monitoring\"}]}", "type": "mcp_call", "round": 1}]
����	����	������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������¡[��Cm�55���y�#��%���00000000-0000-0000-0000-000fe9de705c5630fcde9d293942d32362b7c4547069af0b502AÚ]ÚöÆ_á2026-01-26T13:27:47Z2026-01-26T13:27:55Z
When adding widgets that are charts, ensure or pass query property to the widget.
Chart widgets do load the data at runtime and do not need any hardcoded data. They do not know how to use them.
Avoid using queries that have invalid characters in them. For example:
      - invalid query: sum by(namespace) (rate(container_cpu_usage_seconds_total{c������a��Cm�55�9�K�#��o�ñG00000000-0000-0000-0000-000fe9de705c5630fcde9d293942d32362b7c4547069af0b502AÚ]Ú¬ºôB2026-01-26T13:22:55Z2026-01-26T13:22:58Z
When adding widgets that are charts, ensure or pass query property to the widget.
Chart widgets do load the data at runtime and do not need any hardcoded data. They do not know how to use them.
Avoid using queries that have invalid characters in them. For example:
      - invalid query: sum by(namespace) (rate(container_cpu_usage_seconds_total{container!"POD",container!""}[5m]))
      - valid query: sum by(namespace) (rate(container_cpu_usage_seconds_total{container!="POD", container!=""}[5m]))
The invalid query is using a character ! which is not allowed in PromQL without escaping it with a backslash . Correct operator for "not equal" is !=.
The quote character " can never follow exclamation !. No queries like that are valid.

You are not allowed to use != in a query. Find alternatives like !~

We want to avoid runtime errors due to invalid queries.

Here is more context about the system:
- The system is OpenShift, a Kubernetes-based container orchestration platform.
- The widgets are part of a dashboard that visualizes various metrics and data related to OpenShift clusters.
- The queries are written in P��������ontainer!"POD",container!""}[5m]))
      - valid query: sum by(namespace) (rate(container_cpu_usage_seconds_total{container!="POD", container!=""}[5m]))
The invalid query is using a character ! which is not allowed in PromQL without escaping it with a backslash . Correct operator for "not equal" is !=.
The quote character " can never follow exclamation !. No queries like that are valid.

You are not allowed to use != in a query. Find alternatives like !~

We want to avoid runtime errors due to invalid queries.

Here is more context about the system:
- The system is OpenShift, a Kubernetes-based container orchestration platform.
- The widgets are part of a dashboard that visualizes various metrics and data related to OpenShift clusters.
- The queries are written in PromQL, the query language for Prometheus, which is commonly used for monitoring in Kubernetes environments.

We also have a NG UI capabilities that can visualize data that is not related to charts. For example, we can show lists, tables, text blocks, and other UI elements.
Screenshot From 2026-01-26 18-53-43
  • Test if this renders widgets as intended in the overall jhadvig/genie-plugin flow (genie-web-client does not support this currently).
    • It seems I've exhausted my token quota after running evals, will test this out after a while.

saswatamcode and others added 6 commits January 25, 2026 13:41
This commit enables listing Perses dashboards from a cluster, getting a
specific one, as well as returning out of the box dashboards that we
ship with OpenShift platform.

The idea is to for the LLM to first look at Out of the box dashboards,
to see if it can answer a user's question using panels from those. And
if not search wider for any custom dashboards a user might have.

We also allow users (and ourselves!) to specify an LLM-friendly
description of PersesDashboard objects using an annotation
operator.perses.dev/mcp-help! This would help the LLM accurately filter
and select dashboards that match a user's query.

Signed-off-by: Saswata Mukherjee <saswataminsta@yahoo.com>
Signed-off-by: Saswata Mukherjee <saswataminsta@yahoo.com>
The MCP server can now find existing Perses dashboards in the cluster
and narrow them down to panels that, upon visualization, help answer the
user's concerns better. It is assumed that since the queries in these
panels have been through human reviews and used in production, they do
not need to be subject to the same validation and guardrails as we for
the queries that the LLM comes up with on-the-fly.

Upon the LLM sharing it's intent to render one or more panels after
going through the existing dashboards, the MCP server will expose
payloads representing panels in the same structure as found in
jhadvig/genie-plugin, so the UI side has no problems graphing these out.
@rexagod rexagod force-pushed the perses branch 6 times, most recently from 70bca15 to 08bb97a Compare January 25, 2026 11:46
Comment on lines +38 to +55
// DashboardWidget represents a dashboard widget in the format expected by genie-plugin UI.
// This matches the DashboardWidget interface from jhadvig/genie-plugin.
type DashboardWidget struct {
ID string `json:"id" jsonschema:"description=Unique identifier for the widget"`
ComponentType string `json:"componentType" jsonschema:"description=Type of Perses component to render (PersesTimeSeries, PersesPieChart, PersesTable)"`
Position PanelPosition `json:"position" jsonschema:"description=Layout position in 24-column grid (optional, included when available from dashboard layout)"`
Props DashboardWidgetProps `json:"props" jsonschema:"description=Properties passed to the Perses component"`
Breakpoint string `json:"breakpoint" jsonschema:"description=Responsive grid breakpoint (xl/lg/md/sm) inferred from panel width, defaults to lg if position unavailable"`
}

// DashboardWidgetProps contains the properties passed to Perses components.
type DashboardWidgetProps struct {
Query string `json:"query" jsonschema:"description=PromQL query string"`
Duration string `json:"duration" jsonschema:"description=Time duration for the query (e.g. 1h, 24h), defaults to 1h if not specified in dashboard"`
Start string `json:"start,omitempty" jsonschema:"description=Optional explicit start time as RFC3339 or Unix timestamp"`
End string `json:"end,omitempty" jsonschema:"description=Optional explicit end time as RFC3339 or Unix timestamp"`
Step string `json:"step" jsonschema:"description=Query resolution step width (e.g. 15s, 1m, 5m), defaults to 15s if not specified in dashboard"`
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inferred from the genie-plugin repository, specifically, this.

IIUC genie-web-client has no such agnostic interface yet that lets MCP servers instruct the bot on how to render panels (based on certain tools they may have to expose things in an expected format (format_panels_for_ui tool does this here)), as I wasn't able to find any there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rendering widgets through MCP tools is not yet supported, see this Slack thread for more details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants