Skip to content

Commit 6e7439f

Browse files
committed
feat(tools): add tool to query exemplars
Signed-off-by: TJ Hoplock <[email protected]>
1 parent 63dfc45 commit 6e7439f

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

pkg/mcp/promapi.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,20 @@ func rangeQueryApiCall(ctx context.Context, query string, start, end time.Time,
143143
return string(jsonBytes), nil
144144
}
145145

146+
func exemplarQueryApiCall(ctx context.Context, query string, start, end time.Time) (string, error) {
147+
res, err := apiV1Client.QueryExemplars(ctx, query, start, end)
148+
if err != nil {
149+
return "", fmt.Errorf("error executing exemplar query: %w", err)
150+
}
151+
152+
jsonBytes, err := json.Marshal(res)
153+
if err != nil {
154+
return "", fmt.Errorf("error converting query response to JSON: %w", err)
155+
}
156+
157+
return string(jsonBytes), nil
158+
}
159+
146160
func seriesApiCall(ctx context.Context, matchers []string, start, end time.Time) (string, error) {
147161
result, warnings, err := apiV1Client.Series(ctx, matchers, start, end)
148162
if err != nil {

pkg/mcp/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func NewServer(logger *slog.Logger) *server.MCPServer {
2727
// add tools
2828
mcpServer.AddTool(queryTool, queryToolHandler)
2929
mcpServer.AddTool(rangeQueryTool, rangeQueryToolHandler)
30+
mcpServer.AddTool(exemplarQueryTool, exemplarQueryToolHandler)
3031
mcpServer.AddTool(seriesTool, seriesToolHandler)
3132
mcpServer.AddTool(labelNamesTool, labelNamesToolHandler)
3233
mcpServer.AddTool(labelValuesTool, labelValuesToolHandler)

pkg/mcp/tools.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,22 @@ var (
4545
),
4646
)
4747

48+
exemplarQueryTool = mcp.NewTool("exemplar_query",
49+
mcp.WithDescription("Execute a exemplar query against the Prometheus datasource"),
50+
mcp.WithString("query",
51+
mcp.Required(),
52+
mcp.Description("Query to be executed"),
53+
),
54+
mcp.WithString("start_time",
55+
mcp.Description("[Optional] Start timestamp for the query to be executed at."+
56+
" Must be either Unix timestamp or RFC3339. Defaults to 5m ago."),
57+
),
58+
mcp.WithString("end_time",
59+
mcp.Description("[Optional] End timestamp for the query to be executed at."+
60+
" Must be either Unix timestamp or RFC3339. Defaults to current time."),
61+
),
62+
)
63+
4864
seriesTool = mcp.NewTool("series",
4965
mcp.WithDescription("Finds series by label matchers"),
5066
mcp.WithArray("matchers",
@@ -227,6 +243,38 @@ func rangeQueryToolHandler(ctx context.Context, request mcp.CallToolRequest) (*m
227243
return mcp.NewToolResultText(data), err
228244
}
229245

246+
func exemplarQueryToolHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
247+
args := request.Params.Arguments
248+
query, ok := args["query"].(string)
249+
if !ok {
250+
return nil, errors.New("query must be a string")
251+
}
252+
253+
endTs := time.Now()
254+
startTs := endTs.Add(DefaultLookbackDelta)
255+
256+
if argEndTime, ok := args["end_time"].(string); ok {
257+
parsedEndTime, err := mcpProm.ParseTimestamp(argEndTime)
258+
if err != nil {
259+
return nil, fmt.Errorf("failed to parse end_time %s from args: %w", argEndTime, err)
260+
}
261+
262+
endTs = parsedEndTime
263+
}
264+
265+
if argStartTime, ok := args["start_time"].(string); ok {
266+
parsedStartTime, err := mcpProm.ParseTimestamp(argStartTime)
267+
if err != nil {
268+
return nil, fmt.Errorf("failed to parse start_time %s from args: %w", argStartTime, err)
269+
}
270+
271+
startTs = parsedStartTime
272+
}
273+
274+
data, err := exemplarQueryApiCall(ctx, query, startTs, endTs)
275+
return mcp.NewToolResultText(data), err
276+
}
277+
230278
func seriesToolHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
231279
args := request.Params.Arguments
232280
argMatchers, ok := args["matchers"].([]any)

0 commit comments

Comments
 (0)