Skip to content

Commit 6e1f08a

Browse files
committed
Limit the data returned in list operations
When running list operations against large instances even the default limits were causing things like Cursor or Claude Desktop to bail as the conversation was too large. Using summary types for these calls instead allow them to return with reasonable limits.
1 parent 23ab161 commit 6e1f08a

File tree

3 files changed

+38
-9
lines changed

3 files changed

+38
-9
lines changed

src/mcp_grafana/client.py

+16-6
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,21 @@
88
We should separate HTTP types from tool types.
99
"""
1010

11+
import json
1112
import math
1213
from datetime import datetime
13-
from typing import Any
14+
from typing import Any, List
1415

1516
import httpx
16-
from pydantic import UUID4
17+
from pydantic import UUID4, TypeAdapter
1718

1819
from .settings import grafana_settings
1920
from .grafana_types import (
2021
AddActivityToIncidentArguments,
2122
CreateIncidentArguments,
2223
CreateSiftInvestigationArguments,
24+
Datasource,
25+
IncidentPreview,
2326
QueryIncidentPreviewsRequest,
2427
Query,
2528
SearchDashboardsArguments,
@@ -64,8 +67,10 @@ async def post(self, path: str, json: dict[str, Any]) -> bytes:
6467
raise GrafanaError(r.read().decode())
6568
return r.read()
6669

67-
async def list_datasources(self) -> bytes:
68-
return await self.get("/api/datasources")
70+
async def list_datasources(self) -> List[Datasource]:
71+
response = await self.get("/api/datasources")
72+
ta = TypeAdapter(List[Datasource])
73+
return ta.validate_json(response)
6974

7075
async def get_datasource(
7176
self, uid: str | None = None, name: str | None = None
@@ -90,11 +95,16 @@ async def get_dashboard(self, dashboard_uid: str) -> bytes:
9095
return await self.get(f"/api/dashboards/uid/{dashboard_uid}")
9196

9297
# TODO: split incident stuff into a separate client.
93-
async def list_incidents(self, body: QueryIncidentPreviewsRequest) -> bytes:
94-
return await self.post(
98+
async def list_incidents(
99+
self, body: QueryIncidentPreviewsRequest
100+
) -> List[IncidentPreview]:
101+
response = await self.post(
95102
"/api/plugins/grafana-incident-app/resources/api/IncidentsService.QueryIncidentPreviews",
96103
json=body.model_dump(),
97104
)
105+
response = json.loads(response)
106+
ta = TypeAdapter(List[IncidentPreview])
107+
return ta.validate_python(response["incidentPreviews"])
98108

99109
async def create_incident(self, arguments: CreateIncidentArguments) -> bytes:
100110
return await self.post(

src/mcp_grafana/tools/datasources.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
1+
from typing import List
12
from mcp.server import FastMCP
23

4+
from mcp_grafana.grafana_types import Datasource
5+
36
from ..client import grafana_client
47

58

6-
async def list_datasources() -> bytes:
9+
async def list_datasources() -> List[Datasource]:
710
"""
811
List datasources in the Grafana instance.
912
"""
10-
return await grafana_client.list_datasources()
13+
datasources = await grafana_client.list_datasources()
14+
resp = []
15+
# Only push a subset of fields to save on space.
16+
for ds in datasources:
17+
resp.append(
18+
{
19+
"id": ds.id,
20+
"uid": ds.uid,
21+
"name": ds.name,
22+
"type": ds.type,
23+
"isDefault": ds.is_default,
24+
}
25+
)
26+
27+
return resp
1128

1229

1330
async def get_datasource_by_uid(uid: str) -> bytes:

src/mcp_grafana/tools/incident.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ class ListIncidentsArguments(BaseModel):
3131
)
3232

3333

34-
async def list_incidents(arguments: ListIncidentsArguments) -> bytes:
34+
async def list_incidents(
35+
arguments: ListIncidentsArguments = ListIncidentsArguments(),
36+
) -> bytes:
3537
"""
3638
List incidents from the Grafana Incident incident management tool.
3739

0 commit comments

Comments
 (0)