openclaw-grafana-lens
Version:
OpenClaw plugin that gives AI agents full Grafana access — 18 composable tools for PromQL/LogQL/TraceQL queries, dashboard creation, alerting, SRE investigation, security monitoring, data collection pipeline management via Grafana Alloy (29 recipes), and
475 lines (474 loc) • 14.7 kB
TypeScript
/**
* Grafana HTTP client — self-contained, no external dependencies.
*
* Wraps Grafana's REST API for all operations grafana-lens needs:
* - Dashboards: create, search, get, render, snapshot
* - Queries: PromQL instant/range via datasource proxy
* - Alerting: create/list/delete alert rules (Unified Alerting)
* - Annotations: create/query event markers
* - Datasources: list, discover metrics/labels
* - Folders: create/list for organization
*/
export type GrafanaClientOptions = {
url: string;
apiKey: string;
orgId?: number;
};
export type DashboardCreateRequest = {
dashboard: Record<string, unknown>;
folderUid?: string;
message?: string;
overwrite?: boolean;
};
export type DashboardCreateResponse = {
id: number;
uid: string;
url: string;
status: string;
version: number;
};
export type DashboardSearchResult = {
id: number;
uid: string;
title: string;
url: string;
type: string;
tags: string[];
folderUid?: string;
folderTitle?: string;
folderUrl?: string;
};
export type SnapshotCreateResponse = {
key: string;
deleteKey: string;
url: string;
deleteUrl: string;
id: number;
};
export type DatasourceListItem = {
id: number;
uid: string;
name: string;
type: string;
url: string;
isDefault: boolean;
access: string;
};
export type PrometheusInstantResult = {
status: string;
data: {
resultType: string;
result: Array<{
metric: Record<string, string>;
value: [number, string];
}>;
};
/** Non-fatal warnings from Prometheus (e.g., rate() applied to gauge). */
infos?: string[];
};
export type PrometheusRangeResult = {
status: string;
data: {
resultType: string;
result: Array<{
metric: Record<string, string>;
values: Array<[number, string]>;
}>;
};
/** Non-fatal warnings from Prometheus (e.g., rate() applied to gauge). */
infos?: string[];
};
export type PrometheusLabelValuesResult = {
status: string;
data: string[];
};
export type MetricMetadataItem = {
type: string;
help: string;
unit: string;
};
export type PrometheusMetadataResult = {
status: string;
data: Record<string, MetricMetadataItem[]>;
};
export type LokiStreamEntry = {
stream: Record<string, string>;
values: Array<[string, string]>;
};
export type LokiQueryResult = {
status: string;
data: {
resultType: "streams" | "matrix" | "vector";
result: LokiStreamEntry[] | Array<{
metric: Record<string, string>;
values?: Array<[number, string]>;
value?: [number, string];
}>;
stats?: Record<string, unknown>;
};
};
export type TempoAttribute = {
key: string;
value: {
stringValue?: string;
intValue?: string;
doubleValue?: number;
boolValue?: boolean;
arrayValue?: {
values: Array<{
stringValue?: string;
}>;
};
};
};
export type TempoSearchTrace = {
traceID: string;
rootServiceName: string;
rootTraceName: string;
startTimeUnixNano: string;
durationMs: number;
spanSets?: Array<{
spans: Array<{
spanID: string;
startTimeUnixNano: string;
durationNanos: string;
attributes?: TempoAttribute[];
}>;
matched: number;
}>;
};
export type TempoSearchResult = {
traces: TempoSearchTrace[];
};
export type TempoSpan = {
traceId: string;
spanId: string;
parentSpanId?: string;
operationName?: string;
name?: string;
startTimeUnixNano: string;
endTimeUnixNano: string;
status?: {
code?: number | string;
message?: string;
};
attributes?: TempoAttribute[];
kind?: number | string;
};
export type TempoScopeSpans = {
scope?: {
name?: string;
version?: string;
};
spans: TempoSpan[];
};
export type TempoResourceSpans = {
resource?: {
attributes?: TempoAttribute[];
};
scopeSpans: TempoScopeSpans[];
};
export type TempoTraceResult = {
/** OTLP JSON format (some Tempo versions) */
resourceSpans?: TempoResourceSpans[];
/** Protobuf-JSON format (Tempo v2 default — uses base64 IDs, string kind/status) */
batches?: TempoResourceSpans[];
};
export type AnnotationCreateRequest = {
dashboardUID?: string;
panelId?: number;
time: number;
timeEnd?: number;
tags?: string[];
text: string;
};
export type AnnotationCreateResponse = {
id: number;
message: string;
};
export type AnnotationQueryParams = {
from?: number;
to?: number;
dashboardUID?: string;
panelId?: number;
tags?: string[];
limit?: number;
};
export type Annotation = {
id: number;
dashboardUID: string;
panelId: number;
time: number;
timeEnd: number;
tags: string[];
text: string;
created: number;
updated: number;
};
export type AlertQuery = {
refId: string;
datasourceUid: string;
model: Record<string, unknown>;
relativeTimeRange?: {
from: number;
to: number;
};
queryType?: string;
};
export type AlertRuleCreateRequest = {
title: string;
folderUID: string;
ruleGroup: string;
condition: string;
data: AlertQuery[];
for?: string;
noDataState?: "NoData" | "Alerting" | "OK";
execErrState?: "Alerting" | "OK" | "Error";
labels?: Record<string, string>;
annotations?: Record<string, string>;
};
export type AlertRule = {
uid: string;
title: string;
folderUID: string;
ruleGroup: string;
condition: string;
data: AlertQuery[];
for: string;
noDataState: string;
execErrState: string;
labels: Record<string, string>;
annotations: Record<string, string>;
updated: string;
provenance: string;
};
/** Evaluation state for a single alert rule from Grafana's Prometheus-compatible endpoint. */
export type AlertRuleState = {
uid: string;
state: "inactive" | "firing" | "pending" | "nodata" | "error";
health: "ok" | "nodata" | "error" | "unknown";
lastEvaluation: string;
evaluationTime: number;
isPaused: boolean;
};
export type FolderCreateRequest = {
title: string;
uid?: string;
};
export type Folder = {
id: number;
uid: string;
title: string;
url: string;
};
export type ContactPoint = {
uid: string;
name: string;
type: string;
settings: Record<string, unknown>;
disableResolveMessage: boolean;
};
export type ContactPointCreateRequest = {
name: string;
type: string;
settings: Record<string, unknown>;
disableResolveMessage?: boolean;
};
export type NotificationPolicyTree = {
receiver: string;
group_by?: string[];
routes?: NotificationPolicyRoute[];
};
export type NotificationPolicyRoute = {
receiver?: string;
matchers?: Array<{
name: string;
type: string;
value: string;
}>;
continue?: boolean;
group_by?: string[];
routes?: NotificationPolicyRoute[];
};
export declare class GrafanaClient {
private baseUrl;
private headers;
private url;
constructor(opts: GrafanaClientOptions);
/** Public getter for the instance URL (used by GrafanaClientRegistry). */
getUrl(): string;
private fetchWithTimeout;
/**
* Create or update a dashboard.
* Maps to POST /api/dashboards/db — the same endpoint mcp-grafana's
* update_dashboard tool calls under the hood.
*/
createDashboard(req: DashboardCreateRequest): Promise<DashboardCreateResponse>;
/**
* Search for dashboards by query string with optional filters.
*/
searchDashboards(query: string, opts?: {
tags?: string[];
starred?: boolean;
sort?: string;
limit?: number;
}): Promise<DashboardSearchResult[]>;
/**
* Get a dashboard by UID.
*/
getDashboard(uid: string): Promise<Record<string, unknown>>;
/**
* Render a panel as PNG image.
* Requires the Grafana Image Renderer plugin to be installed.
*
* Uses GET /render/d/{uid}?viewPanel={panelId}&kiosk=true
* (verified from mcp-grafana tools/rendering.go)
*/
renderPanel(dashboardUid: string, panelId: number, opts?: {
width?: number;
height?: number;
from?: string;
to?: string;
theme?: "light" | "dark";
scale?: number;
}): Promise<ArrayBuffer>;
/**
* Create a dashboard snapshot.
* Snapshots freeze the dashboard state at a point in time and provide
* a shareable URL that works without authentication.
*/
createSnapshot(dashboard: Record<string, unknown>, opts?: {
name?: string;
expires?: number;
}): Promise<SnapshotCreateResponse>;
/** Health check — validates the API key works. */
healthCheck(): Promise<boolean>;
/** Returns the full URL for a dashboard by UID. */
dashboardUrl(uid: string): string;
/** List all configured datasources. */
listDatasources(): Promise<DatasourceListItem[]>;
/** Run a PromQL instant query against a Prometheus datasource. */
queryPrometheus(dsUid: string, expr: string, time?: string): Promise<PrometheusInstantResult>;
/** Run a PromQL range query against a Prometheus datasource. */
queryPrometheusRange(dsUid: string, expr: string, start: string, end: string, step: string): Promise<PrometheusRangeResult>;
/** List available metric names from a Prometheus datasource. */
listMetricNames(dsUid: string, opts?: {
match?: string;
}): Promise<string[]>;
/** List values for a specific label from a Prometheus datasource. */
listLabelValues(dsUid: string, label: string): Promise<string[]>;
/** Get metric metadata (type, help, unit) from a Prometheus datasource. */
getMetricMetadata(dsUid: string, opts?: {
limit?: number;
metric?: string;
}): Promise<Record<string, MetricMetadataItem[]>>;
/** Run a LogQL instant query against a Loki datasource. */
queryLoki(dsUid: string, expr: string, opts?: {
time?: string;
limit?: number;
direction?: string;
}): Promise<LokiQueryResult>;
/** Run a LogQL range query against a Loki datasource. */
queryLokiRange(dsUid: string, expr: string, start: string, end: string, opts?: {
step?: string;
limit?: number;
direction?: string;
}): Promise<LokiQueryResult>;
/** Search traces via TraceQL or basic query parameters. */
searchTraces(dsUid: string, query: string, opts?: {
start?: string;
end?: string;
limit?: number;
minDuration?: string;
maxDuration?: string;
spss?: number;
}): Promise<TempoSearchResult>;
/** Get a full trace by trace ID. */
getTrace(dsUid: string, traceId: string): Promise<TempoTraceResult>;
/** Create an annotation on a dashboard or globally. */
createAnnotation(req: AnnotationCreateRequest): Promise<AnnotationCreateResponse>;
/** Query annotations with optional filters. */
getAnnotations(params: AnnotationQueryParams): Promise<Annotation[]>;
/**
* Create an alert rule via Grafana's Unified Alerting provisioning API.
* Sends X-Disable-Provenance so agent-created rules remain editable in UI.
*/
createAlertRule(req: AlertRuleCreateRequest): Promise<AlertRule>;
/** List all alert rules. */
listAlertRules(): Promise<AlertRule[]>;
/** Delete an alert rule by UID. */
deleteAlertRule(uid: string): Promise<void>;
/**
* Fetch evaluation state for all alert rules via Grafana's Prometheus-compatible endpoint.
* Returns a map of rule UID → state info for efficient lookup.
*/
getAlertRuleStates(): Promise<Map<string, AlertRuleState>>;
/** Create a folder for organizing dashboards and alert rules. */
createFolder(req: FolderCreateRequest): Promise<Folder>;
/** List folders. */
listFolders(): Promise<Folder[]>;
/** List configured alert contact points. */
listContactPoints(): Promise<ContactPoint[]>;
/** Create a contact point (webhook, email, etc.). */
createContactPoint(req: ContactPointCreateRequest): Promise<ContactPoint>;
/** Update an existing contact point by UID. */
updateContactPoint(uid: string, req: ContactPointCreateRequest): Promise<void>;
/** Delete a contact point by UID. */
deleteContactPoint(uid: string): Promise<void>;
/** Get the notification policy tree. */
getNotificationPolicies(): Promise<NotificationPolicyTree>;
/** Update the full notification policy tree. */
updateNotificationPolicies(tree: NotificationPolicyTree): Promise<void>;
/** Delete a dashboard by UID. */
deleteDashboard(uid: string): Promise<{
title: string;
}>;
/** Create a silence for matching alerts. */
createSilence(matchers: Array<{
name: string;
value: string;
isRegex: boolean;
}>, duration: string, comment: string, createdBy?: string): Promise<{
silenceID: string;
}>;
/** List all silences. */
listSilences(): Promise<Array<{
id: string;
status: {
state: string;
};
matchers: Array<{
name: string;
value: string;
isRegex: boolean;
}>;
comment: string;
createdBy: string;
endsAt: string;
}>>;
/** Delete (expire) a silence by ID. */
deleteSilence(silenceId: string): Promise<void>;
/** Classify render-specific errors with actionable messages. */
private classifyRenderError;
/** Classify general API errors with actionable messages. */
private classifyError;
}
/**
* Convert Grafana date math to Unix nanoseconds (for Loki).
* Accepted: "now", "now-1h", "now-30m", "now-7d", "now-2w",
* RFC3339 ("2026-01-15T00:00:00Z"), Unix seconds, Unix nanoseconds.
* Throws on unrecognized formats with recovery guidance.
*/
export declare function parseDateMathToNs(time: string): string;
/**
* Convert Grafana date math to Unix seconds (for Prometheus).
* Same accepted formats as parseDateMathToNs().
*/
export declare function parseDateMathToSeconds(time: string): string;
/**
* Convert Grafana date math to epoch milliseconds (for Annotations API).
* Same accepted formats as parseDateMathToNs().
*/
export declare function parseDateMathToMs(time: string): number;
/** Escape special regex characters in user input for safe use in Prometheus match[] selectors. */
export declare function escapeRegex(s: string): string;