Skip to content

Commit 697748d

Browse files
committed
Use separate contextvar with client in it
This avoids the need to create a new client for each tool use, and hopefully lets us reuse connections.
1 parent 50c2d9c commit 697748d

File tree

6 files changed

+35
-31
lines changed

6 files changed

+35
-31
lines changed

src/mcp_grafana/client.py

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

11+
import contextvars
1112
import math
1213
from datetime import datetime
1314
from typing import Any
@@ -236,3 +237,7 @@ async def list_prometheus_label_values(
236237
f"/api/datasources/proxy/uid/{datasource_uid}/api/v1/label/{label_name}/values",
237238
params,
238239
)
240+
241+
242+
grafana_client = contextvars.ContextVar("grafana_client")
243+
grafana_client.set(GrafanaClient.for_current_request())

src/mcp_grafana/middleware.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from starlette.datastructures import Headers
44

5+
from .client import GrafanaClient, grafana_client
56
from .settings import GrafanaSettings, grafana_settings
67

78

@@ -40,13 +41,15 @@ def __init__(self, request):
4041

4142
async def __aenter__(self):
4243
if (info := GrafanaInfo.from_headers(self.request.headers)) is not None:
43-
current = grafana_settings.get()
44-
self.token = grafana_settings.set(
45-
GrafanaSettings(
46-
url=info.url,
47-
api_key=info.authorization,
48-
tools=current.tools,
49-
)
44+
current_settings = grafana_settings.get()
45+
new_settings = GrafanaSettings(
46+
url=info.url,
47+
api_key=info.authorization,
48+
tools=current_settings.tools,
49+
)
50+
self.settings_token = grafana_settings.set(new_settings)
51+
self.client_token = grafana_client.set(
52+
GrafanaClient.from_settings(new_settings)
5053
)
5154

5255
async def __aexit__(self, exc_type, exc_val, exc_tb):

src/mcp_grafana/tools/datasources.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
from mcp.server import FastMCP
22

3-
from ..client import GrafanaClient
3+
from ..client import grafana_client
44

55

66
async def list_datasources() -> bytes:
77
"""
88
List datasources in the Grafana instance.
99
"""
10-
return await GrafanaClient.for_current_request().list_datasources()
10+
return await grafana_client.get().list_datasources()
1111

1212

1313
async def get_datasource_by_uid(uid: str) -> bytes:
1414
"""
1515
Get a datasource by uid.
1616
"""
17-
return await GrafanaClient.for_current_request().get_datasource(uid=uid)
17+
return await grafana_client.get().get_datasource(uid=uid)
1818

1919

2020
async def get_datasource_by_name(name: str) -> bytes:
2121
"""
2222
Get a datasource by name.
2323
"""
24-
return await GrafanaClient.for_current_request().get_datasource(name=name)
24+
return await grafana_client.get().get_datasource(name=name)
2525

2626

2727
def add_tools(mcp: FastMCP):

src/mcp_grafana/tools/incident.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from ..client import (
88
AddActivityToIncidentArguments,
99
CreateIncidentArguments,
10-
GrafanaClient,
10+
grafana_client,
1111
)
1212
from ..grafana_types import QueryIncidentPreviewsRequest, IncidentPreviewsQuery
1313

@@ -49,14 +49,14 @@ async def list_incidents(arguments: ListIncidentsArguments) -> bytes:
4949
),
5050
includeCustomFieldValues=True,
5151
)
52-
return await GrafanaClient.for_current_request().list_incidents(body)
52+
return await grafana_client.get().list_incidents(body)
5353

5454

5555
async def create_incident(arguments: CreateIncidentArguments) -> bytes:
5656
"""
5757
Create an incident in the Grafana Incident incident management tool.
5858
"""
59-
return await GrafanaClient.for_current_request().create_incident(arguments)
59+
return await grafana_client.get().create_incident(arguments)
6060

6161

6262
async def add_activity_to_incident(
@@ -70,7 +70,7 @@ async def add_activity_to_incident(
7070
:param event_time: The time that the activity occurred. If not provided, the current time will be used.
7171
If provided, it must be in RFC3339 format.
7272
"""
73-
return await GrafanaClient.for_current_request().add_activity_to_incident(
73+
return await grafana_client.get().add_activity_to_incident(
7474
AddActivityToIncidentArguments(
7575
incidentId=incident_id,
7676
body=body,
@@ -89,9 +89,7 @@ async def resolve_incident(incident_id: str, summary: str) -> bytes:
8989
This should be succint but thorough and informative,
9090
enough to serve as a mini post-incident-report.
9191
"""
92-
return await GrafanaClient.for_current_request().close_incident(
93-
incident_id, summary
94-
)
92+
return await grafana_client.get().close_incident(incident_id, summary)
9593

9694

9795
def add_tools(mcp: FastMCP):

src/mcp_grafana/tools/prometheus.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from mcp.server import FastMCP
77

8-
from ..client import GrafanaClient
8+
from ..client import grafana_client
99
from ..grafana_types import (
1010
DatasourceRef,
1111
DSQueryResponse,
@@ -56,7 +56,7 @@ async def query_prometheus(
5656
expr=expr, # type: ignore
5757
intervalMs=interval_ms,
5858
)
59-
response = await GrafanaClient.for_current_request().query(start, end, [query])
59+
response = await grafana_client.get().query(start, end, [query])
6060
return DSQueryResponse.model_validate_json(response)
6161

6262

@@ -81,13 +81,11 @@ async def list_prometheus_metric_metadata(
8181
8282
A mapping from metric name to all available metadata for that metric.
8383
"""
84-
response = (
85-
await GrafanaClient.for_current_request().list_prometheus_metric_metadata(
86-
datasource_uid,
87-
limit=limit,
88-
limit_per_metric=limit_per_metric,
89-
metric=metric,
90-
)
84+
response = await grafana_client.get().list_prometheus_metric_metadata(
85+
datasource_uid,
86+
limit=limit,
87+
limit_per_metric=limit_per_metric,
88+
metric=metric,
9189
)
9290
return (
9391
ResponseWrapper[dict[str, list[PrometheusMetricMetadata]]]
@@ -146,7 +144,7 @@ async def list_prometheus_label_names(
146144
end: Optionally, the end time of the time range to filter the results by.
147145
limit: Optionally, the maximum number of results to return. Defaults to 100.
148146
"""
149-
response = await GrafanaClient.for_current_request().list_prometheus_label_names(
147+
response = await grafana_client.get().list_prometheus_label_names(
150148
datasource_uid,
151149
matches=matches,
152150
start=start,
@@ -176,7 +174,7 @@ async def list_prometheus_label_values(
176174
end: Optionally, the end time of the query.
177175
limit: Optionally, the maximum number of results to return. Defaults to 100.
178176
"""
179-
response = await GrafanaClient.for_current_request().list_prometheus_label_values(
177+
response = await grafana_client.get().list_prometheus_label_values(
180178
datasource_uid,
181179
label_name,
182180
matches=matches,

src/mcp_grafana/tools/search.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from mcp.server import FastMCP
22

3-
from ..client import SearchDashboardsArguments, GrafanaClient
3+
from ..client import SearchDashboardsArguments, grafana_client
44

55

66
async def search_dashboards(arguments: SearchDashboardsArguments) -> bytes:
77
"""
88
Search dashboards in the Grafana instance.
99
"""
10-
return await GrafanaClient.for_current_request().search_dashboards(arguments)
10+
return await grafana_client.get().search_dashboards(arguments)
1111

1212

1313
def add_tools(mcp: FastMCP):

0 commit comments

Comments
 (0)