diff --git a/README.md b/README.md index 5e2067c..7d44866 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This provides access to your Grafana instance and the surrounding ecosystem. ## Features - [x] Search for dashboards +- [x] Get dashboard by UID - [x] List and fetch datasource information - [ ] Query datasources - [x] Prometheus @@ -35,6 +36,7 @@ This is useful if you don't use certain functionality or if you don't want to ta | Tool | Category | Description | | --- | --- | --- | | `search_dashboards` | Search | Search for dashboards | +| `get_dashboard_by_uid` | Dashboard | Get a dashboard by uid | | `list_datasources` | Datasources | List datasources | | `get_datasource_by_uid` | Datasources | Get a datasource by uid | | `get_datasource_by_name` | Datasources | Get a datasource by name | diff --git a/cmd/mcp-grafana/main.go b/cmd/mcp-grafana/main.go index 55ac5e9..15d9419 100644 --- a/cmd/mcp-grafana/main.go +++ b/cmd/mcp-grafana/main.go @@ -23,6 +23,7 @@ func newServer() *server.MCPServer { tools.AddIncidentTools(s) tools.AddPrometheusTools(s) tools.AddLokiTools(s) + tools.AddDashboardTools(s) return s } diff --git a/tools/dashboard.go b/tools/dashboard.go new file mode 100644 index 0000000..72c7744 --- /dev/null +++ b/tools/dashboard.go @@ -0,0 +1,35 @@ +package tools + +import ( + "context" + "fmt" + + "github.com/mark3labs/mcp-go/server" + + "github.com/grafana/grafana-openapi-client-go/models" + mcpgrafana "github.com/grafana/mcp-grafana" +) + +type GetDashboardByUIDParams struct { + UID string `json:"uid" jsonschema:"required,description=The UID of the dashboard"` +} + +func getDashboardByUID(ctx context.Context, args GetDashboardByUIDParams) (*models.DashboardFullWithMeta, error) { + c := mcpgrafana.GrafanaClientFromContext(ctx) + + dashboard, err := c.Dashboards.GetDashboardByUID(args.UID) + if err != nil { + return nil, fmt.Errorf("get dashboard by uid %s: %w", args.UID, err) + } + return dashboard.Payload, nil +} + +var GetDashboardByUID = mcpgrafana.MustTool( + "get_dashboard_by_uid", + "Get dashboard by uid", + getDashboardByUID, +) + +func AddDashboardTools(mcp *server.MCPServer) { + GetDashboardByUID.Register(mcp) +} \ No newline at end of file diff --git a/tools/dashboard_test.go b/tools/dashboard_test.go new file mode 100644 index 0000000..b1c4499 --- /dev/null +++ b/tools/dashboard_test.go @@ -0,0 +1,45 @@ +// Requires a Grafana instance running on localhost:3000, +// with a dashboard provisioned. +// Run with `go test -tags integration`. +//go:build integration + +package tools + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDashboardTools(t *testing.T) { + t.Run("get dashboard by uid", func(t *testing.T) { + ctx := newTestContext() + + // First, let's search for a dashboard to get its UID + searchResults, err := searchDashboards(ctx, SearchDashboardsParams{}) + require.NoError(t, err) + require.Greater(t, len(searchResults), 0, "No dashboards found") + + dashboardUID := searchResults[0].UID + + // Now test the get dashboard by uid functionality + result, err := getDashboardByUID(ctx, GetDashboardByUIDParams{ + UID: dashboardUID, + }) + require.NoError(t, err) + dashboardMap, ok := result.Dashboard.(map[string]interface{}) + require.True(t, ok, "Dashboard should be a map") + assert.Equal(t, dashboardUID, dashboardMap["uid"]) + assert.NotNil(t, result.Meta) + }) + + t.Run("get dashboard by uid - invalid uid", func(t *testing.T) { + ctx := newTestContext() + + _, err := getDashboardByUID(ctx, GetDashboardByUIDParams{ + UID: "non-existent-uid", + }) + require.Error(t, err) + }) +} \ No newline at end of file