Skip to content

Commit 4371d04

Browse files
authored
feat(tools): Update dashboards with jsonpath (#241)
* enable dashboard updates with patch operations * add integration test * update README * create helper functions for jsonpath dashboard updates * lint * improve tool description * improve tool description * replace jsonpath library
1 parent cea09ee commit 4371d04

File tree

6 files changed

+766
-13
lines changed

6 files changed

+766
-13
lines changed

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,21 @@ _The following features are currently available in MCP server. This list is for
2121
### Dashboards
2222

2323
- **Search for dashboards:** Find dashboards by title or other metadata
24-
- **Get dashboard by UID:** Retrieve full dashboard details using its unique identifier
25-
- **Update or create a dashboard:** Modify existing dashboards or create new ones. _Note: Use with caution due to context window limitations; see [issue #101](https://github.com/grafana/mcp-grafana/issues/101)_
24+
- **Get dashboard by UID:** Retrieve full dashboard details using its unique identifier. _Warning: Large dashboards can consume significant context window space._
25+
- **Get dashboard summary:** Get a compact overview of a dashboard including title, panel count, panel types, variables, and metadata without the full JSON to minimize context window usage
26+
- **Get dashboard property:** Extract specific parts of a dashboard using JSONPath expressions (e.g., `$.title`, `$.panels[*].title`) to fetch only needed data and reduce context window consumption
27+
- **Update or create a dashboard:** Modify existing dashboards or create new ones. _Warning: Requires full dashboard JSON which can consume large amounts of context window space._
28+
- **Patch dashboard:** Apply specific changes to a dashboard without requiring the full JSON, significantly reducing context window usage for targeted modifications
2629
- **Get panel queries and datasource info:** Get the title, query string, and datasource information (including UID and type, if available) from every panel in a dashboard
2730

31+
#### Context Window Management
32+
33+
The dashboard tools now include several strategies to manage context window usage effectively ([issue #101](https://github.com/grafana/mcp-grafana/issues/101)):
34+
35+
- **Use `get_dashboard_summary`** for dashboard overview and planning modifications
36+
- **Use `get_dashboard_property`** with JSONPath when you only need specific dashboard parts
37+
- **Avoid `get_dashboard_by_uid`** unless you specifically need the complete dashboard JSON
38+
2839
### Datasources
2940

3041
- **List and fetch datasource information:** View all configured datasources and retrieve detailed information about each.
@@ -148,6 +159,8 @@ Scopes define the specific resources that permissions apply to. Each action requ
148159
| `get_dashboard_by_uid` | Dashboard | Get a dashboard by uid | `dashboards:read` | `dashboards:uid:abc123` |
149160
| `update_dashboard` | Dashboard | Update or create a new dashboard | `dashboards:create`, `dashboards:write` | `dashboards:*`, `folders:*` or `folders:uid:xyz789` |
150161
| `get_dashboard_panel_queries` | Dashboard | Get panel title, queries, datasource UID and type from a dashboard | `dashboards:read` | `dashboards:uid:abc123` |
162+
| `get_dashboard_property` | Dashboard | Extract specific parts of a dashboard using JSONPath expressions | `dashboards:read` | `dashboards:uid:abc123` |
163+
| `get_dashboard_summary` | Dashboard | Get a compact summary of a dashboard without full JSON | `dashboards:read` | `dashboards:uid:abc123` |
151164
| `list_datasources` | Datasources | List datasources | `datasources:read` | `datasources:*` |
152165
| `get_datasource_by_uid` | Datasources | Get a datasource by uid | `datasources:read` | `datasources:uid:prometheus-uid` |
153166
| `get_datasource_by_name` | Datasources | Get a datasource by name | `datasources:read` | `datasources:*` or `datasources:uid:loki-uid` |

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ toolchain go1.24.2
66

77
require (
88
connectrpc.com/connect v1.18.1
9+
github.com/PaesslerAG/gval v1.2.2
10+
github.com/PaesslerAG/jsonpath v0.1.1
911
github.com/go-openapi/runtime v0.28.0
1012
github.com/go-openapi/strfmt v0.23.0
1113
github.com/google/uuid v1.6.0
@@ -105,6 +107,7 @@ require (
105107
github.com/prometheus/procfs v0.16.1 // indirect
106108
github.com/rivo/uniseg v0.4.7 // indirect
107109
github.com/russross/blackfriday/v2 v2.1.0 // indirect
110+
github.com/shopspring/decimal v1.3.1 // indirect
108111
github.com/smartystreets/assertions v1.0.1 // indirect
109112
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
110113
github.com/spf13/cast v1.7.1 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2pr
33
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
44
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
55
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
6+
github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I=
7+
github.com/PaesslerAG/gval v1.2.2 h1:Y7iBzhgE09IGTt5QgGQ2IdaYYYOU134YGHBThD+wm9E=
8+
github.com/PaesslerAG/gval v1.2.2/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac=
9+
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
10+
github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk=
11+
github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY=
612
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
713
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
814
github.com/apache/arrow-go/v18 v18.3.0 h1:Xq4A6dZj9Nu33sqZibzn012LNnewkTUlfKVUFD/RX/I=
@@ -248,6 +254,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
248254
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
249255
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
250256
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
257+
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
258+
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
251259
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
252260
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
253261
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=

tests/dashboards_test.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import pytest
23
from langevals import expect
34
from langevals_langevals.llm_boolean import (
@@ -42,3 +43,74 @@ async def test_dashboard_panel_queries_tool(model: str, mcp_client: ClientSessio
4243
)
4344
print("content", content)
4445
expect(input=prompt, output=content).to_pass(panel_queries_checker)
46+
47+
48+
@pytest.mark.parametrize("model", models)
49+
@pytest.mark.flaky(max_runs=3)
50+
async def test_dashboard_update_with_patch_operations(model: str, mcp_client: ClientSession):
51+
"""Test that LLMs naturally use patch operations for dashboard updates"""
52+
tools = await get_converted_tools(mcp_client)
53+
54+
# First, create a non-provisioned test dashboard by copying the demo dashboard
55+
# 1. Get the demo dashboard JSON
56+
demo_result = await mcp_client.call_tool("get_dashboard_by_uid", {"uid": "fe9gm6guyzi0wd"})
57+
demo_data = json.loads(demo_result.content[0].text)
58+
dashboard_json = demo_data["dashboard"]
59+
60+
# 2. Remove uid and id to create a new dashboard
61+
if "uid" in dashboard_json:
62+
del dashboard_json["uid"]
63+
if "id" in dashboard_json:
64+
del dashboard_json["id"]
65+
66+
# 3. Set a new title
67+
title = f"Test Dashboard"
68+
dashboard_json["title"] = title
69+
dashboard_json["tags"] = ["python-integration-test"]
70+
71+
# 4. Create the dashboard in Grafana
72+
create_result = await mcp_client.call_tool("update_dashboard", {
73+
"dashboard": dashboard_json,
74+
"folderUid": "",
75+
"overwrite": False
76+
})
77+
create_data = json.loads(create_result.content[0].text)
78+
created_dashboard_uid = create_data["uid"]
79+
80+
# 5. Update the dashboard title
81+
updated_title = f"Updated {title}"
82+
title_prompt = f"Update the title of the Test Dashboard to {updated_title}. Search for the dashboard by title first."
83+
84+
messages = [
85+
Message(role="system", content="You are a helpful assistant"),
86+
Message(role="user", content=title_prompt),
87+
]
88+
89+
# 6. Search for the test dashboard
90+
messages = await llm_tool_call_sequence(
91+
model, messages, tools, mcp_client, "search_dashboards",
92+
{"query": title}
93+
)
94+
95+
# 7. Update the dashboard using patch operations
96+
messages = await llm_tool_call_sequence(
97+
model, messages, tools, mcp_client, "update_dashboard",
98+
{
99+
"uid": created_dashboard_uid,
100+
"operations": [
101+
{
102+
"op": "replace",
103+
"path": "$.title",
104+
"value": updated_title
105+
}
106+
]
107+
}
108+
)
109+
110+
# 8. Final LLM response - just verify it completes successfully
111+
response = await acompletion(model=model, messages=messages, tools=tools)
112+
content = response.choices[0].message.content
113+
114+
# Test passes if we get here - the tool call sequence worked correctly
115+
assert len(content) > 0, "LLM should provide a response after updating the dashboard"
116+

0 commit comments

Comments
 (0)